Merge "Fix domain collection for wildcards" into sc-dev
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index 8723515..8fcd2f9 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -146,19 +146,17 @@
      * <p>It is a no-op to set the same schema as has been previously set; this is handled
      * efficiently.
      *
-     * <p>By default, documents are visible on platform surfaces. To opt out, call {@code
-     * SetSchemaRequest.Builder#setPlatformSurfaceable} with {@code surfaceable} as false. Any
-     * visibility settings apply only to the schemas that are included in the {@code request}.
-     * Visibility settings for a schema type do not apply or persist across
-     * {@link SetSchemaRequest}s.
+     * <p>By default, documents are visible on platform surfaces. To opt out, call
+     * {@link SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi} with {@code visible} as
+     * false. Any visibility settings apply only to the schemas that are included in the
+     * {@code request}. Visibility settings for a schema type do not persist across
+     * {@link #setSchema} calls.
      *
      * @param request  The schema update request.
      * @param executor Executor on which to invoke the callback.
      * @param callback Callback to receive errors resulting from setting the schema. If the
      *                 operation succeeds, the callback will be invoked with {@code null}.
      */
-    // TODO(b/169883602): Change @code references to @link when setPlatformSurfaceable APIs are
-    //  exposed.
     public void setSchema(
             @NonNull SetSchemaRequest request,
             @NonNull @CallbackExecutor Executor executor,
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index c369801..a45fa39 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -41,6 +41,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -58,8 +59,11 @@
     private PackageManagerInternal mPackageManagerInternal;
     private ImplInstanceManager mImplInstanceManager;
 
-    // Cache of unlocked user ids so we don't have to query UserManager service each time.
-    private final Set<Integer> mUnlockedUserIds = new ArraySet<>();
+    // Cache of unlocked user ids so we don't have to query UserManager service each time. The
+    // "locked" suffix refers to the fact that access to the field should be locked; unrelated to
+    // the unlocked status of user ids.
+    @GuardedBy("mUnlockedUserIdsLocked")
+    private final Set<Integer> mUnlockedUserIdsLocked = new ArraySet<>();
 
     public AppSearchManagerService(Context context) {
         super(context);
@@ -74,7 +78,9 @@
 
     @Override
     public void onUserUnlocked(@NonNull TargetUser user) {
-        mUnlockedUserIds.add(user.getUserIdentifier());
+        synchronized (mUnlockedUserIdsLocked) {
+            mUnlockedUserIdsLocked.add(user.getUserIdentifier());
+        }
     }
 
     private class Stub extends IAppSearchManager.Stub {
@@ -503,9 +509,11 @@
         }
 
         private void verifyUserUnlocked(int callingUserId) {
-            if (!mUnlockedUserIds.contains(callingUserId)) {
-                throw new IllegalStateException(
-                        "User " + callingUserId + " is locked or not running.");
+            synchronized (mUnlockedUserIdsLocked) {
+                if (!mUnlockedUserIdsLocked.contains(callingUserId)) {
+                    throw new IllegalStateException(
+                            "User " + callingUserId + " is locked or not running.");
+                }
             }
         }
 
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
index 5ea2a02..82319d4 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
@@ -29,6 +29,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.appsearch.external.localstorage.AppSearchImpl;
 
 import java.io.File;
@@ -43,7 +44,9 @@
 
     private static ImplInstanceManager sImplInstanceManager;
 
-    private final SparseArray<AppSearchImpl> mInstances = new SparseArray<>();
+    @GuardedBy("mInstancesLocked")
+    private final SparseArray<AppSearchImpl> mInstancesLocked = new SparseArray<>();
+
     private final String mGlobalQuerierPackage;
 
     private ImplInstanceManager(@NonNull String globalQuerierPackage) {
@@ -81,19 +84,16 @@
      * @return An initialized {@link AppSearchImpl} for this user
      */
     @NonNull
-    public AppSearchImpl getAppSearchImpl(@NonNull Context context, @UserIdInt int userId)
-            throws AppSearchException {
-        AppSearchImpl instance = mInstances.get(userId);
-        if (instance == null) {
-            synchronized (ImplInstanceManager.class) {
-                instance = mInstances.get(userId);
-                if (instance == null) {
-                    instance = createImpl(context, userId);
-                    mInstances.put(userId, instance);
-                }
+    public AppSearchImpl getAppSearchImpl(
+            @NonNull Context context, @UserIdInt int userId) throws AppSearchException {
+        synchronized (mInstancesLocked) {
+            AppSearchImpl instance = mInstancesLocked.get(userId);
+            if (instance == null) {
+                instance = createImpl(context, userId);
+                mInstancesLocked.put(userId, instance);
             }
+            return instance;
         }
-        return instance;
     }
 
     private AppSearchImpl createImpl(@NonNull Context context, @UserIdInt int userId)
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
index 64dc972..babcd25 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
@@ -332,10 +332,8 @@
         for (Map.Entry<String, List<PackageIdentifier>> entry :
                 schemasPackageAccessible.entrySet()) {
             for (int i = 0; i < entry.getValue().size(); i++) {
-                // TODO(b/169883602): remove the "placeholder" uri once upstream changes to relax
-                // nested document uri rules gets synced down.
                 GenericDocument packageAccessibleDocument =
-                        new GenericDocument.Builder(/*uri=*/ "placeholder", PACKAGE_ACCESSIBLE_TYPE)
+                        new GenericDocument.Builder(/*uri=*/"", PACKAGE_ACCESSIBLE_TYPE)
                                 .setNamespace(NAMESPACE)
                                 .setPropertyString(
                                         PACKAGE_NAME_PROPERTY,
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
index 8e62c0e..b2ffd5b 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
@@ -86,11 +86,11 @@
      * <p>It is a no-op to set the same schema as has been previously set; this is handled
      * efficiently.
      *
-     * <p>By default, documents are visible on platform surfaces. To opt out, call {@code
-     * SetSchemaRequest.Builder#setPlatformSurfaceable} with {@code surfaceable} as false. Any
-     * visibility settings apply only to the schemas that are included in the {@code request}.
-     * Visibility settings for a schema type do not apply or persist across {@link
-     * SetSchemaRequest}s.
+     * <p>By default, documents are visible on platform surfaces. To opt out, call
+     * {@link SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi} with {@code visible} as
+     * false. Any visibility settings apply only to the schemas that are included in the
+     * {@code request}. Visibility settings for a schema type do not persist across
+     * {@link #setSchema} calls.
      *
      * <p>Migration: make non-backwards-compatible changes will delete all stored documents in old
      * schema. You can save your documents by setting {@link
@@ -116,8 +116,6 @@
      * @see android.app.appsearch.AppSearchSchema.Migrator
      * @see android.app.appsearch.AppSearchMigrationHelper.Transformer
      */
-    // TODO(b/169883602): Change @code references to @link when setPlatformSurfaceable APIs are
-    //  exposed.
     @NonNull
     ListenableFuture<SetSchemaResponse> setSchema(@NonNull SetSchemaRequest request);
 
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 82e967a..3cefe65 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -64,7 +64,7 @@
  * and which {@link JobServiceContext} to run each job on.
  */
 class JobConcurrencyManager {
-    private static final String TAG = JobSchedulerService.TAG;
+    private static final String TAG = JobSchedulerService.TAG + ".Concurrency";
     private static final boolean DEBUG = JobSchedulerService.DEBUG;
 
     static final String CONFIG_KEY_PREFIX_CONCURRENCY = "concurrency_";
@@ -321,13 +321,14 @@
         }
     }
 
+    /** Return {@code true} if the state was updated. */
     @GuardedBy("mLock")
-    private void refreshSystemStateLocked() {
+    private boolean refreshSystemStateLocked() {
         final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis();
 
         // Only refresh the information every so often.
         if (nowUptime < mNextSystemStateRefreshTime) {
-            return;
+            return false;
         }
 
         final long start = mStatLogger.getTime();
@@ -340,11 +341,14 @@
         }
 
         mStatLogger.logDurationStat(Stats.REFRESH_SYSTEM_STATE, start);
+        return true;
     }
 
     @GuardedBy("mLock")
     private void updateCounterConfigLocked() {
-        refreshSystemStateLocked();
+        if (!refreshSystemStateLocked()) {
+            return;
+        }
 
         final WorkConfigLimitsPerMemoryTrimLevel workConfigs = mEffectiveInteractiveState
                 ? CONFIG_LIMITS_SCREEN_ON : CONFIG_LIMITS_SCREEN_OFF;
@@ -437,9 +441,10 @@
             // (sharing the same Uid as nextPending)
             int minPriorityForPreemption = Integer.MAX_VALUE;
             int selectedContextId = -1;
-            int workType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+            int allWorkTypes = getJobWorkTypes(nextPending);
+            int workType = mWorkCountTracker.canJobStart(allWorkTypes);
             boolean startingJob = false;
-            for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
+            for (int j = 0; j < MAX_JOB_CONTEXTS_COUNT; j++) {
                 JobStatus job = contextIdToJobMap[j];
                 int preferredUid = preferredUidForContext[j];
                 if (job == null) {
@@ -483,7 +488,7 @@
             if (startingJob) {
                 // Increase the counters when we're going to start a job.
                 workTypeForContext[selectedContextId] = workType;
-                mWorkCountTracker.stageJob(workType);
+                mWorkCountTracker.stageJob(workType, allWorkTypes);
             }
         }
         if (DEBUG) {
@@ -578,8 +583,10 @@
 
             JobStatus highestPriorityJob = null;
             int highPriWorkType = workType;
+            int highPriAllWorkTypes = workType;
             JobStatus backupJob = null;
             int backupWorkType = WORK_TYPE_NONE;
+            int backupAllWorkTypes = WORK_TYPE_NONE;
             for (int i = 0; i < pendingJobs.size(); i++) {
                 final JobStatus nextPending = pendingJobs.get(i);
 
@@ -589,11 +596,12 @@
 
                 if (worker.getPreferredUid() != nextPending.getUid()) {
                     if (backupJob == null) {
-                        int workAsType =
-                                mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+                        int allWorkTypes = getJobWorkTypes(nextPending);
+                        int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
                         if (workAsType != WORK_TYPE_NONE) {
                             backupJob = nextPending;
                             backupWorkType = workAsType;
+                            backupAllWorkTypes = allWorkTypes;
                         }
                     }
                     continue;
@@ -611,7 +619,8 @@
                 // reserved slots. We should just run the highest priority job we can find,
                 // though it would be ideal to use an available WorkType slot instead of
                 // overloading slots.
-                final int workAsType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+                highPriAllWorkTypes = getJobWorkTypes(nextPending);
+                final int workAsType = mWorkCountTracker.canJobStart(highPriAllWorkTypes);
                 if (workAsType == WORK_TYPE_NONE) {
                     // Just use the preempted job's work type since this new one is technically
                     // replacing it anyway.
@@ -624,7 +633,7 @@
                 if (DEBUG) {
                     Slog.d(TAG, "Running job " + jobStatus + " as preemption");
                 }
-                mWorkCountTracker.stageJob(highPriWorkType);
+                mWorkCountTracker.stageJob(highPriWorkType, highPriAllWorkTypes);
                 startJobLocked(worker, highestPriorityJob, highPriWorkType);
             } else {
                 if (DEBUG) {
@@ -635,7 +644,7 @@
                     if (DEBUG) {
                         Slog.d(TAG, "Running job " + jobStatus + " instead");
                     }
-                    mWorkCountTracker.stageJob(backupWorkType);
+                    mWorkCountTracker.stageJob(backupWorkType, backupAllWorkTypes);
                     startJobLocked(worker, backupJob, backupWorkType);
                 }
             }
@@ -647,6 +656,7 @@
             // find.
             JobStatus highestPriorityJob = null;
             int highPriWorkType = workType;
+            int highPriAllWorkTypes = workType;
             for (int i = 0; i < pendingJobs.size(); i++) {
                 final JobStatus nextPending = pendingJobs.get(i);
 
@@ -654,7 +664,8 @@
                     continue;
                 }
 
-                final int workAsType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+                final int allWorkTypes = getJobWorkTypes(nextPending);
+                final int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
                 if (workAsType == WORK_TYPE_NONE) {
                     continue;
                 }
@@ -663,6 +674,7 @@
                         < nextPending.lastEvaluatedPriority) {
                     highestPriorityJob = nextPending;
                     highPriWorkType = workAsType;
+                    highPriAllWorkTypes = allWorkTypes;
                 }
             }
 
@@ -672,7 +684,7 @@
                 if (DEBUG) {
                     Slog.d(TAG, "About to run job: " + jobStatus);
                 }
-                mWorkCountTracker.stageJob(highPriWorkType);
+                mWorkCountTracker.stageJob(highPriWorkType, highPriAllWorkTypes);
                 startJobLocked(worker, highestPriorityJob, highPriWorkType);
             }
         }
@@ -1102,26 +1114,58 @@
         }
 
         void incrementPendingJobCount(int workTypes) {
-            // We don't know which type we'll classify the job as when we run it yet, so make sure
-            // we have space in all applicable slots.
-            if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) {
-                mNumPendingJobs.put(WORK_TYPE_TOP, mNumPendingJobs.get(WORK_TYPE_TOP) + 1);
-            }
-            if ((workTypes & WORK_TYPE_EJ) == WORK_TYPE_EJ) {
-                mNumPendingJobs.put(WORK_TYPE_EJ, mNumPendingJobs.get(WORK_TYPE_EJ) + 1);
-            }
-            if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) {
-                mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + 1);
-            }
-            if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) {
-                mNumPendingJobs.put(WORK_TYPE_BGUSER, mNumPendingJobs.get(WORK_TYPE_BGUSER) + 1);
+            adjustPendingJobCount(workTypes, true);
+        }
+
+        void decrementPendingJobCount(int workTypes) {
+            if (adjustPendingJobCount(workTypes, false) > 1) {
+                // We don't need to adjust reservations if only one work type was modified
+                // because that work type is the one we're using.
+
+                // 0 is WORK_TYPE_NONE.
+                int workType = 1;
+                int rem = workTypes;
+                while (rem > 0) {
+                    if ((rem & 1) != 0) {
+                        maybeAdjustReservations(workType);
+                    }
+                    rem = rem >>> 1;
+                    workType = workType << 1;
+                }
             }
         }
 
-        void stageJob(@WorkType int workType) {
+        /** Returns the number of WorkTypes that were modified. */
+        private int adjustPendingJobCount(int workTypes, boolean add) {
+            final int adj = add ? 1 : -1;
+
+            int numAdj = 0;
+            // We don't know which type we'll classify the job as when we run it yet, so make sure
+            // we have space in all applicable slots.
+            if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) {
+                mNumPendingJobs.put(WORK_TYPE_TOP, mNumPendingJobs.get(WORK_TYPE_TOP) + adj);
+                numAdj++;
+            }
+            if ((workTypes & WORK_TYPE_EJ) == WORK_TYPE_EJ) {
+                mNumPendingJobs.put(WORK_TYPE_EJ, mNumPendingJobs.get(WORK_TYPE_EJ) + adj);
+                numAdj++;
+            }
+            if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) {
+                mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + adj);
+                numAdj++;
+            }
+            if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) {
+                mNumPendingJobs.put(WORK_TYPE_BGUSER, mNumPendingJobs.get(WORK_TYPE_BGUSER) + adj);
+                numAdj++;
+            }
+
+            return numAdj;
+        }
+
+        void stageJob(@WorkType int workType, int allWorkTypes) {
             final int newNumStartingJobs = mNumStartingJobs.get(workType) + 1;
             mNumStartingJobs.put(workType, newNumStartingJobs);
-            mNumPendingJobs.put(workType, Math.max(0, mNumPendingJobs.get(workType) - 1));
+            decrementPendingJobCount(allWorkTypes);
             if (newNumStartingJobs + mNumRunningJobs.get(workType)
                     > mNumActuallyReservedSlots.get(workType)) {
                 mNumUnspecializedRemaining--;
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 82ee5d8..96f3bcc 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -212,7 +212,7 @@
     final JobPackageTracker mJobPackageTracker = new JobPackageTracker();
     final JobConcurrencyManager mConcurrencyManager;
 
-    static final int MSG_JOB_EXPIRED = 0;
+    static final int MSG_CHECK_INDIVIDUAL_JOB = 0;
     static final int MSG_CHECK_JOB = 1;
     static final int MSG_STOP_JOB = 2;
     static final int MSG_CHECK_JOB_GREEDY = 3;
@@ -1711,6 +1711,12 @@
             if (DEBUG) {
                 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
             }
+            JobStatus newJs = mJobs.getJobByUidAndJobId(jobStatus.getUid(), 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.
+                mHandler.obtainMessage(MSG_CHECK_INDIVIDUAL_JOB, newJs).sendToTarget();
+            }
             return;
         }
 
@@ -1748,7 +1754,11 @@
 
     @Override
     public void onRunJobNow(JobStatus jobStatus) {
-        mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
+        if (jobStatus == null) {
+            mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
+        } else {
+            mHandler.obtainMessage(MSG_CHECK_INDIVIDUAL_JOB, jobStatus).sendToTarget();
+        }
     }
 
     final private class JobHandler extends Handler {
@@ -1764,18 +1774,15 @@
                     return;
                 }
                 switch (message.what) {
-                    case MSG_JOB_EXPIRED: {
-                        JobStatus runNow = (JobStatus) message.obj;
-                        // runNow can be null, which is a controller's way of indicating that its
-                        // state is such that all ready jobs should be run immediately.
-                        if (runNow != null) {
-                            if (!isCurrentlyActiveLocked(runNow)
-                                    && isReadyToBeExecutedLocked(runNow)) {
-                                mJobPackageTracker.notePending(runNow);
-                                addOrderedItem(mPendingJobs, runNow, sPendingJobComparator);
+                    case MSG_CHECK_INDIVIDUAL_JOB: {
+                        JobStatus js = (JobStatus) message.obj;
+                        if (js != null) {
+                            if (isReadyToBeExecutedLocked(js)) {
+                                mJobPackageTracker.notePending(js);
+                                addOrderedItem(mPendingJobs, js, sPendingJobComparator);
                             }
                         } else {
-                            queueReadyJobsForExecutionLocked();
+                            Slog.e(TAG, "Given null job to check individually");
                         }
                     } break;
                     case MSG_CHECK_JOB:
@@ -1909,12 +1916,10 @@
         // This method will check and capture all ready jobs, so we don't need to keep any messages
         // in the queue.
         mHandler.removeMessages(MSG_CHECK_JOB_GREEDY);
+        mHandler.removeMessages(MSG_CHECK_INDIVIDUAL_JOB);
         // MSG_CHECK_JOB is a weaker form of _GREEDY. Since we're checking and queueing all ready
         // jobs, we don't need to keep any MSG_CHECK_JOB messages in the queue.
         mHandler.removeMessages(MSG_CHECK_JOB);
-        // This method will capture all expired jobs that are ready, so there's no need to keep
-        // the _EXPIRED messages in the queue.
-        mHandler.removeMessages(MSG_JOB_EXPIRED);
         if (DEBUG) {
             Slog.d(TAG, "queuing all ready jobs for execution:");
         }
@@ -3178,7 +3183,7 @@
                     TimeUtils.formatDuration(jsc.getTimeoutElapsed() - nowElapsed, pw);
                     pw.println();
                     job.dump(pw, "    ", false, nowElapsed);
-                    int priority = evaluateJobPriorityLocked(jsc.getRunningJobLocked());
+                    int priority = evaluateJobPriorityLocked(job);
                     pw.print("    Evaluated priority: ");
                     pw.println(JobInfo.getPriorityString(priority));
 
@@ -3344,7 +3349,7 @@
                     job.dump(proto, ActiveJob.RunningJob.DUMP, false, nowElapsed);
 
                     proto.write(ActiveJob.RunningJob.EVALUATED_PRIORITY,
-                            evaluateJobPriorityLocked(jsc.getRunningJobLocked()));
+                            evaluateJobPriorityLocked(job));
 
                     proto.write(ActiveJob.RunningJob.TIME_SINCE_MADE_ACTIVE_MS,
                             nowUptime - job.madeActive);
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 d15bae0..da6f9fe 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -20,6 +20,7 @@
 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
+import android.annotation.Nullable;
 import android.app.job.IJobCallback;
 import android.app.job.IJobService;
 import android.app.job.JobInfo;
@@ -326,6 +327,7 @@
     /**
      * Used externally to query the running job. Will return null if there is no job running.
      */
+    @Nullable
     JobStatus getRunningJobLocked() {
         return mRunningJob;
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
index 50723c7..131a6d4 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
@@ -107,8 +107,6 @@
                         taskStatus.contentObserverJobInstance.mChangedUris.add(uri);
                     }
                 }
-                taskStatus.changedAuthorities = null;
-                taskStatus.changedUris = null;
             }
             taskStatus.changedAuthorities = null;
             taskStatus.changedUris = null;
diff --git a/apex/media/framework/api/current.txt b/apex/media/framework/api/current.txt
index 67fa9bb..a2366df 100644
--- a/apex/media/framework/api/current.txt
+++ b/apex/media/framework/api/current.txt
@@ -8,9 +8,10 @@
     method @NonNull public java.util.List<java.lang.String> getSupportedVideoMimeTypes();
     method @NonNull public java.util.List<java.lang.String> getUnsupportedHdrTypes();
     method @NonNull public java.util.List<java.lang.String> getUnsupportedVideoMimeTypes();
-    method public boolean isHdrTypeSupported(@NonNull String) throws android.media.ApplicationMediaCapabilities.FormatNotFoundException;
+    method public boolean isFormatSpecified(@NonNull String);
+    method public boolean isHdrTypeSupported(@NonNull String);
     method public boolean isSlowMotionSupported();
-    method public boolean isVideoMimeTypeSupported(@NonNull String) throws android.media.ApplicationMediaCapabilities.FormatNotFoundException;
+    method public boolean isVideoMimeTypeSupported(@NonNull String);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.ApplicationMediaCapabilities> CREATOR;
   }
@@ -24,10 +25,6 @@
     method @NonNull public android.media.ApplicationMediaCapabilities build();
   }
 
-  public static class ApplicationMediaCapabilities.FormatNotFoundException extends android.util.AndroidException {
-    ctor public ApplicationMediaCapabilities.FormatNotFoundException(@NonNull String);
-  }
-
   public class MediaCommunicationManager {
     method @IntRange(from=1) public int getVersion();
   }
diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
index aefeab6..685cf0d 100644
--- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
+++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
@@ -22,7 +22,6 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.AndroidException;
 import android.util.Log;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -79,17 +78,7 @@
 public final class ApplicationMediaCapabilities implements Parcelable {
     private static final String TAG = "ApplicationMediaCapabilities";
 
-    /**
-     * This exception is thrown when a given format is not specified in the media capabilities.
-     */
-    public static class FormatNotFoundException extends AndroidException {
-        public FormatNotFoundException(@NonNull String format) {
-            super(format);
-        }
-    }
-
     /** List of supported video codec mime types. */
-    // TODO: init it with avc and mpeg4 as application is assuming to support them.
     private Set<String> mSupportedVideoMimeTypes = new HashSet<>();
 
     /** List of unsupported video codec mime types. */
@@ -113,39 +102,54 @@
 
     /**
      * Query if a video codec format is supported by the application.
+     * <p>
+     * If the application has not specified supporting the format or not, this will return false.
+     * Use {@link #isFormatSpecified(String)} to query if a format is specified or not.
+     *
      * @param videoMime The mime type of the video codec format. Must be the one used in
      * {@link MediaFormat#KEY_MIME}.
      * @return true if application supports the video codec format, false otherwise.
-     * @throws FormatNotFoundException if the application did not specify the codec either in the
-     * supported or unsupported formats.
      */
     public boolean isVideoMimeTypeSupported(
-            @NonNull String videoMime) throws FormatNotFoundException {
-        if (mUnsupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {
-            return false;
-        } else if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {
+            @NonNull String videoMime) {
+        if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {
             return true;
-        } else {
-            throw new FormatNotFoundException(videoMime);
         }
+        return false;
     }
 
     /**
      * Query if a HDR type is supported by the application.
+     * <p>
+     * If the application has not specified supporting the format or not, this will return false.
+     * Use {@link #isFormatSpecified(String)} to query if a format is specified or not.
+     *
      * @param hdrType The type of the HDR format.
      * @return true if application supports the HDR format, false otherwise.
-     * @throws FormatNotFoundException if the application did not specify the format either in the
-     * supported or unsupported formats.
      */
     public boolean isHdrTypeSupported(
-            @NonNull @MediaFeature.MediaHdrType String hdrType) throws FormatNotFoundException {
-        if (mUnsupportedHdrTypes.contains(hdrType)) {
-            return false;
-        } else if (mSupportedHdrTypes.contains(hdrType)) {
+            @NonNull @MediaFeature.MediaHdrType String hdrType) {
+        if (mSupportedHdrTypes.contains(hdrType)) {
             return true;
-        } else {
-            throw new FormatNotFoundException(hdrType);
         }
+        return false;
+    }
+
+    /**
+     * Query if a format is specified by the application.
+     * <p>
+     * The format could be either the video format or the hdr format.
+     *
+     * @param format The name of the format.
+     * @return true if application specifies the format, false otherwise.
+     */
+    public boolean isFormatSpecified(@NonNull String format) {
+        if (mSupportedVideoMimeTypes.contains(format) || mUnsupportedVideoMimeTypes.contains(format)
+                || mSupportedHdrTypes.contains(format) || mUnsupportedHdrTypes.contains(format)) {
+            return true;
+
+        }
+        return false;
     }
 
     @Override
diff --git a/apex/media/framework/java/android/media/MediaTranscodeManager.java b/apex/media/framework/java/android/media/MediaTranscodeManager.java
index ce7726a..c924d9a 100644
--- a/apex/media/framework/java/android/media/MediaTranscodeManager.java
+++ b/apex/media/framework/java/android/media/MediaTranscodeManager.java
@@ -1062,14 +1062,8 @@
                             "Source video format hint must be set!");
                 }
 
-                boolean supportHevc = false;
-                try {
-                    supportHevc = mClientCaps.isVideoMimeTypeSupported(
-                            MediaFormat.MIMETYPE_VIDEO_HEVC);
-                } catch (ApplicationMediaCapabilities.FormatNotFoundException ex) {
-                    // Set to false if application did not specify.
-                    supportHevc = false;
-                }
+                boolean supportHevc = mClientCaps.isVideoMimeTypeSupported(
+                        MediaFormat.MIMETYPE_VIDEO_HEVC);
                 if (!supportHevc && MediaFormat.MIMETYPE_VIDEO_HEVC.equals(
                         mSrcVideoFormatHint.getString(MediaFormat.KEY_MIME))) {
                     return true;
diff --git a/config/hiddenapi-unsupported.txt b/config/hiddenapi-unsupported.txt
index 90a526b..48aa8b2 100644
--- a/config/hiddenapi-unsupported.txt
+++ b/config/hiddenapi-unsupported.txt
@@ -204,7 +204,6 @@
 Landroid/os/IUpdateEngine$Stub;-><init>()V
 Landroid/os/IUserManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
 Landroid/os/IUserManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/IUserManager;
-Landroid/os/IVibratorService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/IVibratorService;
 Landroid/os/storage/IObbActionListener$Stub;-><init>()V
 Landroid/os/storage/IStorageManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
 Landroid/os/storage/IStorageManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/storage/IStorageManager;
diff --git a/core/api/current.txt b/core/api/current.txt
index f627f98..dd57b6a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5587,10 +5587,10 @@
     field public static final String EXTRA_PROGRESS = "android.progress";
     field public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
     field public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
-    field public static final String EXTRA_PROMOTE_PICTURE = "android.promotePicture";
     field public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft";
     field public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
     field @Deprecated public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
+    field public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED = "android.showBigPictureWhenCollapsed";
     field public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
     field public static final String EXTRA_SHOW_WHEN = "android.showWhen";
     field @Deprecated public static final String EXTRA_SMALL_ICON = "android.icon";
@@ -6896,6 +6896,7 @@
     method public void onLockTaskModeEntering(@NonNull android.content.Context, @NonNull android.content.Intent, @NonNull String);
     method public void onLockTaskModeExiting(@NonNull android.content.Context, @NonNull android.content.Intent);
     method public void onNetworkLogsAvailable(@NonNull android.content.Context, @NonNull android.content.Intent, long, @IntRange(from=1) int);
+    method public void onOperationSafetyStateChanged(@NonNull android.content.Context, int, boolean);
     method @Deprecated public void onPasswordChanged(@NonNull android.content.Context, @NonNull android.content.Intent);
     method public void onPasswordChanged(@NonNull android.content.Context, @NonNull android.content.Intent, @NonNull android.os.UserHandle);
     method @Deprecated public void onPasswordExpiring(@NonNull android.content.Context, @NonNull android.content.Intent);
@@ -7072,6 +7073,7 @@
     method public boolean isProfileOwnerApp(String);
     method public boolean isProvisioningAllowed(@NonNull String);
     method public boolean isResetPasswordTokenActive(android.content.ComponentName);
+    method public boolean isSafeOperation(int);
     method public boolean isSecurityLoggingEnabled(@Nullable android.content.ComponentName);
     method public boolean isUninstallBlocked(@Nullable android.content.ComponentName, String);
     method public boolean isUniqueDeviceAttestationSupported();
@@ -7244,7 +7246,7 @@
     field public static final String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
     field public static final String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME";
     field public static final String EXTRA_PROVISIONING_LOGO_URI = "android.app.extra.PROVISIONING_LOGO_URI";
-    field public static final String EXTRA_PROVISIONING_MAIN_COLOR = "android.app.extra.PROVISIONING_MAIN_COLOR";
+    field @Deprecated public static final String EXTRA_PROVISIONING_MAIN_COLOR = "android.app.extra.PROVISIONING_MAIN_COLOR";
     field public static final String EXTRA_PROVISIONING_MODE = "android.app.extra.PROVISIONING_MODE";
     field public static final String EXTRA_PROVISIONING_PERMISSION_GRANT_OPT_OUT = "android.app.extra.PROVISIONING_PERMISSION_GRANT_OPT_OUT";
     field public static final String EXTRA_PROVISIONING_SERIAL_NUMBER = "android.app.extra.PROVISIONING_SERIAL_NUMBER";
@@ -7300,6 +7302,7 @@
     field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1
     field public static final int MAKE_USER_EPHEMERAL = 2; // 0x2
     field public static final String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning";
+    field public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1; // 0x1
     field public static final int PASSWORD_COMPLEXITY_HIGH = 327680; // 0x50000
     field public static final int PASSWORD_COMPLEXITY_LOW = 65536; // 0x10000
     field public static final int PASSWORD_COMPLEXITY_MEDIUM = 196608; // 0x30000
@@ -7336,7 +7339,6 @@
     field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
     field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
     field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
-    field public static final int UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION = 1; // 0x1
     field public static final int WIPE_EUICC = 4; // 0x4
     field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1
     field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2
@@ -7490,7 +7492,7 @@
 
   public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {
     method public int describeContents();
-    method public int getReason();
+    method @NonNull public java.util.List<java.lang.Integer> getReasons();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.UnsafeStateException> CREATOR;
   }
@@ -10441,6 +10443,7 @@
     field public static final String USAGE_STATS_SERVICE = "usagestats";
     field public static final String USB_SERVICE = "usb";
     field public static final String USER_SERVICE = "user";
+    field public static final String VIBRATOR_MANAGER_SERVICE = "vibrator_manager";
     field public static final String VIBRATOR_SERVICE = "vibrator";
     field public static final String VPN_MANAGEMENT_SERVICE = "vpn_management";
     field public static final String WALLPAPER_SERVICE = "wallpaper";
@@ -31629,6 +31632,7 @@
     method @NonNull public int[] areEffectsSupported(@NonNull int...);
     method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...);
     method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
+    method public int getId();
     method public abstract boolean hasAmplitudeControl();
     method public abstract boolean hasVibrator();
     method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long);
@@ -31643,10 +31647,12 @@
   }
 
   public abstract class VibratorManager {
+    method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
     method @NonNull public abstract android.os.Vibrator getDefaultVibrator();
     method @NonNull public abstract android.os.Vibrator getVibrator(int);
     method @NonNull public abstract int[] getVibratorIds();
-    method public abstract void vibrate(@NonNull android.os.CombinedVibrationEffect);
+    method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibrationEffect);
+    method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibrationEffect, @Nullable android.os.VibrationAttributes);
   }
 
   public class WorkSource implements android.os.Parcelable {
@@ -38070,6 +38076,10 @@
     method public final void setNotificationsShown(String[]);
     method public final void snoozeNotification(String, long);
     method public final void updateNotificationChannel(@NonNull String, @NonNull android.os.UserHandle, @NonNull android.app.NotificationChannel);
+    field public static final int FLAG_FILTER_TYPE_ALERTING = 2; // 0x2
+    field public static final int FLAG_FILTER_TYPE_CONVERSATIONS = 1; // 0x1
+    field public static final int FLAG_FILTER_TYPE_ONGOING = 8; // 0x8
+    field public static final int FLAG_FILTER_TYPE_SILENT = 4; // 0x4
     field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
     field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
     field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
@@ -38078,6 +38088,7 @@
     field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3
     field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2
     field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
+    field public static final String META_DATA_DEFAULT_FILTER_TYPES = "android.service.notification.default_filter_types";
     field public static final int NOTIFICATION_CHANNEL_OR_GROUP_ADDED = 1; // 0x1
     field public static final int NOTIFICATION_CHANNEL_OR_GROUP_DELETED = 3; // 0x3
     field public static final int NOTIFICATION_CHANNEL_OR_GROUP_UPDATED = 2; // 0x2
@@ -42033,8 +42044,9 @@
     field public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2; // 0x2
     field public static final int OVERRIDE_NETWORK_TYPE_LTE_CA = 1; // 0x1
     field public static final int OVERRIDE_NETWORK_TYPE_NONE = 0; // 0x0
+    field public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4; // 0x4
     field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA = 3; // 0x3
-    field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4
+    field @Deprecated public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4
   }
 
   public class TelephonyManager {
@@ -53586,16 +53598,19 @@
 
   public class EdgeEffect {
     ctor public EdgeEffect(android.content.Context);
+    ctor public EdgeEffect(@NonNull android.content.Context, @Nullable android.util.AttributeSet);
     method public boolean draw(android.graphics.Canvas);
     method public void finish();
     method @Nullable public android.graphics.BlendMode getBlendMode();
     method @ColorInt public int getColor();
+    method public float getDistance();
     method public int getMaxHeight();
     method public int getType();
     method public boolean isFinished();
     method public void onAbsorb(int);
     method public void onPull(float);
     method public void onPull(float, float);
+    method public float onPullDistance(float, float);
     method public void onRelease();
     method public void setBlendMode(@Nullable android.graphics.BlendMode);
     method public void setColor(@ColorInt int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ba7a981..d7dc6ef 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -179,6 +179,7 @@
     field public static final String PACKET_KEEPALIVE_OFFLOAD = "android.permission.PACKET_KEEPALIVE_OFFLOAD";
     field public static final String PEERS_MAC_ADDRESS = "android.permission.PEERS_MAC_ADDRESS";
     field public static final String PERFORM_CDMA_PROVISIONING = "android.permission.PERFORM_CDMA_PROVISIONING";
+    field public static final String PERFORM_IMS_SINGLE_REGISTRATION = "android.permission.PERFORM_IMS_SINGLE_REGISTRATION";
     field public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION";
     field public static final String POWER_SAVER = "android.permission.POWER_SAVER";
     field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
@@ -904,8 +905,8 @@
     field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
     field public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
     field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
-    field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
-    field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
+    field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
+    field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
     field public static final String EXTRA_PROVISIONING_ORGANIZATION_NAME = "android.app.extra.PROVISIONING_ORGANIZATION_NAME";
     field public static final String EXTRA_PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE = "android.app.extra.PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE";
     field public static final String EXTRA_PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER = "android.app.extra.PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER";
@@ -2522,7 +2523,8 @@
     field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio";
     field public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub";
     field public static final String FEATURE_CROSS_LAYER_BLUR = "android.software.cross_layer_blur";
-    field public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery";
+    field @Deprecated public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery";
+    field public static final String FEATURE_INCREMENTAL_DELIVERY_VERSION = "android.software.incremental_delivery_version";
     field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
     field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
     field public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION = "android.hardware.telephony.ims.singlereg";
@@ -9518,10 +9520,12 @@
   }
 
   public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
+    method @Nullable public int[] getAttestationIds();
     method public int getNamespace();
   }
 
   public static final class KeyGenParameterSpec.Builder {
+    method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setAttestationIds(@NonNull int[]);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setNamespace(int);
     method @Deprecated @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int);
   }
@@ -11946,6 +11950,7 @@
     field public static final int CALL_WAITING_STATUS_ENABLED = 1; // 0x1
     field public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4; // 0x4
     field public static final int CALL_WAITING_STATUS_UNKNOWN_ERROR = 3; // 0x3
+    field public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED = "CAPABILITY_ALLOWED_NETWORK_TYPES_USED";
     field public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE = "CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE";
     field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
     field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 0611031..694507d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -391,11 +391,11 @@
     method public java.util.List<java.lang.String> getOwnerInstalledCaCerts(@NonNull android.os.UserHandle);
     method public boolean isCurrentInputMethodSetByOwner();
     method public boolean isFactoryResetProtectionPolicySupported();
+    method @NonNull public static String operationSafetyReasonToString(int);
     method @NonNull public static String operationToString(int);
     method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
     method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void resetDefaultCrossProfileIntentFilters(int);
     method @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public void setNextOperationSafety(int, int);
-    method @NonNull public static String unsafeOperationReasonToString(int);
     field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
     field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6
     field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
@@ -425,6 +425,7 @@
     field public static final int OPERATION_REMOVE_KEY_PAIR = 28; // 0x1c
     field public static final int OPERATION_REMOVE_USER = 6; // 0x6
     field public static final int OPERATION_REQUEST_BUGREPORT = 29; // 0x1d
+    field public static final int OPERATION_SAFETY_REASON_NONE = -1; // 0xffffffff
     field public static final int OPERATION_SET_ALWAYS_ON_VPN_PACKAGE = 30; // 0x1e
     field public static final int OPERATION_SET_APPLICATION_HIDDEN = 15; // 0xf
     field public static final int OPERATION_SET_APPLICATION_RESTRICTIONS = 16; // 0x10
@@ -460,7 +461,6 @@
     field public static final int PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED = 4; // 0x4
     field public static final int PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED = 7; // 0x7
     field public static final int PROVISIONING_RESULT_STARTING_PROFILE_FAILED = 5; // 0x5
-    field public static final int UNSAFE_OPERATION_REASON_NONE = -1; // 0xffffffff
   }
 
   public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable {
@@ -693,6 +693,7 @@
     method @Nullable public String getSystemTextClassifierPackageName();
     method @Nullable public String getWellbeingPackageName();
     method public void holdLock(android.os.IBinder, int);
+    method @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES) public void setKeepUninstalledPackages(@NonNull java.util.List<java.lang.String>);
     field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
     field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption";
     field public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
@@ -913,6 +914,8 @@
     method @NonNull public int[] getSupportedStates();
     method public void removeDeviceStateListener(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateListener);
     method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
+    field public static final int MAXIMUM_DEVICE_STATE = 255; // 0xff
+    field public static final int MINIMUM_DEVICE_STATE = 0; // 0x0
   }
 
   public static interface DeviceStateManager.DeviceStateListener {
@@ -1371,6 +1374,32 @@
     field public static final int RESOURCES_SDK_INT;
   }
 
+  public abstract class CombinedVibrationEffect implements android.os.Parcelable {
+    method public abstract long getDuration();
+  }
+
+  public static final class CombinedVibrationEffect.Mono extends android.os.CombinedVibrationEffect {
+    method public long getDuration();
+    method @NonNull public android.os.VibrationEffect getEffect();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Mono> CREATOR;
+  }
+
+  public static final class CombinedVibrationEffect.Sequential extends android.os.CombinedVibrationEffect {
+    method @NonNull public java.util.List<java.lang.Integer> getDelays();
+    method public long getDuration();
+    method @NonNull public java.util.List<android.os.CombinedVibrationEffect> getEffects();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Sequential> CREATOR;
+  }
+
+  public static final class CombinedVibrationEffect.Stereo extends android.os.CombinedVibrationEffect {
+    method public long getDuration();
+    method @NonNull public android.util.SparseArray<android.os.VibrationEffect> getEffects();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Stereo> CREATOR;
+  }
+
   public class DeviceIdleManager {
     method @NonNull public String[] getSystemPowerWhitelist();
     method @NonNull public String[] getSystemPowerWhitelistExceptIdle();
diff --git a/core/java/android/accounts/OWNERS b/core/java/android/accounts/OWNERS
index ea5fd36..8dcc04a 100644
--- a/core/java/android/accounts/OWNERS
+++ b/core/java/android/accounts/OWNERS
@@ -3,7 +3,6 @@
 sandrakwan@google.com
 hackbod@google.com
 svetoslavganov@google.com
-moltmann@google.com
 fkupolov@google.com
 yamasani@google.com
 omakoto@google.com
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 050f34a..b6fc47f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1208,7 +1208,8 @@
      * of a {@link BigPictureStyle} notification.  This will replace a
      * {@link Builder#setLargeIcon(Icon) large icon} in that state if one was provided.
      */
-    public static final String EXTRA_PROMOTE_PICTURE = "android.promotePicture";
+    public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED =
+            "android.showBigPictureWhenCollapsed";
 
     /**
      * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
@@ -7059,7 +7060,7 @@
         private Icon mBigLargeIcon;
         private boolean mBigLargeIconSet = false;
         private CharSequence mPictureContentDescription;
-        private boolean mPromotePicture;
+        private boolean mShowBigPictureWhenCollapsed;
 
         public BigPictureStyle() {
         }
@@ -7124,7 +7125,7 @@
          */
         @NonNull
         public BigPictureStyle showBigPictureWhenCollapsed(boolean show) {
-            mPromotePicture = show;
+            mShowBigPictureWhenCollapsed = show;
             return this;
         }
 
@@ -7195,7 +7196,7 @@
          */
         @Override
         public RemoteViews makeContentView(boolean increasedHeight) {
-            if (mPicture == null || !mPromotePicture) {
+            if (mPicture == null || !mShowBigPictureWhenCollapsed) {
                 return super.makeContentView(increasedHeight);
             }
 
@@ -7225,7 +7226,7 @@
          */
         @Override
         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
-            if (mPicture == null || !mPromotePicture) {
+            if (mPicture == null || !mShowBigPictureWhenCollapsed) {
                 return super.makeHeadsUpContentView(increasedHeight);
             }
 
@@ -7309,7 +7310,7 @@
                 extras.putCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION,
                         mPictureContentDescription);
             }
-            extras.putBoolean(EXTRA_PROMOTE_PICTURE, mPromotePicture);
+            extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed);
             extras.putParcelable(EXTRA_PICTURE, mPicture);
         }
 
@@ -7330,7 +7331,7 @@
                         extras.getCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION);
             }
 
-            mPromotePicture = extras.getBoolean(EXTRA_PROMOTE_PICTURE);
+            mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED);
             mPicture = extras.getParcelable(EXTRA_PICTURE);
         }
 
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 7404e53..d5e9570 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -167,9 +167,11 @@
 import android.os.SystemConfigManager;
 import android.os.SystemUpdateManager;
 import android.os.SystemVibrator;
+import android.os.SystemVibratorManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.Vibrator;
+import android.os.VibratorManager;
 import android.os.health.SystemHealthManager;
 import android.os.image.DynamicSystemManager;
 import android.os.image.IDynamicSystemService;
@@ -699,6 +701,13 @@
                     }
                 });
 
+        registerService(Context.VIBRATOR_MANAGER_SERVICE, VibratorManager.class,
+                new CachedServiceFetcher<VibratorManager>() {
+                    @Override
+                    public VibratorManager createService(ContextImpl ctx) {
+                        return new SystemVibratorManager(ctx);
+                    }});
+
         registerService(Context.VIBRATOR_SERVICE, Vibrator.class,
                 new CachedServiceFetcher<Vibrator>() {
             @Override
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index d175a66..4dbff0c 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -16,6 +16,8 @@
 
 package android.app.admin;
 
+import static android.app.admin.DevicePolicyManager.OperationSafetyReason;
+
 import android.accounts.AccountManager;
 import android.annotation.BroadcastBehavior;
 import android.annotation.IntDef;
@@ -35,6 +37,7 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.security.KeyChain;
+import android.util.Log;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -72,8 +75,8 @@
  * </div>
  */
 public class DeviceAdminReceiver extends BroadcastReceiver {
-    private static String TAG = "DevicePolicy";
-    private static boolean localLOGV = false;
+    private static final String TAG = "DevicePolicy";
+    private static final boolean LOCAL_LOGV = false;
 
     /**
      * This is the primary action that a device administrator must implement to be
@@ -509,6 +512,36 @@
     public static final String EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE =
             "android.app.extra.TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE";
 
+    /**
+     * Broadcast action: notify the admin that the state of operations that can be unsafe because
+     * of a given reason (specified by the {@link #EXTRA_OPERATION_SAFETY_REASON} {@code int} extra)
+     * has changed (the new value is specified by the {@link #EXTRA_OPERATION_SAFETY_STATE}
+     * {@code boolean} extra).
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_OPERATION_SAFETY_STATE_CHANGED =
+            "android.app.action.OPERATION_SAFETY_STATE_CHANGED";
+
+    /**
+     * An {@code int} extra specifying an {@link OperationSafetyReason}.
+     *
+     * @hide
+     */
+    public static final String EXTRA_OPERATION_SAFETY_REASON  =
+            "android.app.extra.OPERATION_SAFETY_REASON";
+
+    /**
+     * An {@code boolean} extra specifying whether an operation will fail due to a
+     * {@link OperationSafetyReason}. {@code true} means operations that rely on that reason are
+     * safe, while {@code false} means they're unsafe.
+     *
+     * @hide
+     */
+    public static final String EXTRA_OPERATION_SAFETY_STATE  =
+            "android.app.extra.OPERATION_SAFETY_STATE";
+
     private DevicePolicyManager mManager;
     private ComponentName mWho;
 
@@ -1018,6 +1051,51 @@
     }
 
     /**
+     * Called to notify the state of operations that can be unsafe to execute has changed.
+     *
+     * <p><b>Note:/b> notice that the operation safety state might change between the time this
+     * callback is received and the operation's method on {@link DevicePolicyManager} is called, so
+     * calls to the latter could still throw a {@link UnsafeStateException} even when this method
+     * is called with {@code isSafe} as {@code true}
+     *
+     * @param context the running context as per {@link #onReceive}
+     * @param reason the reason an operation could be unsafe.
+     * @param isSafe whether the operation is safe to be executed.
+     */
+    public void onOperationSafetyStateChanged(@NonNull Context context,
+            @OperationSafetyReason int reason, boolean isSafe) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, String.format("onOperationSafetyStateChanged(): %s=%b",
+                    DevicePolicyManager.operationSafetyReasonToString(reason), isSafe));
+        }
+    }
+
+    private void onOperationSafetyStateChanged(Context context, Intent intent) {
+        if (!hasRequiredExtra(intent, EXTRA_OPERATION_SAFETY_REASON)
+                || !hasRequiredExtra(intent, EXTRA_OPERATION_SAFETY_STATE)) {
+            return;
+        }
+
+        int reason = intent.getIntExtra(EXTRA_OPERATION_SAFETY_REASON,
+                DevicePolicyManager.OPERATION_SAFETY_REASON_NONE);
+        if (!DevicePolicyManager.isValidOperationSafetyReason(reason)) {
+            Log.wtf(TAG, "Received invalid reason on " + intent.getAction() + ": " + reason);
+            return;
+        }
+        boolean isSafe = intent.getBooleanExtra(EXTRA_OPERATION_SAFETY_STATE,
+                /* defaultValue=*/ false);
+
+        onOperationSafetyStateChanged(context, reason, isSafe);
+    }
+
+    private boolean hasRequiredExtra(Intent intent, String extra) {
+        if (intent.hasExtra(extra)) return true;
+
+        Log.wtf(TAG, "Missing '" + extra + "' on intent " +  intent);
+        return false;
+    }
+
+    /**
      * Intercept standard device administrator broadcasts.  Implementations
      * should not override this method; it is better to implement the
      * convenience callbacks for each action.
@@ -1025,6 +1103,9 @@
     @Override
     public void onReceive(@NonNull Context context, @NonNull Intent intent) {
         String action = intent.getAction();
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "onReceive(): received " + action + " on user " + context.getUserId());
+        }
 
         if (ACTION_PASSWORD_CHANGED.equals(action)) {
             onPasswordChanged(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER));
@@ -1092,6 +1173,8 @@
         } else if (ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE.equals(action)) {
             onTransferAffiliatedProfileOwnershipComplete(context,
                     intent.getParcelableExtra(Intent.EXTRA_USER));
+        } else if (ACTION_OPERATION_SAFETY_STATE_CHANGED.equals(action)) {
+            onOperationSafetyStateChanged(context, intent);
         }
     }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ff41d1c8..ff6f6a0 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -206,7 +206,6 @@
      * {@link android.os.Build.VERSION_CODES#N}</li>
      * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_SKIP_USER_CONSENT}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_DISCLAIMERS}, optional</li>
@@ -250,7 +249,6 @@
      * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
      * </ul>
      *
      * <p>If provisioning fails, the device returns to its previous state.
@@ -289,7 +287,6 @@
      * <li>{@link #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_DISCLAIMERS}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS}, optional</li>
      * </ul>
@@ -388,8 +385,6 @@
      * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_LOCAL_TIME} (convert to String), optional</li>
      * <li>{@link #EXTRA_PROVISIONING_TIME_ZONE}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_LOCALE}, optional</li>
@@ -438,8 +433,6 @@
      * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_SUPPORT_URL}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_ORGANIZATION_NAME}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
@@ -654,7 +647,10 @@
      *
      * <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE} or
      * {@link #ACTION_PROVISION_MANAGED_DEVICE}.
+     *
+     * @deprecated Color customization is no longer supported in the provisioning flow.
      */
+    @Deprecated
     public static final String EXTRA_PROVISIONING_MAIN_COLOR =
              "android.app.extra.PROVISIONING_MAIN_COLOR";
 
@@ -905,8 +901,10 @@
      * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
      * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
      *
+     * @deprecated This extra is no longer respected in the provisioning flow.
      * @hide
      */
+    @Deprecated
     @SystemApi
     public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL =
             "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
@@ -930,9 +928,11 @@
      * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
      * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
      *
+     * @deprecated This extra is no longer respected in the provisioning flow.
      * @hide
      */
     @SystemApi
+    @Deprecated
     public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI =
             "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
 
@@ -2922,33 +2922,61 @@
         return DebugUtils.constantToString(DevicePolicyManager.class, PREFIX_OPERATION, operation);
     }
 
-    private static final String PREFIX_UNSAFE_OPERATION_REASON = "UNSAFE_OPERATION_REASON_";
+    private static final String PREFIX_OPERATION_SAFETY_REASON = "OPERATION_SAFETY_REASON_";
 
     /** @hide */
-    @IntDef(prefix = PREFIX_UNSAFE_OPERATION_REASON, value = {
-            UNSAFE_OPERATION_REASON_NONE,
-            UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION
+    @IntDef(prefix = PREFIX_OPERATION_SAFETY_REASON, value = {
+            OPERATION_SAFETY_REASON_NONE,
+            OPERATION_SAFETY_REASON_DRIVING_DISTRACTION
     })
     @Retention(RetentionPolicy.SOURCE)
-    public static @interface UnsafeOperationReason {
+    public static @interface OperationSafetyReason {
     }
 
     /** @hide */
     @TestApi
-    public static final int UNSAFE_OPERATION_REASON_NONE = -1;
+    public static final int OPERATION_SAFETY_REASON_NONE = -1;
 
     /**
      * Indicates that a {@link UnsafeStateException} was thrown because the operation would distract
      * the driver of the vehicle.
      */
-    public static final int UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION = 1;
+    public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1;
 
     /** @hide */
     @NonNull
     @TestApi
-    public static String unsafeOperationReasonToString(@UnsafeOperationReason int reason) {
+    public static String operationSafetyReasonToString(@OperationSafetyReason int reason) {
         return DebugUtils.constantToString(DevicePolicyManager.class,
-                PREFIX_UNSAFE_OPERATION_REASON, reason);
+                PREFIX_OPERATION_SAFETY_REASON, reason);
+    }
+
+    /** @hide */
+    public static boolean isValidOperationSafetyReason(@OperationSafetyReason int reason) {
+        return reason == OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
+    }
+
+    /**
+     * Checks if it's safe to run operations that can be affected by the given {@code reason}.
+     *
+     * <p><b>Note:/b> notice that the operation safety state might change between the time this
+     * method returns and the operation's method is called, so calls to the latter could still throw
+     * a {@link UnsafeStateException} even when this method returns {@code true}.
+     *
+     * @param reason currently, only supported reason is
+     * {@link #OPERATION_SAFETY_REASON_DRIVING_DISTRACTION}.
+     *
+     * @return whether it's safe to run operations that can be affected by the given {@code reason}.
+     */
+    // TODO(b/173541467): should it throw SecurityException if caller is not admin?
+    public boolean isSafeOperation(@OperationSafetyReason int reason) {
+        if (mService == null) return false;
+
+        try {
+            return mService.isSafeOperation(reason);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /** @hide */
@@ -13157,7 +13185,7 @@
     @TestApi
     @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS)
     public void setNextOperationSafety(@DevicePolicyOperation int operation,
-            @UnsafeOperationReason int reason) {
+            @OperationSafetyReason int reason) {
         if (mService != null) {
             try {
                 mService.setNextOperationSafety(operation, reason);
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index a0d2977..67f5c36 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.os.UserHandle;
@@ -255,4 +256,14 @@
      * {@link #supportsResetOp(int)} is true.
      */
     public abstract void resetOp(int op, String packageName, @UserIdInt int userId);
+
+    /**
+     * Notifies the system that an unsafe operation reason has changed.
+     *
+     * @throws IllegalArgumentException if {@code checker} is not the same as set on
+     *         {@code DevicePolicyManagerService}.
+     */
+    public abstract void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker,
+            @OperationSafetyReason int reason, boolean isSafe);
+
 }
diff --git a/core/java/android/app/admin/DevicePolicySafetyChecker.java b/core/java/android/app/admin/DevicePolicySafetyChecker.java
index 6c6f2aa..17b74b1 100644
--- a/core/java/android/app/admin/DevicePolicySafetyChecker.java
+++ b/core/java/android/app/admin/DevicePolicySafetyChecker.java
@@ -17,7 +17,7 @@
 
 import android.annotation.NonNull;
 import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
-import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
 
 import com.android.internal.os.IResultReceiver;
 
@@ -31,15 +31,20 @@
     /**
      * Returns whether the given {@code operation} can be safely executed at the moment.
      */
-    @UnsafeOperationReason
+    @OperationSafetyReason
     int getUnsafeOperationReason(@DevicePolicyOperation int operation);
 
     /**
+     * Return whether it's safe to run operations that can be affected by the given {@code reason}.
+     */
+    boolean isSafeOperation(@OperationSafetyReason int reason);
+
+    /**
      * Returns a new exception for when the given {@code operation} cannot be safely executed.
      */
     @NonNull
     default UnsafeStateException newUnsafeStateException(@DevicePolicyOperation int operation,
-            @UnsafeOperationReason int reason) {
+            @OperationSafetyReason int reason) {
         return new UnsafeStateException(operation, reason);
     }
 
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 89f30cc..032cf24 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -492,6 +492,7 @@
     boolean canProfileOwnerResetPasswordWhenLocked(int userId);
 
     void setNextOperationSafety(int operation, int reason);
+    boolean isSafeOperation(int reason);
 
     String getEnrollmentSpecificId(String callerPackage);
     void setOrganizationIdForUser(in String callerPackage, in String enterpriseId, int userId);
diff --git a/core/java/android/app/admin/UnsafeStateException.java b/core/java/android/app/admin/UnsafeStateException.java
index 56eeb06..f1f6526 100644
--- a/core/java/android/app/admin/UnsafeStateException.java
+++ b/core/java/android/app/admin/UnsafeStateException.java
@@ -15,18 +15,20 @@
  */
 package android.app.admin;
 
-import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION;
-import static android.app.admin.DevicePolicyManager.unsafeOperationReasonToString;
+import static android.app.admin.DevicePolicyManager.isValidOperationSafetyReason;
 
 import android.annotation.NonNull;
 import android.annotation.TestApi;
 import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
-import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import com.android.internal.util.Preconditions;
 
+import java.util.Arrays;
+import java.util.List;
+
 /**
  * Exception thrown when a {@link android.app.admin.DevicePolicyManager} operation failed because it
  * was not safe to be executed at that moment.
@@ -39,17 +41,15 @@
 public final class UnsafeStateException extends IllegalStateException implements Parcelable {
 
     private final @DevicePolicyOperation int mOperation;
-    private final @UnsafeOperationReason int mReason;
+    private final @OperationSafetyReason int mReason;
 
     /** @hide */
     @TestApi
     public UnsafeStateException(@DevicePolicyOperation int operation,
-            @UnsafeOperationReason int reason) {
+            @OperationSafetyReason int reason) {
         super();
-        Preconditions.checkArgument(reason == UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION,
-                "invalid reason %d, must be %d (%s)", reason,
-                UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION,
-                unsafeOperationReasonToString(UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION));
+        Preconditions.checkArgument(isValidOperationSafetyReason(reason), "invalid reason %d",
+                reason);
         mOperation = operation;
         mReason = reason;
     }
@@ -61,19 +61,20 @@
     }
 
     /**
-     * Gets the reason the operation is unsafe.
+     * Gets the reasons the operation is unsafe.
      *
      * @return currently, only valid reason is
-     * {@link android.app.admin.DevicePolicyManager#UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION}.
+     * {@link android.app.admin.DevicePolicyManager#OPERATION_SAFETY_REASON_DRIVING_DISTRACTION}.
      */
-    public @UnsafeOperationReason int getReason() {
-        return mReason;
+    @NonNull
+    public List<Integer> getReasons() {
+        return Arrays.asList(mReason);
     }
 
     /** @hide */
     @Override
     public String getMessage() {
-        return DevicePolicyManager.unsafeOperationReasonToString(mReason);
+        return DevicePolicyManager.operationSafetyReasonToString(mReason);
     }
 
     @Override
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/android/app/time/ExternalTimeSuggestion.aidl
similarity index 94%
rename from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
rename to core/java/android/app/time/ExternalTimeSuggestion.aidl
index 14d57bf..07a0fbb 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/core/java/android/app/time/ExternalTimeSuggestion.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package android.app.time;
 
 parcelable ExternalTimeSuggestion;
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.java b/core/java/android/app/time/ExternalTimeSuggestion.java
similarity index 99%
rename from core/java/android/app/timedetector/ExternalTimeSuggestion.java
rename to core/java/android/app/time/ExternalTimeSuggestion.java
index 7ad303a..b566eab 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.java
+++ b/core/java/android/app/time/ExternalTimeSuggestion.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package android.app.time;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl
index 4626543..c4546be 100644
--- a/core/java/android/app/timedetector/ITimeDetectorService.aidl
+++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl
@@ -16,7 +16,7 @@
 
 package android.app.timedetector;
 
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
 import android.app.timedetector.GnssTimeSuggestion;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index df8d797..76f3785 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
+import android.app.time.ExternalTimeSuggestion;
 import android.content.Context;
 import android.os.SystemClock;
 import android.os.TimestampedValue;
diff --git a/core/java/android/app/timedetector/TimeDetectorImpl.java b/core/java/android/app/timedetector/TimeDetectorImpl.java
index f80869f..ef818ef 100644
--- a/core/java/android/app/timedetector/TimeDetectorImpl.java
+++ b/core/java/android/app/timedetector/TimeDetectorImpl.java
@@ -17,6 +17,7 @@
 package android.app.timedetector;
 
 import android.annotation.NonNull;
+import android.app.time.ExternalTimeSuggestion;
 import android.content.Context;
 import android.os.RemoteException;
 import android.os.ServiceManager;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2a402b2..025d777 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3479,6 +3479,7 @@
             STORAGE_STATS_SERVICE,
             WALLPAPER_SERVICE,
             TIME_ZONE_RULES_MANAGER_SERVICE,
+            VIBRATOR_MANAGER_SERVICE,
             VIBRATOR_SERVICE,
             //@hide: STATUS_BAR_SERVICE,
             CONNECTIVITY_SERVICE,
@@ -3625,9 +3626,11 @@
      *   (e.g., GPS) updates.
      *  <dt> {@link #SEARCH_SERVICE} ("search")
      *  <dd> A {@link android.app.SearchManager} for handling search.
+     *  <dt> {@link #VIBRATOR_MANAGER_SERVICE} ("vibrator_manager")
+     *  <dd> A {@link android.os.VibratorManager} for accessing the device vibrators, interacting
+     *  with individual ones and playing synchronized effects on multiple vibrators.
      *  <dt> {@link #VIBRATOR_SERVICE} ("vibrator")
-     *  <dd> A {@link android.os.Vibrator} for interacting with the vibrator
-     *  hardware.
+     *  <dd> A {@link android.os.Vibrator} for interacting with the vibrator hardware.
      *  <dt> {@link #CONNECTIVITY_SERVICE} ("connectivity")
      *  <dd> A {@link android.net.ConnectivityManager ConnectivityManager} for
      *  handling management of network connections.
@@ -3707,6 +3710,8 @@
      * @see android.hardware.SensorManager
      * @see #STORAGE_SERVICE
      * @see android.os.storage.StorageManager
+     * @see #VIBRATOR_MANAGER_SERVICE
+     * @see android.os.VibratorManager
      * @see #VIBRATOR_SERVICE
      * @see android.os.Vibrator
      * @see #CONNECTIVITY_SERVICE
@@ -4034,8 +4039,19 @@
     public static final String WALLPAPER_SERVICE = "wallpaper";
 
     /**
-     * Use with {@link #getSystemService(String)} to retrieve a {@link
-     * android.os.Vibrator} for interacting with the vibration hardware.
+     * Use with {@link #getSystemService(String)} to retrieve a {@link android.os.VibratorManager}
+     * for accessing the device vibrators, interacting with individual ones and playing synchronized
+     * effects on multiple vibrators.
+     *
+     * @see #getSystemService(String)
+     * @see android.os.VibratorManager
+     */
+    @SuppressLint("ServiceName")
+    public static final String VIBRATOR_MANAGER_SERVICE = "vibrator_manager";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a {@link android.os.Vibrator} for
+     * interacting with the vibration hardware.
      *
      * @see #getSystemService(String)
      * @see android.os.Vibrator
diff --git a/core/java/android/content/pm/DataLoaderManager.java b/core/java/android/content/pm/DataLoaderManager.java
index e8fb241..4d79936 100644
--- a/core/java/android/content/pm/DataLoaderManager.java
+++ b/core/java/android/content/pm/DataLoaderManager.java
@@ -41,6 +41,7 @@
      * @param dataLoaderId ID for the new data loader binder service.
      * @param params       DataLoaderParamsParcel object that contains data loader params, including
      *                     its package name, class name, and additional parameters.
+     * @param bindDelayMs  introduce a delay before actual bind in case we want to avoid busylooping
      * @param listener     Callback for the data loader service to report status back to the
      *                     caller.
      * @return false if 1) target ID collides with a data loader that is already bound to data
@@ -48,9 +49,9 @@
      * or 4) fails to bind to the specified data loader service, otherwise return true.
      */
     public boolean bindToDataLoader(int dataLoaderId, @NonNull DataLoaderParamsParcel params,
-            @NonNull IDataLoaderStatusListener listener) {
+            long bindDelayMs, @NonNull IDataLoaderStatusListener listener) {
         try {
-            return mService.bindToDataLoader(dataLoaderId, params, listener);
+            return mService.bindToDataLoader(dataLoaderId, params, bindDelayMs, listener);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/pm/IDataLoaderManager.aidl b/core/java/android/content/pm/IDataLoaderManager.aidl
index 93b3de7..dda4d36 100644
--- a/core/java/android/content/pm/IDataLoaderManager.aidl
+++ b/core/java/android/content/pm/IDataLoaderManager.aidl
@@ -23,7 +23,7 @@
 
 /** @hide */
 interface IDataLoaderManager {
-    boolean bindToDataLoader(int id, in DataLoaderParamsParcel params,
+    boolean bindToDataLoader(int id, in DataLoaderParamsParcel params, long bindDelayMs,
             IDataLoaderStatusListener listener);
     IDataLoader getDataLoader(int dataLoaderId);
     void unbindFromDataLoader(int dataLoaderId);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index a46876e..7fe2a41 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -808,4 +808,6 @@
 
     PackageManager.Property getProperty(String propertyName, String packageName, String className);
     ParceledListSlice queryProperty(String propertyName, int componentType);
+
+    void setKeepUninstalledPackages(in List<String> packageList);
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b95b991b..a3c3500 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -47,8 +47,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
-import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.pm.dex.ArtManager;
+import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
@@ -3592,9 +3592,12 @@
      * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
      * the requisite kernel support to support incremental delivery aka Incremental FileSystem.
      *
-     * @see IncrementalManager#isEnabled
+     * @see IncrementalManager#isFeatureEnabled
      * @hide
+     *
+     * @deprecated Use {@link #FEATURE_INCREMENTAL_DELIVERY_VERSION} instead.
      */
+    @Deprecated
     @SystemApi
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_INCREMENTAL_DELIVERY =
@@ -3602,6 +3605,20 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+     * feature not present - IncFs is not present on the device.
+     * 1 - IncFs v1, core features, no PerUid support. Optional in R.
+     * 2 - IncFs v2, PerUid support, fs-verity support. Required in S.
+     *
+     * @see IncrementalManager#isFeatureEnabled and IncrementalManager#isV2()
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_INCREMENTAL_DELIVERY_VERSION =
+            "android.software.incremental_delivery_version";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
      * The device has tuner hardware to support tuner operations.
      *
      * <p>This feature implies that the device has the tuner HAL implementation.
@@ -9282,4 +9299,20 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Set a list of apps to keep around as APKs even if no user has currently installed it.
+     * @param packageList List of package names to keep cached.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES)
+    @TestApi
+    public void setKeepUninstalledPackages(@NonNull List<String> packageList) {
+        try {
+            ActivityThread.getPackageManager().setKeepUninstalledPackages(packageList);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index a2e533a..0e70a3e 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -28,6 +28,9 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import com.android.internal.util.Parcelling;
+import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Set;
@@ -477,6 +480,8 @@
      */
     public @Nullable CharSequence nonLocalizedDescription;
 
+    private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
+
     /**
      * A {@link Set} of trusted signing certificate digests. If this permission has the {@link
      * #PROTECTION_FLAG_KNOWN_SIGNER} flag set the permission will be granted to a requesting app
@@ -688,6 +693,7 @@
         dest.writeInt(descriptionRes);
         dest.writeInt(requestRes);
         TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
+        sForStringSet.parcel(knownCerts, dest, parcelableFlags);
     }
 
     /** @hide */
@@ -753,5 +759,6 @@
         descriptionRes = source.readInt();
         requestRes = source.readInt();
         nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+        knownCerts = sForStringSet.unparcel(source);
     }
 }
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermission.java b/core/java/android/content/pm/parsing/component/ParsedPermission.java
index 35bb33c..37e0e87 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermission.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermission.java
@@ -21,16 +21,22 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;
 
+import java.util.Locale;
 import java.util.Set;
 
 /** @hide */
 public class ParsedPermission extends ParsedComponent {
 
+    private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
+
     @Nullable
     String backgroundPermission;
     @Nullable
@@ -89,6 +95,19 @@
         return knownCerts;
     }
 
+    protected void setKnownCert(String knownCert) {
+        // Convert the provided digest to upper case for consistent Set membership
+        // checks when verifying the signing certificate digests of requesting apps.
+        this.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
+    }
+
+    protected void setKnownCerts(String[] knownCerts) {
+        this.knownCerts = new ArraySet<>();
+        for (String knownCert : knownCerts) {
+            this.knownCerts.add(knownCert.toUpperCase(Locale.US));
+        }
+    }
+
     public int calculateFootprint() {
         int size = getName().length();
         if (getNonLocalizedLabel() != null) {
@@ -117,6 +136,7 @@
         dest.writeInt(this.protectionLevel);
         dest.writeBoolean(this.tree);
         dest.writeParcelable(this.parsedPermissionGroup, flags);
+        sForStringSet.parcel(knownCerts, dest, flags);
     }
 
     protected ParsedPermission(Parcel in) {
@@ -129,6 +149,7 @@
         this.protectionLevel = in.readInt();
         this.tree = in.readBoolean();
         this.parsedPermissionGroup = in.readParcelable(boot);
+        this.knownCerts = sForStringSet.unparcel(in);
     }
 
     public static final Parcelable.Creator<ParsedPermission> CREATOR =
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
index a7cecbe..8afa70e 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
@@ -25,7 +25,6 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
-import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -33,8 +32,6 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
-import java.util.Locale;
-import java.util.Set;
 
 /** @hide */
 public class ParsedPermissionUtils {
@@ -103,17 +100,12 @@
                 if (resourceType.equals("array")) {
                     final String[] knownCerts = res.getStringArray(knownCertsResource);
                     if (knownCerts != null) {
-                        // Convert the provided digest to upper case for consistent Set membership
-                        // checks when verifying the signing certificate digests of requesting apps.
-                        permission.knownCerts = new ArraySet<>();
-                        for (String knownCert : knownCerts) {
-                            permission.knownCerts.add(knownCert.toUpperCase(Locale.US));
-                        }
+                        permission.setKnownCerts(knownCerts);
                     }
                 } else {
                     final String knownCert = res.getString(knownCertsResource);
                     if (knownCert != null) {
-                        permission.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
+                        permission.setKnownCert(knownCert);
                     }
                 }
                 if (permission.knownCerts == null) {
@@ -126,7 +118,7 @@
                 final String knownCert = sa.getString(
                         R.styleable.AndroidManifestPermission_knownCerts);
                 if (knownCert != null) {
-                    permission.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
+                    permission.setKnownCert(knownCert);
                 }
             }
 
diff --git a/core/java/android/content/pm/permission/OWNERS b/core/java/android/content/pm/permission/OWNERS
index cde7b2a..d302b0a 100644
--- a/core/java/android/content/pm/permission/OWNERS
+++ b/core/java/android/content/pm/permission/OWNERS
@@ -3,7 +3,6 @@
 toddke@android.com
 toddke@google.com
 patb@google.com
-moltmann@google.com
 svetoslavganov@android.com
 svetoslavganov@google.com
 zhanghai@google.com
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index abf694f..bbde8b1 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -252,9 +252,10 @@
             }
 
             if (overrideScale != 1.0f) {
-                applicationDensity = DisplayMetrics.DENSITY_DEFAULT;
                 applicationScale = overrideScale;
                 applicationInvertedScale = 1.0f / overrideScale;
+                applicationDensity = (int) ((DisplayMetrics.DENSITY_DEVICE_STABLE
+                        * applicationInvertedScale) + .5f);
                 compatFlags |= HAS_OVERRIDE_SCALING;
             } else if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
                 applicationDensity = DisplayMetrics.DENSITY_DEVICE;
@@ -519,10 +520,6 @@
         if (isScalingRequired()) {
             float invertedRatio = applicationInvertedScale;
             inoutConfig.densityDpi = (int)((inoutConfig.densityDpi * invertedRatio) + .5f);
-            inoutConfig.screenWidthDp = (int) ((inoutConfig.screenWidthDp * invertedRatio) + .5f);
-            inoutConfig.screenHeightDp = (int) ((inoutConfig.screenHeightDp * invertedRatio) + .5f);
-            inoutConfig.smallestScreenWidthDp =
-                    (int) ((inoutConfig.smallestScreenWidthDp * invertedRatio) + .5f);
             inoutConfig.windowConfiguration.getMaxBounds().scale(invertedRatio);
             inoutConfig.windowConfiguration.getBounds().scale(invertedRatio);
             final Rect appBounds = inoutConfig.windowConfiguration.getAppBounds();
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 8fe7158..8451ded 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -1233,6 +1233,8 @@
                                              int sequenceId) {
             synchronized (mInterfaceLock) {
                 if (mInternalRepeatingRequestEnabled) {
+                    mRepeatingRequestImageReader.setOnImageAvailableListener(
+                            new ImageLoopbackCallback(), mHandler);
                     resumeInternalRepeatingRequest(true);
                 }
             }
@@ -1263,7 +1265,12 @@
                     mRequestUpdatedNeeded = false;
                     resumeInternalRepeatingRequest(false);
                 } else if (mInternalRepeatingRequestEnabled) {
+                    mRepeatingRequestImageReader.setOnImageAvailableListener(
+                            new ImageLoopbackCallback(), mHandler);
                     resumeInternalRepeatingRequest(true);
+                } else {
+                    mRepeatingRequestImageReader.setOnImageAvailableListener(
+                            new ImageLoopbackCallback(), mHandler);
                 }
             }
 
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index f175e7b..2d4b2cc 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -42,6 +42,12 @@
      */
     public static final int INVALID_DEVICE_STATE = -1;
 
+    /** The minimum allowed device state identifier. */
+    public static final int MINIMUM_DEVICE_STATE = 0;
+
+    /** The maximum allowed device state identifier. */
+    public static final int MAXIMUM_DEVICE_STATE = 255;
+
     private final DeviceStateManagerGlobal mGlobal;
 
     /** @hide */
diff --git a/core/java/android/hardware/input/InputDeviceVibrator.java b/core/java/android/hardware/input/InputDeviceVibrator.java
index f4d8a65..a4817ae 100644
--- a/core/java/android/hardware/input/InputDeviceVibrator.java
+++ b/core/java/android/hardware/input/InputDeviceVibrator.java
@@ -74,6 +74,11 @@
     }
 
     @Override
+    public int getId() {
+        return mVibratorId;
+    }
+
+    @Override
     public boolean hasVibrator() {
         return true;
     }
diff --git a/core/java/android/hardware/input/InputDeviceVibratorManager.java b/core/java/android/hardware/input/InputDeviceVibratorManager.java
index a381b02..d843407 100644
--- a/core/java/android/hardware/input/InputDeviceVibratorManager.java
+++ b/core/java/android/hardware/input/InputDeviceVibratorManager.java
@@ -16,9 +16,12 @@
 
 package android.hardware.input;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Binder;
 import android.os.CombinedVibrationEffect;
 import android.os.NullVibrator;
+import android.os.VibrationAttributes;
 import android.os.Vibrator;
 import android.os.VibratorManager;
 import android.util.SparseArray;
@@ -86,6 +89,7 @@
         }
     }
 
+    @NonNull
     @Override
     public int[] getVibratorIds() {
         synchronized (mVibrators) {
@@ -97,6 +101,7 @@
         }
     }
 
+    @NonNull
     @Override
     public Vibrator getVibrator(int vibratorId) {
         synchronized (mVibrators) {
@@ -107,6 +112,7 @@
         return NullVibrator.getInstance();
     }
 
+    @NonNull
     @Override
     public Vibrator getDefaultVibrator() {
         // Returns vibrator ID 0
@@ -119,7 +125,13 @@
     }
 
     @Override
-    public void vibrate(CombinedVibrationEffect effect) {
+    public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
+            String reason, @Nullable VibrationAttributes attributes) {
         mInputManager.vibrate(mDeviceId, effect, mToken);
     }
+
+    @Override
+    public void cancel() {
+        mInputManager.cancelVibrate(mDeviceId, mToken);
+    }
 }
diff --git a/core/java/android/hardware/usb/OWNERS b/core/java/android/hardware/usb/OWNERS
index 8f2b39d..8f5c2a0 100644
--- a/core/java/android/hardware/usb/OWNERS
+++ b/core/java/android/hardware/usb/OWNERS
@@ -1,4 +1,3 @@
 # Bug component: 175220
 
-moltmann@google.com
 badhri@google.com
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index cc86a60..d631f68 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -994,6 +994,19 @@
          */
         public abstract long getScreenOnEnergy();
 
+        /**
+         * Returns the energies used by this uid for each
+         * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer
+         * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+         *
+         * @return energies (in microjoules) used since boot for each (custom) energy consumer of
+         *         type OTHER, indexed by their ordinal. Returns null if no energy reporting is
+         *         supported.
+         *
+         * {@hide}
+         */
+        public abstract @Nullable long[] getCustomMeasuredEnergiesMicroJoules();
+
         public static abstract class Sensor {
 
             @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -2511,6 +2524,19 @@
      */
     public abstract long getScreenDozeEnergy();
 
+    /**
+     * Returns the energies used for each
+     * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer
+     * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+     *
+     * @return energies (in microjoules) used since boot for each (custom) energy consumer of
+     *         type OTHER, indexed by their ordinal. Returns null if no energy reporting is
+     *         supported.
+     *
+     * {@hide}
+     */
+    public abstract @Nullable long[] getCustomMeasuredEnergiesMicroJoules();
+
     public static final BitDescription[] HISTORY_STATE_DESCRIPTIONS = new BitDescription[] {
         new BitDescription(HistoryItem.STATE_CPU_RUNNING_FLAG, "running", "r"),
         new BitDescription(HistoryItem.STATE_WAKE_LOCK_FLAG, "wake_lock", "w"),
@@ -5268,6 +5294,12 @@
                         pw.print(" flash=");
                         printmAh(pw, bs.flashlightPowerMah);
                     }
+                    if (bs.customMeasuredPowerMah != null) {
+                        for (int idx = 0; idx < bs.customMeasuredPowerMah.length; idx++) {
+                            pw.print(" custom[" + idx + "]=");
+                            printmAh(pw, bs.customMeasuredPowerMah[idx]);
+                        }
+                    }
                     pw.print(" )");
                 }
 
diff --git a/core/java/android/os/CombinedVibrationEffect.java b/core/java/android/os/CombinedVibrationEffect.java
index cb4e9cb..c8e682c 100644
--- a/core/java/android/os/CombinedVibrationEffect.java
+++ b/core/java/android/os/CombinedVibrationEffect.java
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.annotation.NonNull;
+import android.annotation.TestApi;
 import android.util.SparseArray;
 
 import com.android.internal.util.Preconditions;
@@ -86,7 +87,20 @@
         return 0;
     }
 
-    /** @hide */
+    /**
+     * Gets the estimated duration of the combined vibration in milliseconds.
+     *
+     * <p>For synced combinations this means the maximum duration of any individual {@link
+     * VibrationEffect}. For sequential combinations, this is a sum of each step and delays.
+     *
+     * <p>For combinations of effects without a defined end (e.g. a Waveform with a non-negative
+     * repeat index), this returns Long.MAX_VALUE. For effects with an unknown duration (e.g.
+     * Prebaked effects where the length is device and potentially run-time dependent), this returns
+     * -1.
+     *
+     * @hide
+     */
+    @TestApi
     public abstract long getDuration();
 
     /** @hide */
@@ -256,6 +270,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final class Mono extends CombinedVibrationEffect {
         private final VibrationEffect mEffect;
 
@@ -267,6 +282,7 @@
             mEffect = effect;
         }
 
+        @NonNull
         public VibrationEffect getEffect() {
             return mEffect;
         }
@@ -282,6 +298,7 @@
             mEffect.validate();
         }
 
+        /** @hide */
         @Override
         public boolean hasVibrator(int vibratorId) {
             return true;
@@ -307,7 +324,12 @@
         }
 
         @Override
-        public void writeToParcel(Parcel out, int flags) {
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel out, int flags) {
             out.writeInt(PARCEL_TOKEN_MONO);
             mEffect.writeToParcel(out, flags);
         }
@@ -335,6 +357,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final class Stereo extends CombinedVibrationEffect {
 
         /** Mapping vibrator ids to effects. */
@@ -357,6 +380,7 @@
         }
 
         /** Effects to be performed in sync, where each key represents the vibrator id. */
+        @NonNull
         public SparseArray<VibrationEffect> getEffects() {
             return mEffects;
         }
@@ -394,6 +418,7 @@
             }
         }
 
+        /** @hide */
         @Override
         public boolean hasVibrator(int vibratorId) {
             return mEffects.indexOfKey(vibratorId) >= 0;
@@ -418,7 +443,7 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(mEffects);
+            return mEffects.contentHashCode();
         }
 
         @Override
@@ -427,7 +452,12 @@
         }
 
         @Override
-        public void writeToParcel(Parcel out, int flags) {
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel out, int flags) {
             out.writeInt(PARCEL_TOKEN_STEREO);
             out.writeInt(mEffects.size());
             for (int i = 0; i < mEffects.size(); i++) {
@@ -459,6 +489,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final class Sequential extends CombinedVibrationEffect {
         private final List<CombinedVibrationEffect> mEffects;
         private final List<Integer> mDelays;
@@ -480,11 +511,13 @@
         }
 
         /** Effects to be performed in sequence. */
+        @NonNull
         public List<CombinedVibrationEffect> getEffects() {
             return mEffects;
         }
 
         /** Delay to be applied before each effect in {@link #getEffects()}. */
+        @NonNull
         public List<Integer> getDelays() {
             return mDelays;
         }
@@ -542,6 +575,7 @@
             }
         }
 
+        /** @hide */
         @Override
         public boolean hasVibrator(int vibratorId) {
             final int effectCount = mEffects.size();
@@ -564,7 +598,7 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(mEffects);
+            return Objects.hash(mEffects, mDelays);
         }
 
         @Override
@@ -573,7 +607,12 @@
         }
 
         @Override
-        public void writeToParcel(Parcel out, int flags) {
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel out, int flags) {
             out.writeInt(PARCEL_TOKEN_SEQUENTIAL);
             out.writeInt(mEffects.size());
             for (int i = 0; i < mEffects.size(); i++) {
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 6a76da2..eac03dc 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -1902,7 +1902,8 @@
      * Retrieves the PSS memory used by the process as given by the smaps. Optionally supply a long
      * array of up to 3 entries to also receive (up to 3 values in order): the Uss and SwapPss and
      * Rss (only filled in as of {@link android.os.Build.VERSION_CODES#P}) of the process, and
-     * another array to also retrieve the separate memtrack size.
+     * another array to also retrieve the separate memtrack sizes (up to 4 values in order): the
+     * total memtrack reported size, memtrack graphics, memtrack gl and memtrack other.
      *
      * @return The PSS memory usage, or 0 if failed to retrieve (i.e., given pid has gone).
      * @hide
@@ -2589,6 +2590,13 @@
     public static native long getIonPoolsSizeKb();
 
     /**
+     * Return GPU DMA buffer usage in kB or -1 on error.
+     *
+     * @hide
+     */
+    public static native long getGpuDmaBufUsageKb();
+
+    /**
      * Return DMA-BUF memory mapped by processes in kB.
      * Notes:
      *  * Warning: Might impact performance as it reads /proc/<pid>/maps files for each process.
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
deleted file mode 100644
index 1cd48dc..0000000
--- a/core/java/android/os/IVibratorService.aidl
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * Copyright (c) 2007, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-import android.os.VibrationEffect;
-import android.os.VibrationAttributes;
-import android.os.VibratorInfo;
-import android.os.IVibratorStateListener;
-
-/** {@hide} */
-interface IVibratorService
-{
-    boolean hasVibrator();
-    boolean isVibrating();
-    VibratorInfo getVibratorInfo();
-    boolean registerVibratorStateListener(in IVibratorStateListener listener);
-    boolean unregisterVibratorStateListener(in IVibratorStateListener listener);
-    boolean hasAmplitudeControl();
-    void vibrate(int uid, String opPkg, in VibrationEffect effect,
-            in VibrationAttributes attributes, String reason, IBinder token);
-    void cancelVibrate(IBinder token);
-}
-
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 2559a33..dac1ede 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -4,7 +4,6 @@
 per-file IExternalVibrationController.aidl = michaelwr@google.com
 per-file IExternalVibratorService.aidl = michaelwr@google.com
 per-file IVibratorManagerService.aidl = michaelwr@google.com
-per-file IVibratorService.aidl = michaelwr@google.com
 per-file NullVibrator.java = michaelwr@google.com
 per-file SystemVibrator.java = michaelwr@google.com
 per-file VibrationEffect.aidl = michaelwr@google.com
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 30afe38..b42a495 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -17,19 +17,18 @@
 package android.os;
 
 import android.annotation.CallbackExecutor;
-import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.media.AudioAttributes;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -41,142 +40,46 @@
 public class SystemVibrator extends Vibrator {
     private static final String TAG = "Vibrator";
 
-    private static final int VIBRATOR_PRESENT_UNKNOWN = 0;
-    private static final int VIBRATOR_PRESENT_YES = 1;
-    private static final int VIBRATOR_PRESENT_NO = 2;
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({VIBRATOR_PRESENT_UNKNOWN, VIBRATOR_PRESENT_YES, VIBRATOR_PRESENT_NO})
-    private @interface VibratorPresent {}
-
-    private final IVibratorService mService;
-    private final IVibratorManagerService mManagerService;
-    private final Object mLock = new Object();
-    private final Binder mToken = new Binder();
+    private final VibratorManager mVibratorManager;
     private final Context mContext;
-    @GuardedBy("mLock")
-    private VibratorInfo mVibratorInfo;
-    @GuardedBy("mLock")
-    @VibratorPresent
-    private int mVibratorPresent;
 
-    @GuardedBy("mDelegates")
-    private final ArrayMap<OnVibratorStateChangedListener,
-            OnVibratorStateChangedListenerDelegate> mDelegates = new ArrayMap<>();
+    @GuardedBy("mBrokenListeners")
+    private final ArrayList<AllVibratorsStateListener> mBrokenListeners = new ArrayList<>();
 
-    @UnsupportedAppUsage
-    public SystemVibrator() {
-        mContext = null;
-        mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
-        mManagerService = IVibratorManagerService.Stub.asInterface(
-                ServiceManager.getService("vibrator_manager"));
-    }
+    @GuardedBy("mRegisteredListeners")
+    private final ArrayMap<OnVibratorStateChangedListener, AllVibratorsStateListener>
+            mRegisteredListeners = new ArrayMap<>();
 
     @UnsupportedAppUsage
     public SystemVibrator(Context context) {
         super(context);
         mContext = context;
-        mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
-        mManagerService = IVibratorManagerService.Stub.asInterface(
-                ServiceManager.getService("vibrator_manager"));
+        mVibratorManager = mContext.getSystemService(VibratorManager.class);
     }
 
     @Override
     public boolean hasVibrator() {
-        try {
-            synchronized (mLock) {
-                if (mVibratorPresent == VIBRATOR_PRESENT_UNKNOWN && mService != null) {
-                    mVibratorPresent =
-                            mService.hasVibrator() ? VIBRATOR_PRESENT_YES : VIBRATOR_PRESENT_NO;
-                }
-                return mVibratorPresent == VIBRATOR_PRESENT_YES;
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to query vibrator presence", e);
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to check if vibrator exists; no vibrator manager.");
             return false;
         }
+        return mVibratorManager.getVibratorIds().length > 0;
     }
 
-    /**
-     * Check whether the vibrator is vibrating.
-     *
-     * @return True if the hardware is vibrating, otherwise false.
-     */
     @Override
     public boolean isVibrating() {
-        if (mService == null) {
-            Log.w(TAG, "Failed to vibrate; no vibrator service.");
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to vibrate; no vibrator manager.");
             return false;
         }
-        try {
-            return mService.isVibrating();
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+        for (int vibratorId : mVibratorManager.getVibratorIds()) {
+            if (mVibratorManager.getVibrator(vibratorId).isVibrating()) {
+                return true;
+            }
         }
         return false;
     }
 
-    private class OnVibratorStateChangedListenerDelegate extends
-            IVibratorStateListener.Stub {
-        private final Executor mExecutor;
-        private final OnVibratorStateChangedListener mListener;
-
-        OnVibratorStateChangedListenerDelegate(@NonNull OnVibratorStateChangedListener listener,
-                @NonNull Executor executor) {
-            mExecutor = executor;
-            mListener = listener;
-        }
-
-        @Override
-        public void onVibrating(boolean isVibrating) {
-            mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating));
-        }
-    }
-
-    /**
-     * Adds a listener for vibrator state change. If the listener was previously added and not
-     * removed, this call will be ignored.
-     *
-     * @param listener Listener to be added.
-     * @param executor The {@link Executor} on which the listener's callbacks will be executed on.
-     */
-    @Override
-    public void addVibratorStateListener(
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull OnVibratorStateChangedListener listener) {
-        Objects.requireNonNull(listener);
-        Objects.requireNonNull(executor);
-        if (mService == null) {
-            Log.w(TAG, "Failed to add vibrate state listener; no vibrator service.");
-            return;
-        }
-
-        synchronized (mDelegates) {
-            // If listener is already registered, reject and return.
-            if (mDelegates.containsKey(listener)) {
-                Log.w(TAG, "Listener already registered.");
-                return;
-            }
-            try {
-                final OnVibratorStateChangedListenerDelegate delegate =
-                        new OnVibratorStateChangedListenerDelegate(listener, executor);
-                if (!mService.registerVibratorStateListener(delegate)) {
-                    Log.w(TAG, "Failed to register vibrate state listener");
-                    return;
-                }
-                mDelegates.put(listener, delegate);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-    }
-
-    /**
-     * Adds a listener for vibrator state changes. Callbacks will be executed on the main thread.
-     * If the listener was previously added and not removed, this call will be ignored.
-     *
-     * @param listener listener to be added
-     */
     @Override
     public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
         Objects.requireNonNull(listener);
@@ -187,88 +90,128 @@
         addVibratorStateListener(mContext.getMainExecutor(), listener);
     }
 
-    /**
-     * Removes the listener for vibrator state changes. If the listener was not previously
-     * registered, this call will do nothing.
-     *
-     * @param listener Listener to be removed.
-     */
     @Override
-    public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+    public void addVibratorStateListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnVibratorStateChangedListener listener) {
         Objects.requireNonNull(listener);
-        if (mService == null) {
-            Log.w(TAG, "Failed to remove vibrate state listener; no vibrator service.");
+        Objects.requireNonNull(executor);
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager.");
             return;
         }
-        synchronized (mDelegates) {
-            // Check if the listener is registered, otherwise will return.
-            if (mDelegates.containsKey(listener)) {
-                final OnVibratorStateChangedListenerDelegate delegate = mDelegates.get(listener);
-                try {
-                    if (!mService.unregisterVibratorStateListener(delegate)) {
-                        Log.w(TAG, "Failed to unregister vibrate state listener");
-                        return;
-                    }
-                    mDelegates.remove(listener);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
+        AllVibratorsStateListener delegate = null;
+        try {
+            synchronized (mRegisteredListeners) {
+                // If listener is already registered, reject and return.
+                if (mRegisteredListeners.containsKey(listener)) {
+                    Log.w(TAG, "Listener already registered.");
+                    return;
+                }
+                delegate = new AllVibratorsStateListener(executor, listener);
+                delegate.register(mVibratorManager);
+                mRegisteredListeners.put(listener, delegate);
+                delegate = null;
+            }
+        } finally {
+            if (delegate != null && delegate.hasRegisteredListeners()) {
+                // The delegate listener was left in a partial state with listeners registered to
+                // some but not all vibrators. Keep track of this to try to unregister them later.
+                synchronized (mBrokenListeners) {
+                    mBrokenListeners.add(delegate);
                 }
             }
+            tryUnregisterBrokenListeners();
         }
     }
 
     @Override
+    public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+        Objects.requireNonNull(listener);
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to remove vibrate state listener; no vibrator manager.");
+            return;
+        }
+        synchronized (mRegisteredListeners) {
+            if (mRegisteredListeners.containsKey(listener)) {
+                AllVibratorsStateListener delegate = mRegisteredListeners.get(listener);
+                delegate.unregister(mVibratorManager);
+                mRegisteredListeners.remove(listener);
+            }
+        }
+        tryUnregisterBrokenListeners();
+    }
+
+    @Override
     public boolean hasAmplitudeControl() {
-        if (mService == null) {
-            Log.w(TAG, "Failed to check amplitude control; no vibrator service.");
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to check vibrator has amplitude control; no vibrator manager.");
             return false;
         }
-        try {
-            return mService.hasAmplitudeControl();
-        } catch (RemoteException e) {
+        int[] vibratorIds = mVibratorManager.getVibratorIds();
+        if (vibratorIds.length == 0) {
+            return false;
         }
-        return false;
+        for (int vibratorId : vibratorIds) {
+            if (!mVibratorManager.getVibrator(vibratorId).hasAmplitudeControl()) {
+                return false;
+            }
+        }
+        return true;
     }
 
     @Override
     public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
             AudioAttributes attributes) {
-        if (mManagerService == null) {
-            Log.w(TAG, "Failed to set always-on effect; no vibrator service.");
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to set always-on effect; no vibrator manager.");
             return false;
         }
-        try {
-            VibrationAttributes atr = new VibrationAttributes.Builder(attributes, effect).build();
-            CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
-            return mManagerService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, atr);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to set always-on effect.", e);
-        }
-        return false;
+        VibrationAttributes attr = new VibrationAttributes.Builder(attributes, effect).build();
+        CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
+        return mVibratorManager.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, attr);
     }
 
     @Override
     public void vibrate(int uid, String opPkg, @NonNull VibrationEffect effect,
             String reason, @NonNull VibrationAttributes attributes) {
-        if (mService == null) {
-            Log.w(TAG, "Failed to vibrate; no vibrator service.");
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to vibrate; no vibrator manager.");
             return;
         }
-        try {
-            mService.vibrate(uid, opPkg, effect, attributes, reason, mToken);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to vibrate.", e);
-        }
+        CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
+        mVibratorManager.vibrate(uid, opPkg, combinedEffect, reason, attributes);
     }
 
     @Override
     public int[] areEffectsSupported(@VibrationEffect.EffectType int... effectIds) {
-        VibratorInfo vibratorInfo = getVibratorInfo();
         int[] supported = new int[effectIds.length];
-        for (int i = 0; i < effectIds.length; i++) {
-            supported[i] = vibratorInfo == null
-                    ? Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN
-                    : vibratorInfo.isEffectSupported(effectIds[i]);
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to check supported effects; no vibrator manager.");
+            Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_NO);
+            return supported;
+        }
+        int[] vibratorIds = mVibratorManager.getVibratorIds();
+        if (vibratorIds.length == 0) {
+            Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_NO);
+            return supported;
+        }
+        int[][] vibratorSupportMap = new int[vibratorIds.length][effectIds.length];
+        for (int i = 0; i < vibratorIds.length; i++) {
+            vibratorSupportMap[i] = mVibratorManager.getVibrator(
+                    vibratorIds[i]).areEffectsSupported(effectIds);
+        }
+        Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_YES);
+        for (int effectIdx = 0; effectIdx < effectIds.length; effectIdx++) {
+            for (int vibratorIdx = 0; vibratorIdx < vibratorIds.length; vibratorIdx++) {
+                int effectSupported = vibratorSupportMap[vibratorIdx][effectIdx];
+                if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_NO) {
+                    supported[effectIdx] = Vibrator.VIBRATION_EFFECT_SUPPORT_NO;
+                    break;
+                } else if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN) {
+                    supported[effectIdx] = Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN;
+                }
+            }
         }
         return supported;
     }
@@ -276,42 +219,169 @@
     @Override
     public boolean[] arePrimitivesSupported(
             @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
-        VibratorInfo vibratorInfo = getVibratorInfo();
         boolean[] supported = new boolean[primitiveIds.length];
-        for (int i = 0; i < primitiveIds.length; i++) {
-            supported[i] = vibratorInfo == null
-                    ? false : vibratorInfo.isPrimitiveSupported(primitiveIds[i]);
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to check supported primitives; no vibrator manager.");
+            Arrays.fill(supported, false);
+            return supported;
+        }
+        int[] vibratorIds = mVibratorManager.getVibratorIds();
+        if (vibratorIds.length == 0) {
+            Arrays.fill(supported, false);
+            return supported;
+        }
+        boolean[][] vibratorSupportMap = new boolean[vibratorIds.length][primitiveIds.length];
+        for (int i = 0; i < vibratorIds.length; i++) {
+            vibratorSupportMap[i] = mVibratorManager.getVibrator(
+                    vibratorIds[i]).arePrimitivesSupported(primitiveIds);
+        }
+        Arrays.fill(supported, true);
+        for (int primitiveIdx = 0; primitiveIdx < primitiveIds.length; primitiveIdx++) {
+            for (int vibratorIdx = 0; vibratorIdx < vibratorIds.length; vibratorIdx++) {
+                if (!vibratorSupportMap[vibratorIdx][primitiveIdx]) {
+                    supported[primitiveIdx] = false;
+                    break;
+                }
+            }
         }
         return supported;
     }
 
     @Override
     public void cancel() {
-        if (mService == null) {
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to cancel vibrate; no vibrator manager.");
             return;
         }
-        try {
-            mService.cancelVibrate(mToken);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to cancel vibration.", e);
+        mVibratorManager.cancel();
+    }
+
+    /**
+     * Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener}
+     * that were left registered to vibrators after failures to register them to all vibrators.
+     *
+     * <p>This might happen if {@link AllVibratorsStateListener} fails to register to any vibrator
+     * and also fails to unregister any previously registered single listeners to other vibrators.
+     *
+     * <p>This method never throws {@link RuntimeException} if it fails to unregister again, it will
+     * fail silently and attempt to unregister the same broken listener later.
+     */
+    private void tryUnregisterBrokenListeners() {
+        synchronized (mBrokenListeners) {
+            try {
+                for (int i = mBrokenListeners.size(); --i >= 0; ) {
+                    mBrokenListeners.get(i).unregister(mVibratorManager);
+                    mBrokenListeners.remove(i);
+                }
+            } catch (RuntimeException e) {
+                Log.w(TAG, "Failed to unregister broken listener", e);
+            }
         }
     }
 
-    @Nullable
-    private VibratorInfo getVibratorInfo() {
-        try {
+    /** Listener for a single vibrator state change. */
+    private static class SingleVibratorStateListener implements OnVibratorStateChangedListener {
+        private final AllVibratorsStateListener mAllVibratorsListener;
+        private final int mVibratorIdx;
+
+        SingleVibratorStateListener(AllVibratorsStateListener listener, int vibratorIdx) {
+            mAllVibratorsListener = listener;
+            mVibratorIdx = vibratorIdx;
+        }
+
+        @Override
+        public void onVibratorStateChanged(boolean isVibrating) {
+            mAllVibratorsListener.onVibrating(mVibratorIdx, isVibrating);
+        }
+    }
+
+    /** Listener for all vibrators state change. */
+    private static class AllVibratorsStateListener {
+        private final Object mLock = new Object();
+        private final Executor mExecutor;
+        private final OnVibratorStateChangedListener mDelegate;
+
+        @GuardedBy("mLock")
+        private final SparseArray<SingleVibratorStateListener> mVibratorListeners =
+                new SparseArray<>();
+
+        @GuardedBy("mLock")
+        private int mInitializedMask;
+        @GuardedBy("mLock")
+        private int mVibratingMask;
+
+        AllVibratorsStateListener(@NonNull Executor executor,
+                @NonNull OnVibratorStateChangedListener listener) {
+            mExecutor = executor;
+            mDelegate = listener;
+        }
+
+        boolean hasRegisteredListeners() {
             synchronized (mLock) {
-                if (mVibratorInfo != null) {
-                    return mVibratorInfo;
-                }
-                if (mService == null) {
-                    return null;
-                }
-                return mVibratorInfo = mService.getVibratorInfo();
+                return mVibratorListeners.size() > 0;
             }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to query vibrator info");
-            throw e.rethrowFromSystemServer();
+        }
+
+        void register(VibratorManager vibratorManager) {
+            int[] vibratorIds = vibratorManager.getVibratorIds();
+            synchronized (mLock) {
+                for (int i = 0; i < vibratorIds.length; i++) {
+                    int vibratorId = vibratorIds[i];
+                    SingleVibratorStateListener listener = new SingleVibratorStateListener(this, i);
+                    try {
+                        vibratorManager.getVibrator(vibratorId).addVibratorStateListener(mExecutor,
+                                listener);
+                        mVibratorListeners.put(vibratorId, listener);
+                    } catch (RuntimeException e) {
+                        try {
+                            unregister(vibratorManager);
+                        } catch (RuntimeException e1) {
+                            Log.w(TAG,
+                                    "Failed to unregister listener while recovering from a failed "
+                                            + "register call", e1);
+                        }
+                        throw e;
+                    }
+                }
+            }
+        }
+
+        void unregister(VibratorManager vibratorManager) {
+            synchronized (mLock) {
+                for (int i = mVibratorListeners.size(); --i >= 0; ) {
+                    int vibratorId = mVibratorListeners.keyAt(i);
+                    SingleVibratorStateListener listener = mVibratorListeners.valueAt(i);
+                    vibratorManager.getVibrator(vibratorId).removeVibratorStateListener(listener);
+                    mVibratorListeners.removeAt(i);
+                }
+            }
+        }
+
+        void onVibrating(int vibratorIdx, boolean vibrating) {
+            mExecutor.execute(() -> {
+                boolean anyVibrating;
+                synchronized (mLock) {
+                    int allInitializedMask = 1 << mVibratorListeners.size() - 1;
+                    int vibratorMask = 1 << vibratorIdx;
+                    if ((mInitializedMask & vibratorMask) == 0) {
+                        // First state report for this vibrator, set vibrating initial value.
+                        mInitializedMask |= vibratorMask;
+                        mVibratingMask |= vibrating ? vibratorMask : 0;
+                    } else {
+                        // Flip vibrating value, if changed.
+                        boolean prevVibrating = (mVibratingMask & vibratorMask) != 0;
+                        if (prevVibrating != vibrating) {
+                            mVibratingMask ^= vibratorMask;
+                        }
+                    }
+                    if (mInitializedMask != allInitializedMask) {
+                        // Wait for all vibrators initial state to be reported before delegating.
+                        return;
+                    }
+                    anyVibrating = mVibratingMask != 0;
+                }
+                mDelegate.onVibratorStateChanged(anyVibrating);
+            });
         }
     }
 }
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
new file mode 100644
index 0000000..b528eb1
--- /dev/null
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * VibratorManager implementation that controls the system vibrators.
+ *
+ * @hide
+ */
+public class SystemVibratorManager extends VibratorManager {
+    private static final String TAG = "VibratorManager";
+
+    private final IVibratorManagerService mService;
+    private final Context mContext;
+    private final Binder mToken = new Binder();
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private int[] mVibratorIds;
+    @GuardedBy("mLock")
+    private final SparseArray<Vibrator> mVibrators = new SparseArray<>();
+
+    @GuardedBy("mLock")
+    private final ArrayMap<Vibrator.OnVibratorStateChangedListener,
+            OnVibratorStateChangedListenerDelegate> mListeners = new ArrayMap<>();
+
+    /**
+     * @hide to prevent subclassing from outside of the framework
+     */
+    public SystemVibratorManager(Context context) {
+        super(context);
+        mContext = context;
+        mService = IVibratorManagerService.Stub.asInterface(
+                ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE));
+    }
+
+    @NonNull
+    @Override
+    public int[] getVibratorIds() {
+        synchronized (mLock) {
+            if (mVibratorIds != null) {
+                return mVibratorIds;
+            }
+            try {
+                if (mService == null) {
+                    Log.w(TAG, "Failed to retrieve vibrator ids; no vibrator manager service.");
+                } else {
+                    return mVibratorIds = mService.getVibratorIds();
+                }
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+            return new int[0];
+        }
+    }
+
+    @NonNull
+    @Override
+    public Vibrator getVibrator(int vibratorId) {
+        synchronized (mLock) {
+            Vibrator vibrator = mVibrators.get(vibratorId);
+            if (vibrator != null) {
+                return vibrator;
+            }
+            VibratorInfo info = null;
+            try {
+                if (mService == null) {
+                    Log.w(TAG, "Failed to retrieve vibrator; no vibrator manager service.");
+                } else {
+                    info = mService.getVibratorInfo(vibratorId);
+                }
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+            if (info != null) {
+                vibrator = new SingleVibrator(info);
+                mVibrators.put(vibratorId, vibrator);
+            } else {
+                vibrator = NullVibrator.getInstance();
+            }
+            return vibrator;
+        }
+    }
+
+    @NonNull
+    @Override
+    public Vibrator getDefaultVibrator() {
+        return mContext.getSystemService(Vibrator.class);
+    }
+
+    @Override
+    public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+            @Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attributes) {
+        if (mService == null) {
+            Log.w(TAG, "Failed to set always-on effect; no vibrator manager service.");
+            return false;
+        }
+        try {
+            return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, effect, attributes);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to set always-on effect.", e);
+        }
+        return false;
+    }
+
+    @Override
+    public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
+            String reason, @Nullable VibrationAttributes attributes) {
+        if (mService == null) {
+            Log.w(TAG, "Failed to vibrate; no vibrator manager service.");
+            return;
+        }
+        try {
+            mService.vibrate(uid, opPkg, effect, attributes, reason, mToken);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to vibrate.", e);
+        }
+    }
+
+    @Override
+    public void cancel() {
+        if (mService == null) {
+            Log.w(TAG, "Failed to cancel vibration; no vibrator manager service.");
+            return;
+        }
+        try {
+            mService.cancelVibrate(mToken);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to cancel vibration.", e);
+        }
+    }
+
+    /** Listener for vibrations on a single vibrator. */
+    private static class OnVibratorStateChangedListenerDelegate extends
+            IVibratorStateListener.Stub {
+        private final Executor mExecutor;
+        private final Vibrator.OnVibratorStateChangedListener mListener;
+
+        OnVibratorStateChangedListenerDelegate(
+                @NonNull Vibrator.OnVibratorStateChangedListener listener,
+                @NonNull Executor executor) {
+            mExecutor = executor;
+            mListener = listener;
+        }
+
+        @Override
+        public void onVibrating(boolean isVibrating) {
+            mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating));
+        }
+    }
+
+    /** Controls vibrations on a single vibrator. */
+    private final class SingleVibrator extends Vibrator {
+        private final VibratorInfo mVibratorInfo;
+
+        SingleVibrator(@NonNull VibratorInfo vibratorInfo) {
+            mVibratorInfo = vibratorInfo;
+        }
+
+        @Override
+        public int getId() {
+            return mVibratorInfo.getId();
+        }
+
+        @Override
+        public boolean hasVibrator() {
+            return true;
+        }
+
+        @Override
+        public boolean hasAmplitudeControl() {
+            return mVibratorInfo.hasAmplitudeControl();
+        }
+
+        @NonNull
+        @Override
+        public int[] areEffectsSupported(@NonNull int... effectIds) {
+            int[] supported = new int[effectIds.length];
+            for (int i = 0; i < effectIds.length; i++) {
+                supported[i] = mVibratorInfo.isEffectSupported(effectIds[i]);
+            }
+            return supported;
+        }
+
+        @Override
+        public boolean[] arePrimitivesSupported(
+                @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+            boolean[] supported = new boolean[primitiveIds.length];
+            for (int i = 0; i < primitiveIds.length; i++) {
+                supported[i] = mVibratorInfo.isPrimitiveSupported(primitiveIds[i]);
+            }
+            return supported;
+        }
+
+        @Override
+        public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+                @Nullable VibrationEffect effect, @Nullable AudioAttributes attributes) {
+            if (mService == null) {
+                Log.w(TAG, "Failed to set always-on effect on vibrator " + mVibratorInfo.getId()
+                        + "; no vibrator manager service.");
+                return false;
+            }
+            try {
+                VibrationAttributes attr = new VibrationAttributes.Builder(
+                        attributes, effect).build();
+                CombinedVibrationEffect combined = CombinedVibrationEffect.startSynced()
+                        .addVibrator(mVibratorInfo.getId(), effect)
+                        .combine();
+                return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combined, attr);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to set always-on effect on vibrator " + mVibratorInfo.getId());
+            }
+            return false;
+        }
+
+        @Override
+        public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe, String reason,
+                @NonNull VibrationAttributes attributes) {
+            if (mService == null) {
+                Log.w(TAG, "Failed to vibrate on vibrator " + mVibratorInfo.getId()
+                        + "; no vibrator manager service.");
+                return;
+            }
+            try {
+                CombinedVibrationEffect combined = CombinedVibrationEffect.startSynced()
+                        .addVibrator(mVibratorInfo.getId(), vibe)
+                        .combine();
+                mService.vibrate(uid, opPkg, combined, attributes, reason, mToken);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to vibrate.", e);
+            }
+        }
+
+        @Override
+        public void cancel() {
+            if (mService == null) {
+                Log.w(TAG, "Failed to cancel vibration on vibrator " + mVibratorInfo.getId()
+                        + "; no vibrator manager service.");
+                return;
+            }
+            try {
+                mService.cancelVibrate(mToken);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to cancel vibration on vibrator " + mVibratorInfo.getId(), e);
+            }
+        }
+
+        @Override
+        public boolean isVibrating() {
+            if (mService == null) {
+                Log.w(TAG, "Failed to check status of vibrator " + mVibratorInfo.getId()
+                        + "; no vibrator service.");
+                return false;
+            }
+            try {
+                return mService.isVibrating(mVibratorInfo.getId());
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+            return false;
+        }
+
+        @Override
+        public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+            Objects.requireNonNull(listener);
+            if (mContext == null) {
+                Log.w(TAG, "Failed to add vibrate state listener; no vibrator context.");
+                return;
+            }
+            addVibratorStateListener(mContext.getMainExecutor(), listener);
+        }
+
+        @Override
+        public void addVibratorStateListener(
+                @NonNull @CallbackExecutor Executor executor,
+                @NonNull OnVibratorStateChangedListener listener) {
+            Objects.requireNonNull(listener);
+            Objects.requireNonNull(executor);
+            if (mService == null) {
+                Log.w(TAG,
+                        "Failed to add vibrate state listener to vibrator " + mVibratorInfo.getId()
+                                + "; no vibrator service.");
+                return;
+            }
+            synchronized (mLock) {
+                // If listener is already registered, reject and return.
+                if (mListeners.containsKey(listener)) {
+                    Log.w(TAG, "Listener already registered.");
+                    return;
+                }
+                try {
+                    OnVibratorStateChangedListenerDelegate delegate =
+                            new OnVibratorStateChangedListenerDelegate(listener, executor);
+                    if (!mService.registerVibratorStateListener(mVibratorInfo.getId(), delegate)) {
+                        Log.w(TAG, "Failed to add vibrate state listener to vibrator "
+                                + mVibratorInfo.getId());
+                        return;
+                    }
+                    mListeners.put(listener, delegate);
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+        @Override
+        public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+            Objects.requireNonNull(listener);
+            if (mService == null) {
+                Log.w(TAG, "Failed to remove vibrate state listener from vibrator "
+                        + mVibratorInfo.getId() + "; no vibrator service.");
+                return;
+            }
+            synchronized (mLock) {
+                // Check if the listener is registered, otherwise will return.
+                if (mListeners.containsKey(listener)) {
+                    OnVibratorStateChangedListenerDelegate delegate = mListeners.get(listener);
+                    try {
+                        if (!mService.unregisterVibratorStateListener(mVibratorInfo.getId(),
+                                delegate)) {
+                            Log.w(TAG, "Failed to remove vibrate state listener from vibrator "
+                                    + mVibratorInfo.getId());
+                            return;
+                        }
+                        mListeners.remove(listener);
+                    } catch (RemoteException e) {
+                        e.rethrowFromSystemServer();
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 7d85d13..d6fa733 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -184,6 +184,15 @@
     }
 
     /**
+     * Return the ID of this vibrator.
+     *
+     * @return The id of the vibrator controlled by this service.
+     */
+    public int getId() {
+        return -1;
+    }
+
+    /**
      * Check whether the hardware has a vibrator.
      *
      * @return True if the hardware has a vibrator, else false.
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
index 1d5a587..5dd38b6 100644
--- a/core/java/android/os/VibratorManager.java
+++ b/core/java/android/os/VibratorManager.java
@@ -17,45 +17,123 @@
 package android.os;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.util.Log;
 
 /**
- * VibratorManager provides access to multiple vibrators, as well as the ability to run them in
- * a synchronized fashion.
+ * Class that provides access to all vibrators from the device, as well as the ability to run them
+ * in a synchronized fashion.
+ * <p>
+ * If your process exits, any vibration you started will stop.
+ * </p>
  */
+@SystemService(Context.VIBRATOR_MANAGER_SERVICE)
 public abstract class VibratorManager {
-    /** @hide */
-    protected static final String TAG = "VibratorManager";
+    private static final String TAG = "VibratorManager";
+
+    private final String mPackageName;
 
     /**
-     * {@hide}
+     * @hide to prevent subclassing from outside of the framework
      */
     public VibratorManager() {
+        mPackageName = ActivityThread.currentPackageName();
     }
 
     /**
-     * This method lists all available actuator ids, returning a possible empty list.
-     * If the device has only a single actuator, this should return a single entry with a
-     * default id.
+     * @hide to prevent subclassing from outside of the framework
+     */
+    protected VibratorManager(Context context) {
+        mPackageName = context.getOpPackageName();
+    }
+
+    /**
+     * List all available vibrator ids, returning a possible empty list.
+     *
+     * @return An array containing the ids of the vibrators available on the device.
      */
     @NonNull
     public abstract int[] getVibratorIds();
 
     /**
-    * Returns a Vibrator service for given id.
-    * This allows users to perform a vibration effect on a single actuator.
-    */
+     * Retrieve a single vibrator by id.
+     *
+     * @param vibratorId The id of the vibrator to be retrieved.
+     * @return The vibrator with given {@code vibratorId}, never null.
+     */
     @NonNull
     public abstract Vibrator getVibrator(int vibratorId);
 
     /**
-    * Returns the system default Vibrator service.
-    */
+     * Returns the system default Vibrator service.
+     */
     @NonNull
     public abstract Vibrator getDefaultVibrator();
 
     /**
-     * Vibrates all actuators by passing each VibrationEffect within CombinedVibrationEffect
-     * to the respective actuator, in sync.
+     * Configure an always-on haptics effect.
+     *
+     * @hide
      */
-    public abstract void vibrate(@NonNull CombinedVibrationEffect effect);
+    @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
+    public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+            @Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attributes) {
+        Log.w(TAG, "Always-on effects aren't supported");
+        return false;
+    }
+
+    /**
+     * Vibrate with a given combination of effects.
+     *
+     * <p>
+     * Pass in a {@link CombinedVibrationEffect} representing a combination of {@link
+     * VibrationEffect} to be played on one or more vibrators.
+     * </p>
+     *
+     * @param effect an array of longs of times for which to turn the vibrator on or off.
+     */
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public final void vibrate(@NonNull CombinedVibrationEffect effect) {
+        vibrate(effect, null);
+    }
+
+    /**
+     * Vibrate with a given combination of effects.
+     *
+     * <p>
+     * Pass in a {@link CombinedVibrationEffect} representing a combination of {@link
+     * VibrationEffect} to be played on one or more vibrators.
+     * </p>
+     *
+     * @param effect     an array of longs of times for which to turn the vibrator on or off.
+     * @param attributes {@link VibrationAttributes} corresponding to the vibration. For example,
+     *                   specify {@link VibrationAttributes#USAGE_ALARM} for alarm vibrations or
+     *                   {@link VibrationAttributes#USAGE_RINGTONE} for vibrations associated with
+     *                   incoming calls.
+     */
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public final void vibrate(@NonNull CombinedVibrationEffect effect,
+            @Nullable VibrationAttributes attributes) {
+        vibrate(Process.myUid(), mPackageName, effect, null, attributes);
+    }
+
+    /**
+     * Like {@link #vibrate(CombinedVibrationEffect, VibrationAttributes)}, but allows the
+     * caller to specify the vibration is owned by someone else and set reason for vibration.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public abstract void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
+            String reason, @Nullable VibrationAttributes attributes);
+
+    /**
+     * Turn all the vibrators off.
+     */
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public abstract void cancel();
 }
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index 0ff68fc..0589994 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -241,6 +241,13 @@
     }
 
     /**
+     * Checks if device supports V2 calls (e.g. PerUid).
+     */
+    public static boolean isV2Available() {
+        return nativeIsV2Available();
+    }
+
+    /**
      * Checks if Incremental installations are allowed.
      * A developer can disable Incremental installations by setting the property.
      */
@@ -439,6 +446,7 @@
 
     /* Native methods */
     private static native boolean nativeIsEnabled();
+    private static native boolean nativeIsV2Available();
     private static native boolean nativeIsIncrementalPath(@NonNull String path);
     private static native byte[] nativeUnsafeGetFileSignature(@NonNull String path);
 }
diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS
index d09f351..b323468 100644
--- a/core/java/android/permission/OWNERS
+++ b/core/java/android/permission/OWNERS
@@ -1,6 +1,5 @@
 # Bug component: 137825
 
-moltmann@google.com
 evanseverson@google.com
 ntmyren@google.com
 zhanghai@google.com
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 85e9fdb..0e35ef9 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -693,13 +693,14 @@
         for (int usageNum = 0; usageNum < rawUsages.size(); usageNum++) {
             OpUsage usage = rawUsages.get(usageNum);
 
+            // If this attribution is a proxy, remove it
+            if (toRemoveProxies.contains(usage.toPackageAttr())) {
+                continue;
+            }
+
             // If this attribution has a special attribution, do not remove it
             if (specialAttributions.contains(usage.toPackageAttr())) {
                 deDuped.add(usage);
-            }
-
-            // If this attribution is a proxy, remove it
-            if (toRemoveProxies.contains(usage.toPackageAttr())) {
                 continue;
             }
 
diff --git a/core/java/android/permissionpresenterservice/OWNERS b/core/java/android/permissionpresenterservice/OWNERS
index d09f351..b323468 100644
--- a/core/java/android/permissionpresenterservice/OWNERS
+++ b/core/java/android/permissionpresenterservice/OWNERS
@@ -1,6 +1,5 @@
 # Bug component: 137825
 
-moltmann@google.com
 evanseverson@google.com
 ntmyren@google.com
 zhanghai@google.com
diff --git a/core/java/android/print/OWNERS b/core/java/android/print/OWNERS
index 72f0983..28a24203 100644
--- a/core/java/android/print/OWNERS
+++ b/core/java/android/print/OWNERS
@@ -1,5 +1,4 @@
 # Bug component: 47273
 
-moltmann@google.com
 svetoslavganov@android.com
 svetoslavganov@google.com
diff --git a/core/java/android/print/pdf/OWNERS b/core/java/android/print/pdf/OWNERS
index 72f0983..28a24203 100644
--- a/core/java/android/print/pdf/OWNERS
+++ b/core/java/android/print/pdf/OWNERS
@@ -1,5 +1,4 @@
 # Bug component: 47273
 
-moltmann@google.com
 svetoslavganov@android.com
 svetoslavganov@google.com
diff --git a/core/java/android/printservice/OWNERS b/core/java/android/printservice/OWNERS
index 72f0983..28a24203 100644
--- a/core/java/android/printservice/OWNERS
+++ b/core/java/android/printservice/OWNERS
@@ -1,5 +1,4 @@
 # Bug component: 47273
 
-moltmann@google.com
 svetoslavganov@android.com
 svetoslavganov@google.com
diff --git a/core/java/android/printservice/recommendation/OWNERS b/core/java/android/printservice/recommendation/OWNERS
index 72f0983..28a24203 100644
--- a/core/java/android/printservice/recommendation/OWNERS
+++ b/core/java/android/printservice/recommendation/OWNERS
@@ -1,5 +1,4 @@
 # Bug component: 47273
 
-moltmann@google.com
 svetoslavganov@android.com
 svetoslavganov@google.com
diff --git a/core/java/android/service/notification/NotificationListenerFilter.java b/core/java/android/service/notification/NotificationListenerFilter.java
index 6fdfaab..9de75ca 100644
--- a/core/java/android/service/notification/NotificationListenerFilter.java
+++ b/core/java/android/service/notification/NotificationListenerFilter.java
@@ -20,6 +20,7 @@
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT;
 
+import android.content.pm.VersionedPackage;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArraySet;
@@ -31,7 +32,8 @@
  */
 public class NotificationListenerFilter implements Parcelable {
     private int mAllowedNotificationTypes;
-    private ArraySet<String> mDisallowedPackages;
+    // VersionedPackage is holding the pkg name and pkg uid
+    private ArraySet<VersionedPackage> mDisallowedPackages;
 
     public NotificationListenerFilter() {
         mAllowedNotificationTypes = FLAG_FILTER_TYPE_CONVERSATIONS
@@ -41,7 +43,7 @@
         mDisallowedPackages = new ArraySet<>();
     }
 
-    public NotificationListenerFilter(int types, ArraySet<String> pkgs) {
+    public NotificationListenerFilter(int types, ArraySet<VersionedPackage> pkgs) {
         mAllowedNotificationTypes = types;
         mDisallowedPackages = pkgs;
     }
@@ -51,7 +53,8 @@
      */
     protected NotificationListenerFilter(Parcel in) {
         mAllowedNotificationTypes = in.readInt();
-        mDisallowedPackages = (ArraySet<String>) in.readArraySet(String.class.getClassLoader());
+        mDisallowedPackages = (ArraySet<VersionedPackage>) in.readArraySet(
+                VersionedPackage.class.getClassLoader());
     }
 
     @Override
@@ -77,7 +80,7 @@
         return (mAllowedNotificationTypes & type) != 0;
     }
 
-    public boolean isPackageAllowed(String pkg) {
+    public boolean isPackageAllowed(VersionedPackage pkg) {
         return !mDisallowedPackages.contains(pkg);
     }
 
@@ -85,7 +88,7 @@
         return mAllowedNotificationTypes;
     }
 
-    public ArraySet<String> getDisallowedPackages() {
+    public ArraySet<VersionedPackage> getDisallowedPackages() {
         return mDisallowedPackages;
     }
 
@@ -93,10 +96,18 @@
         mAllowedNotificationTypes = types;
     }
 
-    public void setDisallowedPackages(ArraySet<String> pkgs) {
+    public void setDisallowedPackages(ArraySet<VersionedPackage> pkgs) {
         mDisallowedPackages = pkgs;
     }
 
+    public void removePackage(VersionedPackage pkg) {
+        mDisallowedPackages.remove(pkg);
+    }
+
+    public void addPackage(VersionedPackage pkg) {
+        mDisallowedPackages.add(pkg);
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 64cddc3..f66f85b 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -80,6 +80,10 @@
  *     &lt;intent-filter>
  *         &lt;action android:name="android.service.notification.NotificationListenerService" />
  *     &lt;/intent-filter>
+ *     &lt;meta-data
+ *               android:name="android.service.notification.default_filter_types"
+ *               android:value="1,2">
+ *           &lt;/meta-data>
  * &lt;/service></pre>
  *
  * <p>The service should wait for the {@link #onListenerConnected()} event
@@ -103,6 +107,21 @@
     private final String TAG = getClass().getSimpleName();
 
     /**
+     * The name of the {@code meta-data} tag containing a comma separated list of default
+     * integer notification types that should be provided to this listener. See
+     * {@link #FLAG_FILTER_TYPE_ONGOING},
+     * {@link #FLAG_FILTER_TYPE_CONVERSATIONS}, {@link #FLAG_FILTER_TYPE_ALERTING),
+     * and {@link #FLAG_FILTER_TYPE_SILENT}.
+     * <p>This value will only be read if the app has not previously specified a default type list,
+     * and if the user has not overridden the allowed types.</p>
+     * <p>An absent value means 'allow all types'.
+     * A present but empty value means 'allow no types'.</p>
+     *
+     */
+    public static final String META_DATA_DEFAULT_FILTER_TYPES
+            = "android.service.notification.default_filter_types";
+
+    /**
      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
      *     Normal interruption filter.
      */
@@ -254,23 +273,19 @@
     /**
      * A flag value indicating that this notification listener can see conversation type
      * notifications.
-     * @hide
      */
     public static final int FLAG_FILTER_TYPE_CONVERSATIONS = 1;
     /**
      * A flag value indicating that this notification listener can see altering type notifications.
-     * @hide
      */
     public static final int FLAG_FILTER_TYPE_ALERTING = 2;
     /**
      * A flag value indicating that this notification listener can see silent type notifications.
-     * @hide
      */
     public static final int FLAG_FILTER_TYPE_SILENT = 4;
     /**
      * A flag value indicating that this notification listener can see important
      * ( > {@link NotificationManager#IMPORTANCE_MIN}) ongoing type notifications.
-     * @hide
      */
     public static final int FLAG_FILTER_TYPE_ONGOING = 8;
 
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 70ec2d4..6eba83f 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1083,7 +1083,12 @@
 
                 if (creating) {
                     updateOpaqueFlag();
-                    mDeferredDestroySurfaceControl = createSurfaceControls(viewRoot);
+                    final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
+                    if (mUseBlastAdapter) {
+                        createBlastSurfaceControls(viewRoot, name);
+                    } else {
+                        mDeferredDestroySurfaceControl = createSurfaceControls(viewRoot, name);
+                    }
                 } else if (mSurfaceControl == null) {
                     return;
                 }
@@ -1220,53 +1225,77 @@
      * out, the old surface can be persevered until the new one has drawn by keeping the reference
      * of the old SurfaceControl alive.
      */
-    private SurfaceControl createSurfaceControls(ViewRootImpl viewRoot) {
-        final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
-
-        SurfaceControl.Builder builder = new SurfaceControl.Builder(mSurfaceSession)
+    private SurfaceControl createSurfaceControls(ViewRootImpl viewRoot, String name) {
+        final SurfaceControl previousSurfaceControl = mSurfaceControl;
+        mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
                 .setName(name)
                 .setLocalOwnerView(this)
                 .setParent(viewRoot.getBoundsLayer())
-                .setCallsite("SurfaceView.updateSurface");
+                .setCallsite("SurfaceView.updateSurface")
+                .setBufferSize(mSurfaceWidth, mSurfaceHeight)
+                .setFlags(mSurfaceFlags)
+                .setFormat(mFormat)
+                .build();
+        mBackgroundControl = createBackgroundControl(name);
+        return previousSurfaceControl;
+    }
 
-        final SurfaceControl previousSurfaceControl;
-        if (mUseBlastAdapter) {
-            mSurfaceControl = builder
+    private SurfaceControl createBackgroundControl(String name) {
+        return new SurfaceControl.Builder(mSurfaceSession)
+        .setName("Background for " + name)
+        .setLocalOwnerView(this)
+        .setOpaque(true)
+        .setColorLayer()
+        .setParent(mSurfaceControl)
+        .setCallsite("SurfaceView.updateSurface")
+        .build();
+    }
+
+    // We don't recreate the surface controls but only recreate the adapter. Since the blast layer
+    // is still alive, the old buffers will continue to be presented until replaced by buffers from
+    // the new adapter. This means we do not need to track the old surface control and destroy it
+    // after the client has drawn to avoid any flickers.
+    private void createBlastSurfaceControls(ViewRootImpl viewRoot, String name) {
+        if (mSurfaceControl == null) {
+            mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
+                    .setName(name)
+                    .setLocalOwnerView(this)
+                    .setParent(viewRoot.getBoundsLayer())
+                    .setCallsite("SurfaceView.updateSurface")
                     .setContainerLayer()
                     .build();
-            previousSurfaceControl = mBlastSurfaceControl;
+        }
+
+        if (mBlastSurfaceControl == null) {
             mBlastSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
                     .setName(name + "(BLAST)")
                     .setLocalOwnerView(this)
-                    .setBufferSize(mSurfaceWidth, mSurfaceHeight)
                     .setParent(mSurfaceControl)
                     .setFlags(mSurfaceFlags)
                     .setHidden(false)
                     .setBLASTLayer()
                     .setCallsite("SurfaceView.updateSurface")
                     .build();
-            mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
-                    mSurfaceHeight, mFormat, true /* TODO */);
         } else {
-            previousSurfaceControl = mSurfaceControl;
-            mSurfaceControl = builder
-                    .setBufferSize(mSurfaceWidth, mSurfaceHeight)
-                    .setFlags(mSurfaceFlags)
-                    .setFormat(mFormat)
-                    .build();
-            mBlastSurfaceControl = null;
-            mBlastBufferQueue = null;
+            // update blast layer
+            mTmpTransaction
+                    .setOpaque(mBlastSurfaceControl, (mSurfaceFlags & SurfaceControl.OPAQUE) != 0)
+                    .setSecure(mBlastSurfaceControl, (mSurfaceFlags & SurfaceControl.SECURE) != 0)
+                    .show(mBlastSurfaceControl)
+                    .apply();
         }
-        mBackgroundControl = new SurfaceControl.Builder(mSurfaceSession)
-            .setName("Background for " + name)
-            .setLocalOwnerView(this)
-            .setOpaque(true)
-            .setColorLayer()
-            .setParent(mSurfaceControl)
-            .setCallsite("SurfaceView.updateSurface")
-            .build();
 
-        return previousSurfaceControl;
+        if (mBackgroundControl == null) {
+            mBackgroundControl = createBackgroundControl(name);
+        }
+
+        // Always recreate the IGBP for compatibility. This can be optimized in the future but
+        // the behavior change will need to be gated by SDK version.
+        if (mBlastBufferQueue != null) {
+            mBlastBufferQueue.destroy();
+        }
+        mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
+                mSurfaceHeight, mFormat, true /* TODO */);
     }
 
     private void onDrawFinished() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 755ae31..6b13a29 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1898,11 +1898,12 @@
     }
 
     private void setBoundsLayerCrop(Transaction t) {
-        // mWinFrame is already adjusted for surface insets. So offset it and use it as
-        // the cropping bounds.
-        mTempBoundsRect.set(mWinFrame);
-        mTempBoundsRect.offsetTo(mWindowAttributes.surfaceInsets.left,
-                mWindowAttributes.surfaceInsets.top);
+        // Adjust of insets and update the bounds layer so child surfaces do not draw into
+        // the surface inset region.
+        mTempBoundsRect.set(0, 0, mSurfaceSize.x, mSurfaceSize.y);
+        mTempBoundsRect.inset(mWindowAttributes.surfaceInsets.left,
+                mWindowAttributes.surfaceInsets.top,
+                mWindowAttributes.surfaceInsets.right, mWindowAttributes.surfaceInsets.bottom);
         t.setWindowCrop(mBoundsLayer, mTempBoundsRect);
     }
 
@@ -3063,8 +3064,10 @@
                     // via the WM relayout code path. We probably eventually
                     // want to synchronize transparent region hint changes
                     // with draws.
-                    mTransaction.setTransparentRegionHint(getSurfaceControl(),
-                        mTransparentRegion).apply();
+                    SurfaceControl sc = getSurfaceControl();
+                    if (sc.isValid()) {
+                        mTransaction.setTransparentRegionHint(sc, mTransparentRegion).apply();
+                    }
                 }
             }
 
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 61ff36c..681fd14 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -18,6 +18,7 @@
 
 import android.annotation.ColorInt;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -27,6 +28,7 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.os.Build;
+import android.util.AttributeSet;
 import android.view.animation.AnimationUtils;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
@@ -111,11 +113,14 @@
     private float mGlowAlpha;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private float mGlowScaleY;
+    private float mDistance;
 
     private float mGlowAlphaStart;
     private float mGlowAlphaFinish;
     private float mGlowScaleYStart;
     private float mGlowScaleYFinish;
+    private float mDistanceStart;
+    private float mDistanceFinish;
 
     private long mStartTime;
     private float mDuration;
@@ -150,9 +155,18 @@
      * @param context Context used to provide theming and resource information for the EdgeEffect
      */
     public EdgeEffect(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * Construct a new EdgeEffect with a theme appropriate for the provided context.
+     * @param context Context used to provide theming and resource information for the EdgeEffect
+     * @param attrs The attributes of the XML tag that is inflating the view
+     */
+    public EdgeEffect(@NonNull Context context, @Nullable AttributeSet attrs) {
         mPaint.setAntiAlias(true);
         final TypedArray a = context.obtainStyledAttributes(
-                com.android.internal.R.styleable.EdgeEffect);
+                attrs, com.android.internal.R.styleable.EdgeEffect);
         final int themeColor = a.getColor(
                 com.android.internal.R.styleable.EdgeEffect_colorEdgeEffect, 0xff666666);
         mEdgeEffectType = a.getInt(
@@ -248,6 +262,7 @@
         mDuration = PULL_TIME;
 
         mPullDistance += deltaDistance;
+        mDistanceStart = mDistanceFinish = mDistance = Math.max(0f, mPullDistance);
 
         final float absdd = Math.abs(deltaDistance);
         mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA,
@@ -267,6 +282,56 @@
     }
 
     /**
+     * A view should call this when content is pulled away from an edge by the user.
+     * This will update the state of the current visual effect and its associated animation.
+     * The host view should always {@link android.view.View#invalidate()} after this
+     * and draw the results accordingly. This works similarly to {@link #onPull(float, float)},
+     * but returns the amount of <code>deltaDistance</code> that has been consumed. If the
+     * {@link #getDistance()} is currently 0 and <code>deltaDistance</code> is negative, this
+     * function will return 0 and the drawn value will remain unchanged.
+     *
+     * This method can be used to reverse the effect from a pull or absorb and partially consume
+     * some of a motion:
+     *
+     * <pre class="prettyprint">
+     *     if (deltaY < 0) {
+     *         float consumed = edgeEffect.onPullDistance(deltaY / getHeight(), x / getWidth());
+     *         deltaY -= consumed * getHeight();
+     *         if (edgeEffect.getDistance() == 0f) edgeEffect.onRelease();
+     *     }
+     * </pre>
+     *
+     * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to
+     *                      1.f (full length of the view) or negative values to express change
+     *                      back toward the edge reached to initiate the effect.
+     * @param displacement The displacement from the starting side of the effect of the point
+     *                     initiating the pull. In the case of touch this is the finger position.
+     *                     Values may be from 0-1.
+     * @return The amount of <code>deltaDistance</code> that was consumed, a number between
+     * 0 and <code>deltaDistance</code>.
+     */
+    public float onPullDistance(float deltaDistance, float displacement) {
+        float finalDistance = Math.max(0f, deltaDistance + mDistance);
+        float delta = finalDistance - mDistance;
+        onPull(delta, displacement);
+        return delta;
+    }
+
+    /**
+     * Returns the pull distance needed to be released to remove the showing effect.
+     * It is determined by the {@link #onPull(float, float)} <code>deltaDistance</code> and
+     * any animating values, including from {@link #onAbsorb(int)} and {@link #onRelease()}.
+     *
+     * This can be used in conjunction with {@link #onPullDistance(float, float)} to
+     * release the currently showing effect.
+     *
+     * @return The pull distance that must be released to remove the showing effect.
+     */
+    public float getDistance() {
+        return mDistance;
+    }
+
+    /**
      * Call when the object is released after being pulled.
      * This will begin the "decay" phase of the effect. After calling this method
      * the host view should {@link android.view.View#invalidate()} and thereby
@@ -282,9 +347,11 @@
         mState = STATE_RECEDE;
         mGlowAlphaStart = mGlowAlpha;
         mGlowScaleYStart = mGlowScaleY;
+        mDistanceStart = mDistance;
 
         mGlowAlphaFinish = 0.f;
         mGlowScaleYFinish = 0.f;
+        mDistanceFinish = 0.f;
 
         mStartTime = AnimationUtils.currentAnimationTimeMillis();
         mDuration = RECEDE_TIME;
@@ -311,7 +378,7 @@
         // nearly invisible.
         mGlowAlphaStart = GLOW_ALPHA_START;
         mGlowScaleYStart = Math.max(mGlowScaleY, 0.f);
-
+        mDistanceStart = mDistance;
 
         // Growth for the size of the glow should be quadratic to properly
         // respond
@@ -322,6 +389,9 @@
         mGlowAlphaFinish = Math.max(
                 mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
         mTargetDisplacement = 0.5f;
+
+        // Use glow values to estimate the absorption for stretch distance.
+        mDistanceFinish = calculateDistanceFromGlowValues(mGlowScaleYFinish, mGlowAlphaFinish);
     }
 
     /**
@@ -447,6 +517,7 @@
 
         mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
         mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
+        mDistance = mDistanceStart + (mDistanceFinish - mDistanceStart) * interp;
         mDisplacement = (mDisplacement + mTargetDisplacement) / 2;
 
         if (t >= 1.f - EPSILON) {
@@ -458,10 +529,12 @@
 
                     mGlowAlphaStart = mGlowAlpha;
                     mGlowScaleYStart = mGlowScaleY;
+                    mDistanceStart = mDistance;
 
                     // After absorb, the glow should fade to nothing.
                     mGlowAlphaFinish = 0.f;
                     mGlowScaleYFinish = 0.f;
+                    mDistanceFinish = 0.f;
                     break;
                 case STATE_PULL:
                     mState = STATE_PULL_DECAY;
@@ -470,10 +543,12 @@
 
                     mGlowAlphaStart = mGlowAlpha;
                     mGlowScaleYStart = mGlowScaleY;
+                    mDistanceStart = mDistance;
 
                     // After pull, the glow should fade to nothing.
                     mGlowAlphaFinish = 0.f;
                     mGlowScaleYFinish = 0.f;
+                    mDistanceFinish = 0.f;
                     break;
                 case STATE_PULL_DECAY:
                     mState = STATE_RECEDE;
@@ -484,4 +559,20 @@
             }
         }
     }
+
+    /**
+     * @return The estimated pull distance as calculated from mGlowScaleY.
+     */
+    private float calculateDistanceFromGlowValues(float scale, float alpha) {
+        if (scale >= 1f) {
+            // It should asymptotically approach 1, but not reach there.
+            // Here, we're just choosing a value that is large.
+            return 1f;
+        }
+        if (scale > 0f) {
+            float v = 1f / 0.7f / (mGlowScaleY - 1f);
+            return v * v / mBounds.height();
+        }
+        return alpha / PULL_DISTANCE_ALPHA_GLOW_FACTOR;
+    }
 }
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 794b642..d59a415 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -69,9 +69,7 @@
     private final TextView mTextView;
 
     SpellCheckerSession mSpellCheckerSession;
-    // We assume that the sentence level spell check will always provide better results than words.
-    // Although word SC has a sequential option.
-    private boolean mIsSentenceSpellCheckSupported;
+
     final int mCookie;
 
     // Paired arrays for the (id, spellCheckSpan) pair. A negative id means the associated
@@ -134,7 +132,6 @@
                             | SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO
                             | SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR
                             | SuggestionsInfo.RESULT_ATTR_DONT_SHOW_UI_FOR_SUGGESTIONS);
-            mIsSentenceSpellCheckSupported = true;
         }
 
         // Restore SpellCheckSpans in pool
@@ -318,13 +315,11 @@
                     && WordIterator.isMidWordPunctuation(
                             mCurrentLocale, Character.codePointBefore(editable, end + 1))) {
                 isEditing = false;
-            } else if (mIsSentenceSpellCheckSupported) {
+            } else {
                 // Allow the overlap of the cursor and the first boundary of the spell check span
                 // no to skip the spell check of the following word because the
                 // following word will never be spell-checked even if the user finishes composing
                 isEditing = selectionEnd <= start || selectionStart > end;
-            } else {
-                isEditing = selectionEnd < start || selectionStart > end;
             }
             if (start >= 0 && end > start && (forceCheckWhenEditingWord || isEditing)) {
                 spellCheckSpan.setSpellCheckInProgress(true);
@@ -346,13 +341,8 @@
                 textInfos = textInfosCopy;
             }
 
-            if (mIsSentenceSpellCheckSupported) {
-                mSpellCheckerSession.getSentenceSuggestions(
-                        textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE);
-            } else {
-                mSpellCheckerSession.getSuggestions(textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE,
-                        false /* TODO Set sequentialWords to true for initial spell check */);
-            }
+            mSpellCheckerSession.getSentenceSuggestions(
+                    textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE);
         }
     }
 
@@ -381,32 +371,30 @@
                             editable, suggestionsInfo, spellCheckSpan, offset, length);
                 } else {
                     // Valid word -- isInDictionary || !looksLikeTypo
-                    if (mIsSentenceSpellCheckSupported) {
-                        // Allow the spell checker to remove existing misspelled span by
-                        // overwriting the span over the same place
-                        final int spellCheckSpanStart = editable.getSpanStart(spellCheckSpan);
-                        final int spellCheckSpanEnd = editable.getSpanEnd(spellCheckSpan);
-                        final int start;
-                        final int end;
-                        if (offset != USE_SPAN_RANGE && length != USE_SPAN_RANGE) {
-                            start = spellCheckSpanStart + offset;
-                            end = start + length;
-                        } else {
-                            start = spellCheckSpanStart;
-                            end = spellCheckSpanEnd;
-                        }
-                        if (spellCheckSpanStart >= 0 && spellCheckSpanEnd > spellCheckSpanStart
-                                && end > start) {
-                            final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
-                            final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
-                            if (tempSuggestionSpan != null) {
-                                if (DBG) {
-                                    Log.i(TAG, "Remove existing misspelled span. "
-                                            + editable.subSequence(start, end));
-                                }
-                                editable.removeSpan(tempSuggestionSpan);
-                                mSuggestionSpanCache.remove(key);
+                    // Allow the spell checker to remove existing misspelled span by
+                    // overwriting the span over the same place
+                    final int spellCheckSpanStart = editable.getSpanStart(spellCheckSpan);
+                    final int spellCheckSpanEnd = editable.getSpanEnd(spellCheckSpan);
+                    final int start;
+                    final int end;
+                    if (offset != USE_SPAN_RANGE && length != USE_SPAN_RANGE) {
+                        start = spellCheckSpanStart + offset;
+                        end = start + length;
+                    } else {
+                        start = spellCheckSpanStart;
+                        end = spellCheckSpanEnd;
+                    }
+                    if (spellCheckSpanStart >= 0 && spellCheckSpanEnd > spellCheckSpanStart
+                            && end > start) {
+                        final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
+                        final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
+                        if (tempSuggestionSpan != null) {
+                            if (DBG) {
+                                Log.i(TAG, "Remove existing misspelled span. "
+                                        + editable.subSequence(start, end));
                             }
+                            editable.removeSpan(tempSuggestionSpan);
+                            mSuggestionSpanCache.remove(key);
                         }
                     }
                 }
@@ -531,20 +519,16 @@
         }
         SuggestionSpan suggestionSpan =
                 new SuggestionSpan(mTextView.getContext(), suggestions, flags);
-        // TODO: Remove mIsSentenceSpellCheckSupported by extracting an interface
-        // to share the logic of word level spell checker and sentence level spell checker
-        if (mIsSentenceSpellCheckSupported) {
-            final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
-            final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
-            if (tempSuggestionSpan != null) {
-                if (DBG) {
-                    Log.i(TAG, "Cached span on the same position is cleard. "
-                            + editable.subSequence(start, end));
-                }
-                editable.removeSpan(tempSuggestionSpan);
+        final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
+        final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
+        if (tempSuggestionSpan != null) {
+            if (DBG) {
+                Log.i(TAG, "Cached span on the same position is cleard. "
+                        + editable.subSequence(start, end));
             }
-            mSuggestionSpanCache.put(key, suggestionSpan);
+            editable.removeSpan(tempSuggestionSpan);
         }
+        mSuggestionSpanCache.put(key, suggestionSpan);
         editable.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
 
         mTextView.invalidateRegion(start, end, false /* No cursor involved */);
@@ -599,15 +583,8 @@
         public void parse() {
             Editable editable = (Editable) mTextView.getText();
             // Iterate over the newly added text and schedule new SpellCheckSpans
-            final int start;
-            if (mIsSentenceSpellCheckSupported) {
-                // TODO: Find the start position of the sentence.
-                // Set span with the context
-                start =  Math.max(
-                        0, editable.getSpanStart(mRange) - MIN_SENTENCE_LENGTH);
-            } else {
-                start = editable.getSpanStart(mRange);
-            }
+            final int start =  Math.max(
+                    0, editable.getSpanStart(mRange) - MIN_SENTENCE_LENGTH);
 
             final int end = editable.getSpanEnd(mRange);
 
@@ -633,155 +610,80 @@
                 return;
             }
 
-            // We need to expand by one character because we want to include the spans that
-            // end/start at position start/end respectively.
-            SpellCheckSpan[] spellCheckSpans = editable.getSpans(start - 1, end + 1,
-                    SpellCheckSpan.class);
-            SuggestionSpan[] suggestionSpans = editable.getSpans(start - 1, end + 1,
-                    SuggestionSpan.class);
-
-            int wordCount = 0;
             boolean scheduleOtherSpellCheck = false;
 
-            if (mIsSentenceSpellCheckSupported) {
-                if (wordIteratorWindowEnd < end) {
-                    if (DBG) {
-                        Log.i(TAG, "schedule other spell check.");
-                    }
-                    // Several batches needed on that region. Cut after last previous word
-                    scheduleOtherSpellCheck = true;
+            if (wordIteratorWindowEnd < end) {
+                if (DBG) {
+                    Log.i(TAG, "schedule other spell check.");
                 }
-                int spellCheckEnd = mWordIterator.preceding(wordIteratorWindowEnd);
-                boolean correct = spellCheckEnd != BreakIterator.DONE;
-                if (correct) {
-                    spellCheckEnd = mWordIterator.getEnd(spellCheckEnd);
-                    correct = spellCheckEnd != BreakIterator.DONE;
-                }
-                if (!correct) {
-                    if (DBG) {
-                        Log.i(TAG, "Incorrect range span.");
-                    }
-                    stop();
-                    return;
-                }
-                do {
-                    // TODO: Find the start position of the sentence.
-                    int spellCheckStart = wordStart;
-                    boolean createSpellCheckSpan = true;
-                    // Cancel or merge overlapped spell check spans
-                    for (int i = 0; i < mLength; ++i) {
-                        final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
-                        if (mIds[i] < 0 || spellCheckSpan.isSpellCheckInProgress()) {
-                            continue;
-                        }
-                        final int spanStart = editable.getSpanStart(spellCheckSpan);
-                        final int spanEnd = editable.getSpanEnd(spellCheckSpan);
-                        if (spanEnd < spellCheckStart || spellCheckEnd < spanStart) {
-                            // No need to merge
-                            continue;
-                        }
-                        if (spanStart <= spellCheckStart && spellCheckEnd <= spanEnd) {
-                            // There is a completely overlapped spell check span
-                            // skip this span
-                            createSpellCheckSpan = false;
-                            if (DBG) {
-                                Log.i(TAG, "The range is overrapped. Skip spell check.");
-                            }
-                            break;
-                        }
-                        // This spellCheckSpan is replaced by the one we are creating
-                        editable.removeSpan(spellCheckSpan);
-                        spellCheckStart = Math.min(spanStart, spellCheckStart);
-                        spellCheckEnd = Math.max(spanEnd, spellCheckEnd);
-                    }
-
-                    if (DBG) {
-                        Log.d(TAG, "addSpellCheckSpan: "
-                                + ", End = " + spellCheckEnd + ", Start = " + spellCheckStart
-                                + ", next = " + scheduleOtherSpellCheck + "\n"
-                                + editable.subSequence(spellCheckStart, spellCheckEnd));
-                    }
-
-                    // Stop spell checking when there are no characters in the range.
-                    if (spellCheckEnd < start) {
-                        break;
-                    }
-                    if (spellCheckEnd <= spellCheckStart) {
-                        Log.w(TAG, "Trying to spellcheck invalid region, from "
-                                + start + " to " + end);
-                        break;
-                    }
-                    if (createSpellCheckSpan) {
-                        addSpellCheckSpan(editable, spellCheckStart, spellCheckEnd);
-                    }
-                } while (false);
-                wordStart = spellCheckEnd;
-            } else {
-                while (wordStart <= end) {
-                    if (wordEnd >= start && wordEnd > wordStart) {
-                        if (wordCount >= MAX_NUMBER_OF_WORDS) {
-                            scheduleOtherSpellCheck = true;
-                            break;
-                        }
-                        // A new word has been created across the interval boundaries with this
-                        // edit. The previous spans (that ended on start / started on end) are
-                        // not valid anymore and must be removed.
-                        if (wordStart < start && wordEnd > start) {
-                            removeSpansAt(editable, start, spellCheckSpans);
-                            removeSpansAt(editable, start, suggestionSpans);
-                        }
-
-                        if (wordStart < end && wordEnd > end) {
-                            removeSpansAt(editable, end, spellCheckSpans);
-                            removeSpansAt(editable, end, suggestionSpans);
-                        }
-
-                        // Do not create new boundary spans if they already exist
-                        boolean createSpellCheckSpan = true;
-                        if (wordEnd == start) {
-                            for (int i = 0; i < spellCheckSpans.length; i++) {
-                                final int spanEnd = editable.getSpanEnd(spellCheckSpans[i]);
-                                if (spanEnd == start) {
-                                    createSpellCheckSpan = false;
-                                    break;
-                                }
-                            }
-                        }
-
-                        if (wordStart == end) {
-                            for (int i = 0; i < spellCheckSpans.length; i++) {
-                                final int spanStart = editable.getSpanStart(spellCheckSpans[i]);
-                                if (spanStart == end) {
-                                    createSpellCheckSpan = false;
-                                    break;
-                                }
-                            }
-                        }
-
-                        if (createSpellCheckSpan) {
-                            addSpellCheckSpan(editable, wordStart, wordEnd);
-                        }
-                        wordCount++;
-                    }
-
-                    // iterate word by word
-                    int originalWordEnd = wordEnd;
-                    wordEnd = mWordIterator.following(wordEnd);
-                    if ((wordIteratorWindowEnd < end) &&
-                            (wordEnd == BreakIterator.DONE || wordEnd >= wordIteratorWindowEnd)) {
-                        wordIteratorWindowEnd =
-                                Math.min(end, originalWordEnd + WORD_ITERATOR_INTERVAL);
-                        mWordIterator.setCharSequence(
-                                editable, originalWordEnd, wordIteratorWindowEnd);
-                        wordEnd = mWordIterator.following(originalWordEnd);
-                    }
-                    if (wordEnd == BreakIterator.DONE) break;
-                    wordStart = mWordIterator.getBeginning(wordEnd);
-                    if (wordStart == BreakIterator.DONE) {
-                        break;
-                    }
-                }
+                // Several batches needed on that region. Cut after last previous word
+                scheduleOtherSpellCheck = true;
             }
+            int spellCheckEnd = mWordIterator.preceding(wordIteratorWindowEnd);
+            boolean correct = spellCheckEnd != BreakIterator.DONE;
+            if (correct) {
+                spellCheckEnd = mWordIterator.getEnd(spellCheckEnd);
+                correct = spellCheckEnd != BreakIterator.DONE;
+            }
+            if (!correct) {
+                if (DBG) {
+                    Log.i(TAG, "Incorrect range span.");
+                }
+                stop();
+                return;
+            }
+            do {
+                // TODO: Find the start position of the sentence.
+                int spellCheckStart = wordStart;
+                boolean createSpellCheckSpan = true;
+                // Cancel or merge overlapped spell check spans
+                for (int i = 0; i < mLength; ++i) {
+                    final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
+                    if (mIds[i] < 0 || spellCheckSpan.isSpellCheckInProgress()) {
+                        continue;
+                    }
+                    final int spanStart = editable.getSpanStart(spellCheckSpan);
+                    final int spanEnd = editable.getSpanEnd(spellCheckSpan);
+                    if (spanEnd < spellCheckStart || spellCheckEnd < spanStart) {
+                        // No need to merge
+                        continue;
+                    }
+                    if (spanStart <= spellCheckStart && spellCheckEnd <= spanEnd) {
+                        // There is a completely overlapped spell check span
+                        // skip this span
+                        createSpellCheckSpan = false;
+                        if (DBG) {
+                            Log.i(TAG, "The range is overrapped. Skip spell check.");
+                        }
+                        break;
+                    }
+                    // This spellCheckSpan is replaced by the one we are creating
+                    editable.removeSpan(spellCheckSpan);
+                    spellCheckStart = Math.min(spanStart, spellCheckStart);
+                    spellCheckEnd = Math.max(spanEnd, spellCheckEnd);
+                }
+
+                if (DBG) {
+                    Log.d(TAG, "addSpellCheckSpan: "
+                            + ", End = " + spellCheckEnd + ", Start = " + spellCheckStart
+                            + ", next = " + scheduleOtherSpellCheck + "\n"
+                            + editable.subSequence(spellCheckStart, spellCheckEnd));
+                }
+
+                // Stop spell checking when there are no characters in the range.
+                if (spellCheckEnd < start) {
+                    break;
+                }
+                if (spellCheckEnd <= spellCheckStart) {
+                    Log.w(TAG, "Trying to spellcheck invalid region, from "
+                            + start + " to " + end);
+                    break;
+                }
+                if (createSpellCheckSpan) {
+                    addSpellCheckSpan(editable, spellCheckStart, spellCheckEnd);
+                }
+            } while (false);
+            wordStart = spellCheckEnd;
 
             if (scheduleOtherSpellCheck && wordStart != BreakIterator.DONE && wordStart <= end) {
                 // Update range span: start new spell check from last wordStart
diff --git a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
index c2ee646..0152387 100644
--- a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
+++ b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
@@ -53,10 +53,8 @@
     public static final String KEY_CUR_TASK = "cur_task";
     /** Package of newly requested heavy-weight app. */
     public static final String KEY_NEW_APP = "new_app";
-    public static final String KEY_ACTIVITY_OPTIONS = "activity_options";
     
     IntentSender mStartIntent;
-    Bundle mActivityOptions;
     boolean mHasResult;
     String mCurApp;
     int mCurTask;
@@ -67,9 +65,8 @@
         super.onCreate(savedInstanceState);
         
         requestWindowFeature(Window.FEATURE_NO_TITLE);
-
+        
         mStartIntent = (IntentSender)getIntent().getParcelableExtra(KEY_INTENT);
-        mActivityOptions = getIntent().getBundleExtra(KEY_ACTIVITY_OPTIONS);
         mHasResult = getIntent().getBooleanExtra(KEY_HAS_RESULT, false);
         mCurApp = getIntent().getStringExtra(KEY_CUR_APP);
         mCurTask = getIntent().getIntExtra(KEY_CUR_TASK, 0);
@@ -151,9 +148,9 @@
                 if (mHasResult) {
                     startIntentSenderForResult(mStartIntent, -1, null,
                             Intent.FLAG_ACTIVITY_FORWARD_RESULT,
-                            Intent.FLAG_ACTIVITY_FORWARD_RESULT, 0, mActivityOptions);
+                            Intent.FLAG_ACTIVITY_FORWARD_RESULT, 0);
                 } else {
-                    startIntentSenderForResult(mStartIntent, -1, null, 0, 0, 0, mActivityOptions);
+                    startIntentSenderForResult(mStartIntent, -1, null, 0, 0, 0);
                 }
             } catch (IntentSender.SendIntentException ex) {
                 Log.w("HeavyWeightSwitcherActivity", "Failure starting", ex);
diff --git a/core/java/com/android/internal/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
similarity index 99%
rename from core/java/com/android/internal/BrightnessSynchronizer.java
rename to core/java/com/android/internal/display/BrightnessSynchronizer.java
index 9049ca5..fae5862 100644
--- a/core/java/com/android/internal/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal;
+package com.android.internal.display;
 
 
 import android.content.ContentResolver;
diff --git a/core/java/com/android/internal/display/OWNERS b/core/java/com/android/internal/display/OWNERS
new file mode 100644
index 0000000..20b75be
--- /dev/null
+++ b/core/java/com/android/internal/display/OWNERS
@@ -0,0 +1,3 @@
+include /services/core/java/com/android/server/display/OWNERS
+
+flc@google.com
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 6e9bc84..cba6af9 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -499,7 +499,7 @@
         }
 
         public String getPerfettoTrigger() {
-            return String.format("interaction-jank-monitor-%d", mCujType);
+            return String.format("com.android.telemetry.interaction-jank-monitor-%d", mCujType);
         }
 
         public String getName() {
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
index af61f91..9b88516 100644
--- a/core/java/com/android/internal/os/BatterySipper.java
+++ b/core/java/com/android/internal/os/BatterySipper.java
@@ -133,6 +133,7 @@
     public double wakeLockPowerMah;
     public double wifiPowerMah;
     public double systemServiceCpuPowerMah;
+    public double[] customMeasuredPowerMah;
 
     // Do not include this sipper in results because it is included
     // in an aggregate sipper.
@@ -251,6 +252,17 @@
         proportionalSmearMah += other.proportionalSmearMah;
         totalSmearedPowerMah += other.totalSmearedPowerMah;
         systemServiceCpuPowerMah += other.systemServiceCpuPowerMah;
+        if (other.customMeasuredPowerMah != null) {
+            if (customMeasuredPowerMah == null) {
+                customMeasuredPowerMah = new double[other.customMeasuredPowerMah.length];
+            }
+            if (customMeasuredPowerMah.length == other.customMeasuredPowerMah.length) {
+                // This should always be true.
+                for (int idx = 0; idx < other.customMeasuredPowerMah.length; idx++) {
+                    customMeasuredPowerMah[idx] += other.customMeasuredPowerMah[idx];
+                }
+            }
+        }
     }
 
     /**
@@ -264,6 +276,11 @@
                 sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah +
                 flashlightPowerMah + bluetoothPowerMah + audioPowerMah + videoPowerMah
                 + systemServiceCpuPowerMah;
+        if (customMeasuredPowerMah != null) {
+            for (int idx = 0; idx < customMeasuredPowerMah.length; idx++) {
+                totalPowerMah += customMeasuredPowerMah[idx];
+            }
+        }
         totalSmearedPowerMah = totalPowerMah + screenPowerMah + proportionalSmearMah;
 
         return totalPowerMah;
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index aa5015a..b20f50d 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -347,6 +347,7 @@
             mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
             mPowerCalculators.add(new SystemServicePowerCalculator(mPowerProfile));
             mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile));
+            mPowerCalculators.add(new CustomMeasuredPowerCalculator(mPowerProfile));
 
             mPowerCalculators.add(new UserPowerCalculator());
         }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 6e41b3f..a39d19e 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -7157,20 +7157,34 @@
 
     @Override
     public long getScreenOnEnergy() {
-        if (mGlobalMeasuredEnergyStats == null) {
-            return ENERGY_DATA_UNAVAILABLE;
-        }
-        return mGlobalMeasuredEnergyStats
-                .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
+        return getMeasuredEnergyMicroJoules(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
     }
 
     @Override
     public long getScreenDozeEnergy() {
+        return getMeasuredEnergyMicroJoules(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE);
+    }
+
+    /**
+     * Returns the energy in microjoules that the given standard energy bucket consumed.
+     * Will return {@link #ENERGY_DATA_UNAVAILABLE} if data is unavailable
+     *
+     * @param bucket standard energy bucket of interest
+     * @return energy (in microjoules) used for this energy bucket
+     */
+    private long getMeasuredEnergyMicroJoules(@StandardEnergyBucket int bucket) {
         if (mGlobalMeasuredEnergyStats == null) {
             return ENERGY_DATA_UNAVAILABLE;
         }
-        return mGlobalMeasuredEnergyStats
-                .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE);
+        return mGlobalMeasuredEnergyStats.getAccumulatedStandardBucketEnergy(bucket);
+    }
+
+    @Override
+    public @Nullable long[] getCustomMeasuredEnergiesMicroJoules() {
+        if (mGlobalMeasuredEnergyStats == null) {
+            return null;
+        }
+        return mGlobalMeasuredEnergyStats.getAccumulatedCustomBucketEnergies();
     }
 
     @Override public long getStartClockTime() {
@@ -7941,6 +7955,13 @@
                     .updateStandardBucket(energyBucket, energyDeltaUJ, accumulate);
         }
 
+        /** Adds the given energy to the given custom energy bucket for this uid. */
+        private void addEnergyToCustomBucketLocked(long energyDeltaUJ, int energyBucket,
+                boolean accumulate) {
+            getOrCreateMeasuredEnergyStatsLocked()
+                    .updateCustomBucket(energyBucket, energyDeltaUJ, accumulate);
+        }
+
         /**
          * Returns the energy used by this uid for a standard energy bucket of interest.
          * @param bucket standard energy bucket of interest
@@ -7957,6 +7978,18 @@
             return mUidMeasuredEnergyStats.getAccumulatedStandardBucketEnergy(bucket);
         }
 
+        @Override
+        public long[] getCustomMeasuredEnergiesMicroJoules() {
+            if (mBsi.mGlobalMeasuredEnergyStats == null) {
+                return null;
+            }
+            if (mUidMeasuredEnergyStats == null) {
+                // Custom buckets may exist. But all values for this uid are 0 so we report all 0s.
+                return new long[mBsi.mGlobalMeasuredEnergyStats.getNumberCustomEnergyBuckets()];
+            }
+            return mUidMeasuredEnergyStats.getAccumulatedCustomBucketEnergies();
+        }
+
         /**
          * Gets the minimum of the uid's foreground activity time and its PROCESS_STATE_TOP time
          * since last marked. Also sets the mark time for both these timers.
@@ -12465,6 +12498,42 @@
     }
 
     /**
+     * Accumulate Custom energy bucket energy, globally and for each app.
+     *
+     * @param totalEnergyUJ energy (microjoules) used for this bucket since this was last called.
+     * @param uidEnergies map of uid->energy (microjoules) for this bucket since last called.
+     *                    Data inside uidEnergies will not be modified (treated immutable).
+     */
+    public void updateCustomMeasuredEnergyDataLocked(int customEnergyBucket,
+            long totalEnergyUJ, @Nullable SparseLongArray uidEnergies) {
+        if (DEBUG_ENERGY) {
+            Slog.d(TAG, "Updating attributed measured energy stats for custom bucket "
+                    + customEnergyBucket
+                    + " with total energy " + totalEnergyUJ
+                    + " and uid energies " + String.valueOf(uidEnergies));
+        }
+        if (mGlobalMeasuredEnergyStats == null) return;
+        if (!mOnBatteryInternal || mIgnoreNextExternalStats || totalEnergyUJ <= 0) return;
+
+        mGlobalMeasuredEnergyStats.updateCustomBucket(customEnergyBucket, totalEnergyUJ, true);
+
+        if (uidEnergies == null) return;
+        final int numUids = uidEnergies.size();
+        for (int i = 0; i < numUids; i++) {
+            final int uidInt = mapUid(uidEnergies.keyAt(i));
+            final long uidEnergyUJ = uidEnergies.valueAt(i);
+            if (uidEnergyUJ == 0) continue;
+            // TODO: Worry about uids not in BSI currently, including uninstalled uids 'coming back'
+            //  Specifically: What if the uid had been removed? We'll re-create it now.
+            //  And if we instead use getAvailableUidStatsLocked() and chec for null, then we might
+            //  not create a Uid even when we should be (say, the app's first event, somehow, was to
+            //  use GPU). I guess that CPU/kernel data might already have this problem?
+            final Uid uidObj = getUidStatsLocked(uidInt);
+            uidObj.addEnergyToCustomBucketLocked(uidEnergyUJ, customEnergyBucket, true);
+        }
+    }
+
+    /**
      * Read and record Rail Energy data.
      */
     public void updateRailStatsLocked() {
@@ -12933,16 +13002,17 @@
         mWakeLockAllocationsUs = null;
         final long startTimeMs = mClocks.uptimeMillis();
         final long elapsedRealtimeMs = mClocks.elapsedRealtime();
+        final List<Integer> uidsToRemove = new ArrayList<>();
         mCpuUidFreqTimeReader.readDelta((uid, cpuFreqTimeMs) -> {
             uid = mapUid(uid);
             if (Process.isIsolated(uid)) {
-                mCpuUidFreqTimeReader.removeUid(uid);
+                uidsToRemove.add(uid);
                 if (DEBUG) Slog.d(TAG, "Got freq readings for an isolated uid: " + uid);
                 return;
             }
             if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
                 if (DEBUG) Slog.d(TAG, "Got freq readings for an invalid user's uid " + uid);
-                mCpuUidFreqTimeReader.removeUid(uid);
+                uidsToRemove.add(uid);
                 return;
             }
             final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, startTimeMs);
@@ -13001,6 +13071,9 @@
                 }
             }
         });
+        for (int uid : uidsToRemove) {
+            mCpuUidFreqTimeReader.removeUid(uid);
+        }
 
         final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
         if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) {
@@ -13047,21 +13120,25 @@
     public void readKernelUidCpuActiveTimesLocked(boolean onBattery) {
         final long startTimeMs = mClocks.uptimeMillis();
         final long elapsedRealtimeMs = mClocks.elapsedRealtime();
+        final List<Integer> uidsToRemove = new ArrayList<>();
         mCpuUidActiveTimeReader.readDelta((uid, cpuActiveTimesMs) -> {
             uid = mapUid(uid);
             if (Process.isIsolated(uid)) {
-                mCpuUidActiveTimeReader.removeUid(uid);
+                uidsToRemove.add(uid);
                 if (DEBUG) Slog.w(TAG, "Got active times for an isolated uid: " + uid);
                 return;
             }
             if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
                 if (DEBUG) Slog.w(TAG, "Got active times for an invalid user's uid " + uid);
-                mCpuUidActiveTimeReader.removeUid(uid);
+                uidsToRemove.add(uid);
                 return;
             }
             final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, startTimeMs);
             u.mCpuActiveTimeMs.addCountLocked(cpuActiveTimesMs, onBattery);
         });
+        for (int uid : uidsToRemove) {
+            mCpuUidActiveTimeReader.removeUid(uid);
+        }
 
         final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
         if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) {
@@ -13077,21 +13154,25 @@
     public void readKernelUidCpuClusterTimesLocked(boolean onBattery) {
         final long startTimeMs = mClocks.uptimeMillis();
         final long elapsedRealtimeMs = mClocks.elapsedRealtime();
+        final List<Integer> uidsToRemove = new ArrayList<>();
         mCpuUidClusterTimeReader.readDelta((uid, cpuClusterTimesMs) -> {
             uid = mapUid(uid);
             if (Process.isIsolated(uid)) {
-                mCpuUidClusterTimeReader.removeUid(uid);
+                uidsToRemove.add(uid);
                 if (DEBUG) Slog.w(TAG, "Got cluster times for an isolated uid: " + uid);
                 return;
             }
             if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
                 if (DEBUG) Slog.w(TAG, "Got cluster times for an invalid user's uid " + uid);
-                mCpuUidClusterTimeReader.removeUid(uid);
+                uidsToRemove.add(uid);
                 return;
             }
             final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, startTimeMs);
             u.mCpuClusterTimesMs.addCountLocked(cpuClusterTimesMs, onBattery);
         });
+        for (int uid : uidsToRemove) {
+            mCpuUidClusterTimeReader.removeUid(uid);
+        }
 
         final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
         if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) {
@@ -14454,7 +14535,12 @@
      */
     @GuardedBy("this")
     public void dumpMeasuredEnergyStatsLocked(PrintWriter pw) {
-        if (mGlobalMeasuredEnergyStats == null) return;
+        pw.printf("On battery measured energy stats (microjoules) \n");
+        if (mGlobalMeasuredEnergyStats == null) {
+            pw.printf("    Not supported on this device.\n");
+            return;
+        }
+
         dumpMeasuredEnergyStatsLocked(pw, "non-uid usage", mGlobalMeasuredEnergyStats);
 
         int size = mUidStats.size();
@@ -14472,7 +14558,8 @@
             MeasuredEnergyStats stats) {
         if (stats == null) return;
         final IndentingPrintWriter iPw = new IndentingPrintWriter(pw, "    ");
-        iPw.printf("On battery measured energy stats for %s:\n", name);
+        iPw.increaseIndent();
+        iPw.printf("%s:\n", name);
         iPw.increaseIndent();
         stats.dump(iPw);
         iPw.decreaseIndent();
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index 094724c..2798b72 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -72,6 +72,7 @@
                 mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new SystemServicePowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile));
+                mPowerCalculators.add(new CustomMeasuredPowerCalculator(mPowerProfile));
 
                 mPowerCalculators.add(new UserPowerCalculator());
             }
diff --git a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
new file mode 100644
index 0000000..4babe8d
--- /dev/null
+++ b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import android.os.BatteryStats;
+
+/**
+ * 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 {
+    public CustomMeasuredPowerCalculator(PowerProfile powerProfile) {
+    }
+
+    @Override
+    protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+            long rawUptimeUs, int statsType) {
+        updateCustomMeasuredPowerMah(app, u.getCustomMeasuredEnergiesMicroJoules());
+    }
+
+    private void updateCustomMeasuredPowerMah(BatterySipper sipper, long[] measuredEnergiesUJ) {
+        sipper.customMeasuredPowerMah = calculateMeasuredEnergiesMah(measuredEnergiesUJ);
+    }
+
+    private double[] calculateMeasuredEnergiesMah(long[] measuredEnergiesUJ) {
+        if (measuredEnergiesUJ == null) {
+            return null;
+        }
+        final double[] measuredEnergiesMah = new double[measuredEnergiesUJ.length];
+        for (int i = 0; i < measuredEnergiesUJ.length; i++) {
+            measuredEnergiesMah[i] = uJtoMah(measuredEnergiesUJ[i]);
+        }
+        return measuredEnergiesMah;
+    }
+}
diff --git a/core/java/com/android/internal/power/MeasuredEnergyArray.java b/core/java/com/android/internal/power/MeasuredEnergyArray.java
deleted file mode 100644
index 1f6dc26..0000000
--- a/core/java/com/android/internal/power/MeasuredEnergyArray.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.power;
-
-
-import android.annotation.IntDef;
-
-import com.android.internal.os.RailStats;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Interface to provide subsystem energy data.
- * TODO: replace this and {@link RailStats} once b/173077356 is done
- */
-public interface MeasuredEnergyArray {
-    int SUBSYSTEM_UNKNOWN = -1;
-    int SUBSYSTEM_DISPLAY = 0;
-    int NUMBER_SUBSYSTEMS = 1;
-    String[] SUBSYSTEM_NAMES = {"display"};
-
-
-    @IntDef(prefix = { "SUBSYSTEM_" }, value = {
-            SUBSYSTEM_UNKNOWN,
-            SUBSYSTEM_DISPLAY,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface MeasuredEnergySubsystem {}
-
-    /**
-     * Get the subsystem at an index in array.
-     *
-     * @param index into the array.
-     * @return subsystem.
-     */
-    @MeasuredEnergySubsystem
-    int getSubsystem(int index);
-
-    /**
-     * Get the energy (in microjoules) consumed since boot of the subsystem at an index.
-     *
-     * @param index into the array.
-     * @return energy (in microjoules) consumed since boot.
-     */
-    long getEnergy(int index);
-
-    /**
-     * Return number of subsystems in the array.
-     */
-    int size();
-}
diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java
index e310f8d..d7b4d78 100644
--- a/core/java/com/android/internal/power/MeasuredEnergyStats.java
+++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java
@@ -28,7 +28,6 @@
 import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.power.MeasuredEnergyArray.MeasuredEnergySubsystem;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -239,6 +238,7 @@
      * Return accumulated energy (in microjoules) for the a custom energy bucket since last reset.
      * Returns {@link android.os.BatteryStats#ENERGY_DATA_UNAVAILABLE} if this data is unavailable.
      */
+    @VisibleForTesting
     public long getAccumulatedCustomBucketEnergy(int customBucket) {
         if (!isValidCustomBucket(customBucket)) {
             return ENERGY_DATA_UNAVAILABLE;
@@ -247,7 +247,18 @@
     }
 
     /**
-     * Map {@link MeasuredEnergySubsystem} and device state to Display {@link StandardEnergyBucket}.
+     * Return accumulated energies (in microjoules) for all custom energy buckets since last reset.
+     */
+    public @NonNull long[] getAccumulatedCustomBucketEnergies() {
+        final long[] energies = new long[getNumberCustomEnergyBuckets()];
+        for (int bucket = 0; bucket < energies.length; bucket++) {
+            energies[bucket] = mAccumulatedEnergiesMicroJoules[customBucketToIndex(bucket)];
+        }
+        return energies;
+    }
+
+    /**
+     * Map {@link android.view.Display} STATE_ to corresponding {@link StandardEnergyBucket}.
      */
     public static @StandardEnergyBucket int getDisplayEnergyBucket(int screenState) {
         if (Display.isOnState(screenState)) {
@@ -405,7 +416,6 @@
 
     /** Dump debug data. */
     public void dump(PrintWriter pw) {
-        pw.println("Accumulated energy since last reset (microjoules):");
         pw.print("   ");
         for (int index = 0; index < mAccumulatedEnergiesMicroJoules.length; index++) {
             pw.print(getBucketName(index));
@@ -432,6 +442,11 @@
         return "CUSTOM_" + indexToCustomBucket(index);
     }
 
+    /** Get the number of custom energy buckets on this device. */
+    public int getNumberCustomEnergyBuckets() {
+        return mAccumulatedEnergiesMicroJoules.length - NUMBER_STANDARD_ENERGY_BUCKETS;
+    }
+
     private static int customBucketToIndex(int customBucket) {
         return customBucket + NUMBER_STANDARD_ENERGY_BUCKETS;
     }
@@ -450,7 +465,9 @@
         return bucket >= 0 && bucket < NUMBER_STANDARD_ENERGY_BUCKETS;
     }
 
-    private boolean isValidCustomBucket(int customBucket) {
+    /** Returns whether the given custom bucket is valid (exists) on this device. */
+    @VisibleForTesting
+    public boolean isValidCustomBucket(int customBucket) {
         return customBucket >= 0
                 && customBucketToIndex(customBucket) < mAccumulatedEnergiesMicroJoules.length;
     }
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 488b18d..c6a040c 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -47,6 +47,13 @@
     private CollectionUtils() { /* cannot be instantiated */ }
 
     /**
+     * @see Collection#contains(Object)
+     */
+    public static <T> boolean contains(@Nullable Collection<T> collection, T element) {
+        return collection != null && collection.contains(element);
+    }
+
+    /**
      * Returns a list of items from the provided list that match the given condition.
      *
      * This is similar to {@link Stream#filter} but without the overhead of creating an intermediate
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index f42f468..dc6880e 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -219,7 +219,7 @@
     }
 
     private static String getTraceTriggerNameForAction(@Action int action) {
-        return "latency-tracker-" + getNameOfAction(STATSD_ACTION[action]);
+        return "com.android.telemetry.latency-tracker-" + getNameOfAction(STATSD_ACTION[action]);
     }
 
     public static boolean isEnabled(Context ctx) {
diff --git a/core/java/com/android/internal/util/PerfettoTrigger.java b/core/java/com/android/internal/util/PerfettoTrigger.java
index 9c87c69..c758504 100644
--- a/core/java/com/android/internal/util/PerfettoTrigger.java
+++ b/core/java/com/android/internal/util/PerfettoTrigger.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.util;
 
+import android.os.SystemClock;
 import android.util.Log;
 
 import java.io.IOException;
@@ -27,16 +28,28 @@
 public class PerfettoTrigger {
     private static final String TAG = "PerfettoTrigger";
     private static final String TRIGGER_COMMAND = "/system/bin/trigger_perfetto";
+    private static final long THROTTLE_MILLIS = 60000;
+    private static volatile long sLastTriggerTime = -THROTTLE_MILLIS;
 
     /**
      * @param triggerName The name of the trigger. Must match the value defined in the AOT
      *                    Perfetto config.
      */
     public static void trigger(String triggerName) {
+        // Trace triggering has a non-negligible cost (fork+exec).
+        // To mitigate potential excessive triggering by the API client we ignore calls that happen
+        // too quickl after the most recent trigger.
+        long sinceLastTrigger = SystemClock.elapsedRealtime() - sLastTriggerTime;
+        if (sinceLastTrigger < THROTTLE_MILLIS) {
+            Log.v(TAG, "Not triggering " + triggerName + " - not enough time since last trigger");
+            return;
+        }
+
         try {
             ProcessBuilder pb = new ProcessBuilder(TRIGGER_COMMAND, triggerName);
             Log.v(TAG, "Triggering " + String.join(" ", pb.command()));
-            Process process = pb.start();
+            pb.start();
+            sLastTriggerTime = SystemClock.elapsedRealtime();
         } catch (IOException e) {
             Log.w(TAG, "Failed to trigger " + triggerName, e);
         }
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index cb586d6..8982519 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -1236,6 +1236,8 @@
 
         if (IncrementalManager.isFeatureEnabled()) {
             addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY, 0);
+            addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY_VERSION,
+                    IncrementalManager.isV2Available() ? 2 : 1);
         }
 
         if (PackageManager.APP_ENUMERATION_ENABLED_BY_DEFAULT) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 8edc8a1..9510f27 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -207,6 +207,7 @@
             ],
 
             shared_libs: [
+                "android.hardware.memtrack-unstable-ndk_platform",
                 "audioclient-types-aidl-cpp",
                 "audioflinger-aidl-cpp",
                 "av-types-aidl-cpp",
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index c64174b..a7950f2 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -33,6 +33,7 @@
 #include <string>
 #include <vector>
 
+#include <aidl/android/hardware/memtrack/DeviceInfo.h>
 #include <android-base/logging.h>
 #include <bionic/malloc.h>
 #include <debuggerd/client.h>
@@ -45,6 +46,7 @@
 #include "jni.h"
 #include <dmabufinfo/dmabuf_sysfs_stats.h>
 #include <dmabufinfo/dmabufinfo.h>
+#include <dmabufinfo/dmabuf_sysfs_stats.h>
 #include <meminfo/procmeminfo.h>
 #include <meminfo/sysmeminfo.h>
 #include <memtrack/memtrack.h>
@@ -520,14 +522,15 @@
     }
 
     if (outUssSwapPssRss != NULL) {
-        if (env->GetArrayLength(outUssSwapPssRss) >= 1) {
+        int outLen = env->GetArrayLength(outUssSwapPssRss);
+        if (outLen >= 1) {
             jlong* outUssSwapPssRssArray = env->GetLongArrayElements(outUssSwapPssRss, 0);
             if (outUssSwapPssRssArray != NULL) {
                 outUssSwapPssRssArray[0] = uss;
-                if (env->GetArrayLength(outUssSwapPssRss) >= 2) {
+                if (outLen >= 2) {
                     outUssSwapPssRssArray[1] = swapPss;
                 }
-                if (env->GetArrayLength(outUssSwapPssRss) >= 3) {
+                if (outLen >= 3) {
                     outUssSwapPssRssArray[2] = rss;
                 }
             }
@@ -536,10 +539,20 @@
     }
 
     if (outMemtrack != NULL) {
-        if (env->GetArrayLength(outMemtrack) >= 1) {
+        int outLen = env->GetArrayLength(outMemtrack);
+        if (outLen >= 1) {
             jlong* outMemtrackArray = env->GetLongArrayElements(outMemtrack, 0);
             if (outMemtrackArray != NULL) {
                 outMemtrackArray[0] = memtrack;
+                if (outLen >= 2) {
+                    outMemtrackArray[1] = graphics_mem.graphics;
+                }
+                if (outLen >= 3) {
+                    outMemtrackArray[2] = graphics_mem.gl;
+                }
+                if (outLen >= 4) {
+                    outMemtrackArray[3] = graphics_mem.other;
+                }
             }
             env->ReleaseLongArrayElements(outMemtrack, outMemtrackArray, 0);
         }
@@ -838,6 +851,31 @@
     return poolsSizeKb;
 }
 
+static jlong android_os_Debug_getGpuDmaBufUsageKb(JNIEnv* env, jobject clazz) {
+    std::vector<aidl::android::hardware::memtrack::DeviceInfo> gpu_device_info;
+    if (!memtrack_gpu_device_info(&gpu_device_info)) {
+        return -1;
+    }
+
+    dmabufinfo::DmabufSysfsStats stats;
+    if (!GetDmabufSysfsStats(&stats)) {
+        return -1;
+    }
+
+    jlong sizeKb = 0;
+    const auto& importer_stats = stats.importer_info();
+    for (const auto& dev_info : gpu_device_info) {
+        const auto& importer_info = importer_stats.find(dev_info.name);
+        if (importer_info == importer_stats.end()) {
+            continue;
+        }
+
+        sizeKb += importer_info->second.size;
+    }
+
+    return sizeKb;
+}
+
 static jlong android_os_Debug_getDmabufMappedSizeKb(JNIEnv* env, jobject clazz) {
     jlong dmabufPss = 0;
     std::vector<dmabufinfo::DmaBuffer> dmabufs;
@@ -946,6 +984,8 @@
             (void*)android_os_Debug_getIonHeapsSizeKb },
     { "getDmabufTotalExportedKb", "()J",
             (void*)android_os_Debug_getDmabufTotalExportedKb },
+    { "getGpuDmaBufUsageKb", "()J",
+            (void*)android_os_Debug_getGpuDmaBufUsageKb },
     { "getIonPoolsSizeKb", "()J",
             (void*)android_os_Debug_getIonPoolsSizeKb },
     { "getDmabufMappedSizeKb", "()J",
diff --git a/core/jni/android_os_incremental_IncrementalManager.cpp b/core/jni/android_os_incremental_IncrementalManager.cpp
index 44bff01..2384efa 100644
--- a/core/jni/android_os_incremental_IncrementalManager.cpp
+++ b/core/jni/android_os_incremental_IncrementalManager.cpp
@@ -30,6 +30,10 @@
     return IncFs_IsEnabled();
 }
 
+static jboolean nativeIsV2Available(JNIEnv* env, jobject clazz) {
+    return !!(IncFs_Features() & INCFS_FEATURE_V2);
+}
+
 static jboolean nativeIsIncrementalPath(JNIEnv* env,
                                     jobject clazz,
                                     jstring javaPath) {
@@ -53,12 +57,12 @@
     return result;
 }
 
-static const JNINativeMethod method_table[] = {{"nativeIsEnabled", "()Z", (void*)nativeIsEnabled},
-                                               {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z",
-                                                (void*)nativeIsIncrementalPath},
-                                               {"nativeUnsafeGetFileSignature",
-                                                "(Ljava/lang/String;)[B",
-                                                (void*)nativeUnsafeGetFileSignature}};
+static const JNINativeMethod method_table[] =
+        {{"nativeIsEnabled", "()Z", (void*)nativeIsEnabled},
+         {"nativeIsV2Available", "()Z", (void*)nativeIsV2Available},
+         {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z", (void*)nativeIsIncrementalPath},
+         {"nativeUnsafeGetFileSignature", "(Ljava/lang/String;)[B",
+          (void*)nativeUnsafeGetFileSignature}};
 
 int register_android_os_incremental_IncrementalManager(JNIEnv* env) {
     return jniRegisterNativeMethods(env, "android/os/incremental/IncrementalManager",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index dc1cc32..99014c5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1055,6 +1055,14 @@
         android:description="@string/permdesc_accessImsCallService"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi @hide Allows an application to perform IMS Single Registration related actions.
+         Only granted if the application is a system app AND is in the Default SMS Role.
+         The permission is revoked when the app is taken out of the Default SMS Role.
+        <p>Protection level: internal|role
+    -->
+    <permission android:name="android.permission.PERFORM_IMS_SINGLE_REGISTRATION"
+        android:protectionLevel="internal|role" />
+
     <!-- Allows an application to read the user's call log.
          <p class="note"><strong>Note:</strong> If your app uses the
          {@link #READ_CONTACTS} permission and <em>both</em> your <a
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 09ca12a..b7c755e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1590,8 +1590,8 @@
          take precedence over lower ones.
          See com.android.server.timedetector.TimeDetectorStrategy for available sources. -->
     <string-array name="config_autoTimeSourcesPriority">
-        <item>telephony</item>
         <item>network</item>
+        <item>telephony</item>
     </string-array>
 
     <!-- Enables the GnssTimeUpdate service. This is the global switch for enabling Gnss time based
diff --git a/core/tests/coretests/src/android/app/timedetector/ExternalTimeSuggestionTest.java b/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
similarity index 98%
rename from core/tests/coretests/src/android/app/timedetector/ExternalTimeSuggestionTest.java
rename to core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
index 6bcec25..266ff3d 100644
--- a/core/tests/coretests/src/android/app/timedetector/ExternalTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package android.app.time;
 
 import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
 import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
diff --git a/core/tests/coretests/src/android/content/pm/PermissionInfoTest.java b/core/tests/coretests/src/android/content/pm/PermissionInfoTest.java
new file mode 100644
index 0000000..606e81d
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/PermissionInfoTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.util.ArraySet;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class PermissionInfoTest {
+    private static final String KNOWN_CERT_DIGEST_1 =
+            "6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599";
+    private static final String KNOWN_CERT_DIGEST_2 =
+            "9369370ffcfdc1e92dae777252c05c483b8cbb55fa9d5fd9f6317f623ae6d8c6";
+
+    @Test
+    public void createFromParcel_returnsKnownCerts() {
+        // The platform supports a knownSigner permission protection flag that allows one or more
+        // trusted signing certificates to be specified with the permission declaration; if a
+        // requesting app is signed by any of these trusted certificates the permission is granted.
+        // This test verifies the Set of knownCerts is properly parceled / unparceled.
+        PermissionInfo permissionInfo = new PermissionInfo();
+        permissionInfo.protectionLevel =
+                PermissionInfo.PROTECTION_SIGNATURE | PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER;
+        permissionInfo.knownCerts = new ArraySet<>(2);
+        permissionInfo.knownCerts.add(KNOWN_CERT_DIGEST_1);
+        permissionInfo.knownCerts.add(KNOWN_CERT_DIGEST_2);
+        Parcel parcel = Parcel.obtain();
+        permissionInfo.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);
+        PermissionInfo unparceledPermissionInfo = PermissionInfo.CREATOR.createFromParcel(parcel);
+
+        assertNotNull(unparceledPermissionInfo.knownCerts);
+        assertEquals(2, unparceledPermissionInfo.knownCerts.size());
+        assertTrue(unparceledPermissionInfo.knownCerts.contains(KNOWN_CERT_DIGEST_1));
+        assertTrue(unparceledPermissionInfo.knownCerts.contains(KNOWN_CERT_DIGEST_2));
+    }
+}
diff --git a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
index 564103e..11239db 100644
--- a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
@@ -17,6 +17,8 @@
 package android.os;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
 
 import static org.testng.Assert.assertThrows;
 
@@ -31,7 +33,6 @@
 @Presubmit
 @RunWith(JUnit4.class)
 public class CombinedVibrationEffectTest {
-
     private static final VibrationEffect VALID_EFFECT = VibrationEffect.createOneShot(10, 255);
     private static final VibrationEffect INVALID_EFFECT = new VibrationEffect.OneShot(-1, -1);
 
@@ -172,6 +173,37 @@
     }
 
     @Test
+    public void testHasVibratorMono_returnsTrueForAnyVibrator() {
+        CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
+                VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
+        assertTrue(effect.hasVibrator(0));
+        assertTrue(effect.hasVibrator(1));
+    }
+
+    @Test
+    public void testHasVibratorStereo_returnsOnlyTheIdsSet() {
+        CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced()
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .combine();
+        assertFalse(effect.hasVibrator(0));
+        assertTrue(effect.hasVibrator(1));
+        assertFalse(effect.hasVibrator(2));
+    }
+
+    @Test
+    public void testHasVibratorSequential_returnsNestedVibrators() {
+        CombinedVibrationEffect effect = CombinedVibrationEffect.startSequential()
+                .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addNext(CombinedVibrationEffect.startSynced()
+                        .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
+                        .combine())
+                .combine();
+        assertFalse(effect.hasVibrator(0));
+        assertTrue(effect.hasVibrator(1));
+        assertTrue(effect.hasVibrator(2));
+    }
+
+    @Test
     public void testSerializationMono() {
         CombinedVibrationEffect original = CombinedVibrationEffect.createSynced(VALID_EFFECT);
 
diff --git a/core/tests/coretests/src/android/provider/OWNERS b/core/tests/coretests/src/android/provider/OWNERS
new file mode 100644
index 0000000..581da71
--- /dev/null
+++ b/core/tests/coretests/src/android/provider/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/provider/OWNERS
diff --git a/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java b/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java
index a43b238..a121941 100644
--- a/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java
+++ b/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java
@@ -16,14 +16,14 @@
 
 package android.service.notification;
 
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
+import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.app.NotificationChannel;
+import android.content.pm.VersionedPackage;
 import android.os.Parcel;
 import android.util.ArraySet;
 
@@ -45,16 +45,19 @@
         assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_SILENT)).isTrue();
         assertThat(nlf.getTypes()).isEqualTo(FLAG_FILTER_TYPE_CONVERSATIONS
                 | FLAG_FILTER_TYPE_ALERTING
-                | FLAG_FILTER_TYPE_SILENT);
+                | FLAG_FILTER_TYPE_SILENT
+                | FLAG_FILTER_TYPE_ONGOING);
 
         assertThat(nlf.getDisallowedPackages()).isEmpty();
-        assertThat(nlf.isPackageAllowed("pkg1")).isTrue();
+        assertThat(nlf.isPackageAllowed(new VersionedPackage("any", 0))).isTrue();
     }
 
 
     @Test
     public void testConstructor() {
-        ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1", "pkg2"});
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+        VersionedPackage a2= new VersionedPackage("pkg2", 2142534);
+        ArraySet<VersionedPackage> pkgs = new ArraySet<>(new VersionedPackage[] {a1, a2});
         NotificationListenerFilter nlf =
                 new NotificationListenerFilter(FLAG_FILTER_TYPE_ALERTING, pkgs);
         assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_CONVERSATIONS)).isFalse();
@@ -62,20 +65,21 @@
         assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_SILENT)).isFalse();
         assertThat(nlf.getTypes()).isEqualTo(FLAG_FILTER_TYPE_ALERTING);
 
-        assertThat(nlf.getDisallowedPackages()).contains("pkg1");
-        assertThat(nlf.getDisallowedPackages()).contains("pkg2");
-        assertThat(nlf.isPackageAllowed("pkg1")).isFalse();
-        assertThat(nlf.isPackageAllowed("pkg2")).isFalse();
+        assertThat(nlf.getDisallowedPackages()).contains(a1);
+        assertThat(nlf.getDisallowedPackages()).contains(a2);
+        assertThat(nlf.isPackageAllowed(a1)).isFalse();
+        assertThat(nlf.isPackageAllowed(a2)).isFalse();
     }
 
     @Test
     public void testSetDisallowedPackages() {
         NotificationListenerFilter nlf = new NotificationListenerFilter();
 
-        ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1"});
+        ArraySet<VersionedPackage> pkgs = new ArraySet<>(
+                new VersionedPackage[] {new VersionedPackage("pkg1", 0)});
         nlf.setDisallowedPackages(pkgs);
 
-        assertThat(nlf.isPackageAllowed("pkg1")).isFalse();
+        assertThat(nlf.isPackageAllowed(new VersionedPackage("pkg1", 0))).isFalse();
     }
 
     @Test
@@ -94,7 +98,9 @@
     @Test
     public void testDescribeContents() {
         final int expected = 0;
-        ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1", "pkg2"});
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+        VersionedPackage a2= new VersionedPackage("pkg2", 2142534);
+        ArraySet<VersionedPackage> pkgs = new ArraySet<>(new VersionedPackage[] {a1, a2});
         NotificationListenerFilter nlf =
                 new NotificationListenerFilter(FLAG_FILTER_TYPE_ALERTING, pkgs);
         assertThat(nlf.describeContents()).isEqualTo(expected);
@@ -102,7 +108,9 @@
 
     @Test
     public void testParceling() {
-        ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1", "pkg2"});
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+        VersionedPackage a2= new VersionedPackage("pkg2", 2142534);
+        ArraySet<VersionedPackage> pkgs = new ArraySet<>(new VersionedPackage[] {a1, a2});
         NotificationListenerFilter nlf =
                 new NotificationListenerFilter(FLAG_FILTER_TYPE_ALERTING, pkgs);
 
@@ -116,9 +124,9 @@
         assertThat(nlf1.isTypeAllowed(FLAG_FILTER_TYPE_SILENT)).isFalse();
         assertThat(nlf1.getTypes()).isEqualTo(FLAG_FILTER_TYPE_ALERTING);
 
-        assertThat(nlf1.getDisallowedPackages()).contains("pkg1");
-        assertThat(nlf1.getDisallowedPackages()).contains("pkg2");
-        assertThat(nlf1.isPackageAllowed("pkg1")).isFalse();
-        assertThat(nlf1.isPackageAllowed("pkg2")).isFalse();
+        assertThat(nlf1.getDisallowedPackages()).contains(a1);
+        assertThat(nlf1.getDisallowedPackages()).contains(a2);
+        assertThat(nlf1.isPackageAllowed(a1)).isFalse();
+        assertThat(nlf1.isPackageAllowed(a2)).isFalse();
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 97c07ea..6652c64 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -24,6 +24,7 @@
 import android.os.BatteryStats.HistoryItem;
 import android.os.BatteryStats.Uid.Sensor;
 import android.os.WorkSource;
+import android.util.SparseLongArray;
 import android.view.Display;
 
 import androidx.test.filters.SmallTest;
@@ -583,6 +584,95 @@
         checkMeasuredEnergy("H", uid1, blame1, uid2, blame2, globalDoze, bi);
     }
 
+    @SmallTest
+    public void testUpdateCustomMeasuredEnergyDataLocked_neverCalled() {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.setOnBatteryInternal(true);
+
+        final int uid1 = 11500;
+        final int uid2 = 11501;
+
+        // Initially, all custom buckets report energy of 0.
+        checkCustomMeasuredEnergy("0", 0, 0, uid1, 0, 0, uid2, 0, 0, bi);
+    }
+
+    @SmallTest
+    public void testUpdateCustomMeasuredEnergyDataLocked() {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+        final int bucketA = 0; // Custom bucket 0
+        final int bucketB = 1; // Custom bucket 1
+
+        long totalBlameA = 0; // Total energy consumption for bucketA (may exceed sum of uids)
+        long totalBlameB = 0; // Total energy consumption for bucketB (may exceed sum of uids)
+
+        final int uid1 = 10500;
+        long blame1A = 0; // Blame for uid1 in bucketA
+        long blame1B = 0; // Blame for uid1 in bucketB
+
+        final int uid2 = 10501;
+        long blame2A = 0; // Blame for uid2 in bucketA
+        long blame2B = 0; // Blame for uid2 in bucketB
+
+        final SparseLongArray newEnergiesA = new SparseLongArray(2);
+        final SparseLongArray newEnergiesB = new SparseLongArray(2);
+
+
+        // ----- Case A: battery off (so blame does not increase)
+        bi.setOnBatteryInternal(false);
+
+        newEnergiesA.put(uid1, 20_000);
+        // Implicit newEnergiesA.put(uid2, 0);
+        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 500_000, newEnergiesA);
+
+        newEnergiesB.put(uid1, 60_000);
+        // Implicit newEnergiesB.put(uid2, 0);
+        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 700_000, newEnergiesB);
+
+        checkCustomMeasuredEnergy(
+                "A", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+
+
+        // ----- Case B: battery on
+        bi.setOnBatteryInternal(true);
+
+        newEnergiesA.put(uid1, 7_000); blame1A += 7_000;
+        // Implicit newEnergiesA.put(uid2, 0); blame2A += 0;
+        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 310_000, newEnergiesA);
+        totalBlameA += 310_000;
+
+        newEnergiesB.put(uid1, 63_000); blame1B += 63_000;
+        newEnergiesB.put(uid2, 15_000); blame2B += 15_000;
+        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 790_000, newEnergiesB);
+        totalBlameB += 790_000;
+
+        checkCustomMeasuredEnergy(
+                "B", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+
+
+        // ----- Case C: battery still on
+        newEnergiesA.delete(uid1); blame1A += 0;
+        newEnergiesA.put(uid2, 16_000); blame2A += 16_000;
+        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 560_000, newEnergiesA);
+        totalBlameA += 560_000;
+
+        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 10_000, null);
+        totalBlameB += 10_000;
+
+        checkCustomMeasuredEnergy(
+                "C", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+
+
+        // ----- Case D: battery still on
+        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 0, newEnergiesA);
+        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 15_000, new SparseLongArray(1));
+        totalBlameB += 15_000;
+        checkCustomMeasuredEnergy(
+                "D", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+    }
+
     private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) {
         // Note that noteUidProcessStateLocked uses ActivityManager process states.
         if (fgOn) {
@@ -610,4 +700,33 @@
         assertEquals("Wrong doze for Case " + caseName, globalDoze,
                 bi.getScreenDozeEnergy());
     }
+
+    private void checkCustomMeasuredEnergy(String caseName,
+            long totalBlameA, long totalBlameB,
+            int uid1, long blame1A, long blame1B,
+            int uid2, long blame2A, long blame2B,
+            MockBatteryStatsImpl bi) {
+
+        final long[] actualTotal = bi.getCustomMeasuredEnergiesMicroJoules();
+        final long[] actualUid1 = bi.getUidStatsLocked(uid1).getCustomMeasuredEnergiesMicroJoules();
+        final long[] actualUid2 = bi.getUidStatsLocked(uid2).getCustomMeasuredEnergiesMicroJoules();
+
+        assertNotNull(actualTotal);
+        assertNotNull(actualUid1);
+        assertNotNull(actualUid2);
+
+        assertEquals("Wrong total blame in bucket 0 for Case " + caseName, totalBlameA,
+                actualTotal[0]);
+
+        assertEquals("Wrong total blame in bucket 1 for Case " + caseName, totalBlameB,
+                actualTotal[1]);
+
+        assertEquals("Wrong uid1 blame in bucket 0 for Case " + caseName, blame1A, actualUid1[0]);
+
+        assertEquals("Wrong uid1 blame in bucket 1 for Case " + caseName, blame1B, actualUid1[1]);
+
+        assertEquals("Wrong uid2 blame in bucket 0 for Case " + caseName, blame2A, actualUid2[0]);
+
+        assertEquals("Wrong uid2 blame in bucket 1 for Case " + caseName, blame2B, actualUid2[1]);
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
index b9908f4..5fd5a78 100644
--- a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
@@ -387,6 +387,59 @@
     }
 
     @Test
+    public void testIsValidCustomBucket() {
+        final MeasuredEnergyStats stats
+                = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3);
+        assertFalse(stats.isValidCustomBucket(-1));
+        assertTrue(stats.isValidCustomBucket(0));
+        assertTrue(stats.isValidCustomBucket(1));
+        assertTrue(stats.isValidCustomBucket(2));
+        assertFalse(stats.isValidCustomBucket(3));
+        assertFalse(stats.isValidCustomBucket(4));
+
+        final MeasuredEnergyStats boringStats
+                = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0);
+        assertFalse(boringStats.isValidCustomBucket(-1));
+        assertFalse(boringStats.isValidCustomBucket(0));
+        assertFalse(boringStats.isValidCustomBucket(1));
+    }
+
+    @Test
+    public void testGetAccumulatedCustomBucketEnergies() {
+        final MeasuredEnergyStats stats
+                = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3);
+
+        stats.updateCustomBucket(0, 50, true);
+        stats.updateCustomBucket(1, 60, true);
+        stats.updateCustomBucket(2, 13, true);
+        stats.updateCustomBucket(1, 70, true);
+
+        final long[] output = stats.getAccumulatedCustomBucketEnergies();
+        assertEquals(3, output.length);
+
+        assertEquals(50, output[0]);
+        assertEquals(60 + 70, output[1]);
+        assertEquals(13, output[2]);
+    }
+
+    @Test
+    public void testGetAccumulatedCustomBucketEnergies_empty() {
+        final MeasuredEnergyStats stats
+                = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0);
+
+        final long[] output = stats.getAccumulatedCustomBucketEnergies();
+        assertEquals(0, output.length);
+    }
+
+    @Test
+    public void testGetNumberCustomEnergyBuckets() {
+        assertEquals(0, new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0)
+                .getNumberCustomEnergyBuckets());
+        assertEquals(3, new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3)
+                .getNumberCustomEnergyBuckets());
+    }
+
+    @Test
     public void testReset() {
         final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
         final int numCustomBuckets = 2;
diff --git a/core/tests/coretests/src/com/android/internal/widget/OWNERS b/core/tests/coretests/src/com/android/internal/widget/OWNERS
new file mode 100644
index 0000000..b40fe24
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/OWNERS
@@ -0,0 +1,3 @@
+# LockSettings related
+per-file *LockPattern* = file:/services/core/java/com/android/server/locksettings/OWNERS
+per-file *Lockscreen* = file:/services/core/java/com/android/server/locksettings/OWNERS
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index 65d3a01..549e074 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -6,7 +6,6 @@
 jsharkey@android.com
 jsharkey@google.com
 lorenzo@google.com
-moltmann@google.com
 svetoslavganov@android.com
 svetoslavganov@google.com
 toddke@android.com
diff --git a/data/etc/car/com.android.car.provision.xml b/data/etc/car/com.android.car.provision.xml
index 4fd9cae..42cfd3c 100644
--- a/data/etc/car/com.android.car.provision.xml
+++ b/data/etc/car/com.android.car.provision.xml
@@ -17,6 +17,7 @@
 <permissions>
     <privapp-permissions package="com.android.car.provision">
         <permission name="android.car.permission.CAR_POWERTRAIN"/>
+        <permission name="android.permission.DISPATCH_PROVISIONING_MESSAGE"/>
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
         <permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
         <permission name="android.permission.MANAGE_USERS"/>
diff --git a/data/etc/com.android.emergency.xml b/data/etc/com.android.emergency.xml
index 734561c..fa92b6d 100644
--- a/data/etc/com.android.emergency.xml
+++ b/data/etc/com.android.emergency.xml
@@ -19,6 +19,7 @@
         <!-- Required to place emergency calls from emergency info screen. -->
         <permission name="android.permission.CALL_PRIVILEGED"/>
         <permission name="android.permission.MANAGE_USERS"/>
+        <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
         <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/com.android.provision.xml b/data/etc/com.android.provision.xml
index d2ea0ec..68f8298 100644
--- a/data/etc/com.android.provision.xml
+++ b/data/etc/com.android.provision.xml
@@ -17,7 +17,7 @@
 <permissions>
     <privapp-permissions package="com.android.provision">
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
-        <permissionn ame="android.permission.DISPATCH_PROVISIONING_MESSAGE"/>
+        <permission name="android.permission.DISPATCH_PROVISIONING_MESSAGE"/>
         <permission name="android.permission.MASTER_CLEAR"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index e473c55..3fdb0da 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -58,5 +58,6 @@
         <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
         <permission name="android.permission.READ_DREAM_STATE"/>
         <permission name="android.permission.READ_DREAM_SUPPRESSION"/>
+        <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/>
     </privapp-permissions>
 </permissions>
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 35e6b859..40c75a4 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1414,6 +1414,16 @@
         return Arrays.binarySearch(mSupportedAxes, axis) >= 0;
     }
 
+    /** @hide */
+    public List<FontFamily> getFallback() {
+        ArrayList<FontFamily> families = new ArrayList<>();
+        int familySize = nativeGetFamilySize(native_instance);
+        for (int i = 0; i < familySize; ++i) {
+            families.add(new FontFamily(nativeGetFamily(native_instance, i)));
+        }
+        return families;
+    }
+
     private static native long nativeCreateFromTypeface(long native_instance, int style);
     private static native long nativeCreateFromTypefaceWithExactStyle(
             long native_instance, int weight, boolean italic);
@@ -1439,6 +1449,13 @@
     @CriticalNative
     private static native long nativeGetReleaseFunc();
 
+    @CriticalNative
+    private static native int nativeGetFamilySize(long naitvePtr);
+
+    @CriticalNative
+    private static native long nativeGetFamily(long nativePtr, int index);
+
+
     private static native void nativeRegisterGenericFamily(String str, long nativePtr);
 
     private static native int nativeWriteTypefaces(
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index 8c13d3e..a771a6e 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -125,7 +125,7 @@
                 nAddFont(builderPtr, mFonts.get(i).getNativePtr());
             }
             final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback);
-            final FontFamily family = new FontFamily(mFonts, ptr);
+            final FontFamily family = new FontFamily(ptr);
             sFamilyRegistory.registerNativeAllocation(family, ptr);
             return family;
         }
@@ -146,7 +146,8 @@
     private final long mNativePtr;
 
     // Use Builder instead.
-    private FontFamily(@NonNull ArrayList<Font> fonts, long ptr) {
+    /** @hide */
+    public FontFamily(long ptr) {
         mNativePtr = ptr;
     }
 
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 904085f..255f9e6 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -68,44 +68,23 @@
      */
     public static @NonNull Set<Font> getAvailableFonts() {
         synchronized (LOCK) {
-            if (sAvailableFonts != null) {
-                return sAvailableFonts;
-            }
-
-            if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
-                sAvailableFonts = collectAllFonts();
-            } else {
+            if (sAvailableFonts == null) {
                 Set<Font> set = new ArraySet<>();
-                for (FontFamily[] items : sFamilyMap.values()) {
-                    for (FontFamily family : items) {
-                        for (int i = 0; i < family.getSize(); ++i) {
-                            set.add(family.getFont(i));
+                for (Typeface tf : Typeface.getSystemFontMap().values()) {
+                    List<FontFamily> families = tf.getFallback();
+                    for (int i = 0; i < families.size(); ++i) {
+                        FontFamily family = families.get(i);
+                        for (int j = 0; j < family.getSize(); ++j) {
+                            set.add(family.getFont(j));
                         }
                     }
                 }
-
                 sAvailableFonts = Collections.unmodifiableSet(set);
             }
             return sAvailableFonts;
         }
     }
 
-    private static @NonNull Set<Font> collectAllFonts() {
-        // TODO: use updated fonts
-        FontConfig fontConfig = getSystemPreinstalledFontConfig();
-        Map<String, FontFamily[]> map = buildSystemFallback(fontConfig);
-
-        Set<Font> res = new ArraySet<>();
-        for (FontFamily[] families : map.values()) {
-            for (FontFamily family : families) {
-                for (int i = 0; i < family.getSize(); ++i) {
-                    res.add(family.getFont(i));
-                }
-            }
-        }
-        return res;
-    }
-
     private static @Nullable ByteBuffer mmap(@NonNull String fullPath) {
         try (FileInputStream file = new FileInputStream(fullPath)) {
             final FileChannel fileChannel = file.getChannel();
@@ -329,13 +308,4 @@
         Typeface.initSystemDefaultTypefaces(fallbackMap, fontConfig.getAliases(), result);
         return result;
     }
-
-    /**
-     * @hide
-     */
-    public void resetFallbackMapping(Map<String, FontFamily[]> fallbackMap) {
-        synchronized (LOCK) {
-            sFamilyMap = fallbackMap;
-        }
-    }
 }
diff --git a/graphics/java/android/graphics/pdf/OWNERS b/graphics/java/android/graphics/pdf/OWNERS
index f04e200..057dc0d 100644
--- a/graphics/java/android/graphics/pdf/OWNERS
+++ b/graphics/java/android/graphics/pdf/OWNERS
@@ -5,4 +5,3 @@
 sumir@google.com
 svetoslavganov@android.com
 svetoslavganov@google.com
-moltmann@google.com
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 334b111..988838b 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -17,6 +17,7 @@
 package android.security.keystore;
 
 import android.annotation.Nullable;
+import android.content.Context;
 import android.os.Build;
 import android.security.Credentials;
 import android.security.KeyPairGeneratorSpec;
@@ -25,6 +26,8 @@
 import android.security.keymaster.KeymasterArguments;
 import android.security.keymaster.KeymasterCertificateChain;
 import android.security.keymaster.KeymasterDefs;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
 
 import com.android.internal.org.bouncycastle.asn1.ASN1EncodableVector;
 import com.android.internal.org.bouncycastle.asn1.ASN1InputStream;
@@ -477,11 +480,11 @@
 
             success = true;
             return keyPair;
-        } catch (ProviderException e) {
+        } catch (ProviderException | IllegalArgumentException | DeviceIdAttestationException e) {
           if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) {
               throw new SecureKeyImportUnavailableException(e);
           } else {
-              throw e;
+                throw new ProviderException(e);
           }
         } finally {
             if (!success) {
@@ -491,7 +494,7 @@
     }
 
     private Iterable<byte[]> createCertificateChain(final String privateKeyAlias, KeyPair keyPair)
-            throws ProviderException {
+            throws ProviderException, DeviceIdAttestationException {
         byte[] challenge = mSpec.getAttestationChallenge();
         if (challenge != null) {
             KeymasterArguments args = new KeymasterArguments();
@@ -510,6 +513,60 @@
                         Build.MODEL.getBytes(StandardCharsets.UTF_8));
             }
 
+            int[] idTypes = mSpec.getAttestationIds();
+            if (idTypes != null) {
+                final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length);
+                for (int idType : idTypes) {
+                    idTypesSet.add(idType);
+                }
+                TelephonyManager telephonyService = null;
+                if (idTypesSet.contains(AttestationUtils.ID_TYPE_IMEI)
+                        || idTypesSet.contains(AttestationUtils.ID_TYPE_MEID)) {
+                    telephonyService =
+                            (TelephonyManager) KeyStore.getApplicationContext().getSystemService(
+                                    Context.TELEPHONY_SERVICE);
+                    if (telephonyService == null) {
+                        throw new DeviceIdAttestationException(
+                                "Unable to access telephony service");
+                    }
+                }
+                for (final Integer idType : idTypesSet) {
+                    switch (idType) {
+                        case AttestationUtils.ID_TYPE_SERIAL:
+                            args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL,
+                                    Build.getSerial().getBytes(StandardCharsets.UTF_8)
+                            );
+                            break;
+                        case AttestationUtils.ID_TYPE_IMEI: {
+                            final String imei = telephonyService.getImei(0);
+                            if (imei == null) {
+                                throw new DeviceIdAttestationException("Unable to retrieve IMEI");
+                            }
+                            args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI,
+                                    imei.getBytes(StandardCharsets.UTF_8)
+                            );
+                            break;
+                        }
+                        case AttestationUtils.ID_TYPE_MEID: {
+                            final String meid = telephonyService.getMeid(0);
+                            if (meid == null) {
+                                throw new DeviceIdAttestationException("Unable to retrieve MEID");
+                            }
+                            args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID,
+                                    meid.getBytes(StandardCharsets.UTF_8)
+                            );
+                            break;
+                        }
+                        case AttestationUtils.USE_INDIVIDUAL_ATTESTATION: {
+                            args.addBoolean(KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION);
+                            break;
+                        }
+                        default:
+                            throw new IllegalArgumentException("Unknown device ID type " + idType);
+                    }
+                }
+            }
+
             return getAttestationChain(privateKeyAlias, keyPair, args);
         }
 
@@ -547,7 +604,8 @@
         }
     }
 
-    private KeymasterArguments constructKeyGenerationArguments() {
+    private KeymasterArguments constructKeyGenerationArguments()
+            throws IllegalArgumentException, DeviceIdAttestationException {
         KeymasterArguments args = new KeymasterArguments();
         args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits);
         args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm);
@@ -565,9 +623,9 @@
                 mSpec.getKeyValidityForConsumptionEnd());
         addAlgorithmSpecificParameters(args);
 
-        if (mSpec.isUniqueIdIncluded())
+        if (mSpec.isUniqueIdIncluded()) {
             args.addBoolean(KeymasterDefs.KM_TAG_INCLUDE_UNIQUE_ID);
-
+        }
         return args;
     }
 
diff --git a/keystore/java/android/security/keystore/ArrayUtils.java b/keystore/java/android/security/keystore/ArrayUtils.java
index c8c1de4..f22b604 100644
--- a/keystore/java/android/security/keystore/ArrayUtils.java
+++ b/keystore/java/android/security/keystore/ArrayUtils.java
@@ -34,6 +34,14 @@
         return ((array != null) && (array.length > 0)) ? array.clone() : array;
     }
 
+    /**
+     * Clones an array if it is not null and has a length greater than 0. Otherwise, returns the
+     * array.
+     */
+    public static int[] cloneIfNotEmpty(int[] array) {
+        return ((array != null) && (array.length > 0)) ? array.clone() : array;
+    }
+
     public static byte[] cloneIfNotEmpty(byte[] array) {
         return ((array != null) && (array.length > 0)) ? array.clone() : array;
     }
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index c2a7b2e..e92eaca 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -267,6 +267,7 @@
     private final boolean mUserPresenceRequired;
     private final byte[] mAttestationChallenge;
     private final boolean mDevicePropertiesAttestationIncluded;
+    private final int[] mAttestationIds;
     private final boolean mUniqueIdIncluded;
     private final boolean mUserAuthenticationValidWhileOnBody;
     private final boolean mInvalidatedByBiometricEnrollment;
@@ -308,6 +309,7 @@
             boolean userPresenceRequired,
             byte[] attestationChallenge,
             boolean devicePropertiesAttestationIncluded,
+            int[] attestationIds,
             boolean uniqueIdIncluded,
             boolean userAuthenticationValidWhileOnBody,
             boolean invalidatedByBiometricEnrollment,
@@ -361,6 +363,7 @@
         mUserAuthenticationType = userAuthenticationType;
         mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge);
         mDevicePropertiesAttestationIncluded = devicePropertiesAttestationIncluded;
+        mAttestationIds = attestationIds;
         mUniqueIdIncluded = uniqueIdIncluded;
         mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
         mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
@@ -720,6 +723,25 @@
     }
 
     /**
+     * @hide
+     * Allows the caller to specify device IDs to be attested to in the certificate for the
+     * generated key pair. These values are the enums specified in
+     * {@link android.security.keystore.AttestationUtils}
+     *
+     * @see android.security.keystore.AttestationUtils#ID_TYPE_SERIAL
+     * @see android.security.keystore.AttestationUtils#ID_TYPE_IMEI
+     * @see android.security.keystore.AttestationUtils#ID_TYPE_MEID
+     * @see android.security.keystore.AttestationUtils#USE_INDIVIDUAL_ATTESTATION
+     *
+     * @return integer array representing the requested device IDs to attest.
+     */
+    @SystemApi
+    @Nullable
+    public int[] getAttestationIds() {
+        return Utils.cloneIfNotNull(mAttestationIds);
+    }
+
+    /**
      * @hide This is a system-only API
      *
      * Returns {@code true} if the attestation certificate will contain a unique ID field.
@@ -834,6 +856,7 @@
         private boolean mUserPresenceRequired = false;
         private byte[] mAttestationChallenge = null;
         private boolean mDevicePropertiesAttestationIncluded = false;
+        private int[] mAttestationIds = null;
         private boolean mUniqueIdIncluded = false;
         private boolean mUserAuthenticationValidWhileOnBody;
         private boolean mInvalidatedByBiometricEnrollment = true;
@@ -902,6 +925,7 @@
             mAttestationChallenge = sourceSpec.getAttestationChallenge();
             mDevicePropertiesAttestationIncluded =
                     sourceSpec.isDevicePropertiesAttestationIncluded();
+            mAttestationIds = sourceSpec.getAttestationIds();
             mUniqueIdIncluded = sourceSpec.isUniqueIdIncluded();
             mUserAuthenticationValidWhileOnBody = sourceSpec.isUserAuthenticationValidWhileOnBody();
             mInvalidatedByBiometricEnrollment = sourceSpec.isInvalidatedByBiometricEnrollment();
@@ -1473,6 +1497,26 @@
         }
 
         /**
+         * @hide
+         * Sets which IDs to attest in the attestation certificate for the key. The acceptable
+         * values in this integer array are the enums specified in
+         * {@link android.security.keystore.AttestationUtils}
+         *
+         * @param attestationIds the array of ID types to attest to in the certificate.
+         *
+         * @see android.security.keystore.AttestationUtils#ID_TYPE_SERIAL
+         * @see android.security.keystore.AttestationUtils#ID_TYPE_IMEI
+         * @see android.security.keystore.AttestationUtils#ID_TYPE_MEID
+         * @see android.security.keystore.AttestationUtils#USE_INDIVIDUAL_ATTESTATION
+         */
+        @SystemApi
+        @NonNull
+        public Builder setAttestationIds(@NonNull int[] attestationIds) {
+            mAttestationIds = attestationIds;
+            return this;
+        }
+
+        /**
          * @hide Only system apps can use this method.
          *
          * Sets whether to include a temporary unique ID field in the attestation certificate.
@@ -1638,6 +1682,7 @@
                     mUserPresenceRequired,
                     mAttestationChallenge,
                     mDevicePropertiesAttestationIncluded,
+                    mAttestationIds,
                     mUniqueIdIncluded,
                     mUserAuthenticationValidWhileOnBody,
                     mInvalidatedByBiometricEnrollment,
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index 8163472..1f2f853 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -101,6 +101,7 @@
         out.writeBoolean(mSpec.isUserPresenceRequired());
         out.writeByteArray(mSpec.getAttestationChallenge());
         out.writeBoolean(mSpec.isDevicePropertiesAttestationIncluded());
+        out.writeIntArray(mSpec.getAttestationIds());
         out.writeBoolean(mSpec.isUniqueIdIncluded());
         out.writeBoolean(mSpec.isUserAuthenticationValidWhileOnBody());
         out.writeBoolean(mSpec.isInvalidatedByBiometricEnrollment());
@@ -160,6 +161,7 @@
         final boolean userPresenceRequired = in.readBoolean();
         final byte[] attestationChallenge = in.createByteArray();
         final boolean devicePropertiesAttestationIncluded = in.readBoolean();
+        final int[] attestationIds = in.createIntArray();
         final boolean uniqueIdIncluded = in.readBoolean();
         final boolean userAuthenticationValidWhileOnBody = in.readBoolean();
         final boolean invalidatedByBiometricEnrollment = in.readBoolean();
@@ -195,6 +197,7 @@
                 userPresenceRequired,
                 attestationChallenge,
                 devicePropertiesAttestationIncluded,
+                attestationIds,
                 uniqueIdIncluded,
                 userAuthenticationValidWhileOnBody,
                 invalidatedByBiometricEnrollment,
diff --git a/keystore/java/android/security/keystore/Utils.java b/keystore/java/android/security/keystore/Utils.java
index 5722c7b..e58b1cc 100644
--- a/keystore/java/android/security/keystore/Utils.java
+++ b/keystore/java/android/security/keystore/Utils.java
@@ -33,4 +33,8 @@
     static byte[] cloneIfNotNull(byte[] value) {
         return (value != null) ? value.clone() : null;
     }
+
+    static int[] cloneIfNotNull(int[] value) {
+        return (value != null) ? value.clone() : null;
+    }
 }
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 70e30d2..4d27c34 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -18,16 +18,20 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.hardware.security.keymint.KeyParameter;
 import android.hardware.security.keymint.SecurityLevel;
 import android.os.Build;
 import android.security.KeyPairGeneratorSpec;
+import android.security.KeyStore;
 import android.security.KeyStore2;
 import android.security.KeyStoreException;
 import android.security.KeyStoreSecurityLevel;
 import android.security.keymaster.KeymasterArguments;
 import android.security.keymaster.KeymasterDefs;
 import android.security.keystore.ArrayUtils;
+import android.security.keystore.AttestationUtils;
+import android.security.keystore.DeviceIdAttestationException;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeymasterUtils;
@@ -38,6 +42,8 @@
 import android.system.keystore2.KeyDescriptor;
 import android.system.keystore2.KeyMetadata;
 import android.system.keystore2.ResponseCode;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
 import android.util.Log;
 
 import libcore.util.EmptyArray;
@@ -478,7 +484,8 @@
                     }
                     throw p;
             }
-        } catch (UnrecoverableKeyException e) {
+        } catch (UnrecoverableKeyException | IllegalArgumentException
+                    | DeviceIdAttestationException e) {
             throw new ProviderException(
                     "Failed to construct key object from newly generated key pair.", e);
         } finally {
@@ -496,7 +503,7 @@
     }
 
     private void addAttestationParameters(@NonNull List<KeyParameter> params)
-            throws ProviderException {
+            throws ProviderException, IllegalArgumentException, DeviceIdAttestationException {
         byte[] challenge = mSpec.getAttestationChallenge();
 
         if (challenge != null) {
@@ -526,15 +533,69 @@
                         Build.MODEL.getBytes(StandardCharsets.UTF_8)
                 ));
             }
-        } else {
-            if (mSpec.isDevicePropertiesAttestationIncluded()) {
-                throw new ProviderException("An attestation challenge must be provided when "
-                        + "requesting device properties attestation.");
+
+            int[] idTypes = mSpec.getAttestationIds();
+            if (idTypes == null) {
+                return;
+            }
+            final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length);
+            for (int idType : idTypes) {
+                idTypesSet.add(idType);
+            }
+            TelephonyManager telephonyService = null;
+            if (idTypesSet.contains(AttestationUtils.ID_TYPE_IMEI)
+                    || idTypesSet.contains(AttestationUtils.ID_TYPE_MEID)) {
+                telephonyService =
+                    (TelephonyManager) KeyStore.getApplicationContext().getSystemService(
+                        Context.TELEPHONY_SERVICE);
+                if (telephonyService == null) {
+                    throw new DeviceIdAttestationException("Unable to access telephony service");
+                }
+            }
+            for (final Integer idType : idTypesSet) {
+                switch (idType) {
+                    case AttestationUtils.ID_TYPE_SERIAL:
+                        params.add(KeyStore2ParameterUtils.makeBytes(
+                                KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL,
+                                Build.getSerial().getBytes(StandardCharsets.UTF_8)
+                        ));
+                        break;
+                    case AttestationUtils.ID_TYPE_IMEI: {
+                        final String imei = telephonyService.getImei(0);
+                        if (imei == null) {
+                            throw new DeviceIdAttestationException("Unable to retrieve IMEI");
+                        }
+                        params.add(KeyStore2ParameterUtils.makeBytes(
+                                KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI,
+                                imei.getBytes(StandardCharsets.UTF_8)
+                        ));
+                        break;
+                    }
+                    case AttestationUtils.ID_TYPE_MEID: {
+                        final String meid = telephonyService.getMeid(0);
+                        if (meid == null) {
+                            throw new DeviceIdAttestationException("Unable to retrieve MEID");
+                        }
+                        params.add(KeyStore2ParameterUtils.makeBytes(
+                                KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID,
+                                meid.getBytes(StandardCharsets.UTF_8)
+                        ));
+                        break;
+                    }
+                    case AttestationUtils.USE_INDIVIDUAL_ATTESTATION: {
+                        params.add(KeyStore2ParameterUtils.makeBool(
+                                KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION));
+                        break;
+                    }
+                    default:
+                        throw new IllegalArgumentException("Unknown device ID type " + idType);
+                }
             }
         }
     }
 
-    private Collection<KeyParameter> constructKeyGenerationArguments() {
+    private Collection<KeyParameter> constructKeyGenerationArguments()
+            throws DeviceIdAttestationException, IllegalArgumentException {
         List<KeyParameter> params = new ArrayList<>();
         params.add(KeyStore2ParameterUtils.makeInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits));
         params.add(KeyStore2ParameterUtils.makeEnum(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
index aa82339..73fd693 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
@@ -16,24 +16,16 @@
 
 package com.android.wm.shell;
 
-import android.util.Slog;
-
-import com.android.wm.shell.apppairs.AppPairs;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.common.annotations.ExternalThread;
 
 import java.io.PrintWriter;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
 
 /**
  * An entry point into the shell for dumping shell internal state and running adb commands.
  *
  * Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}.
  */
+@ExternalThread
 public interface ShellCommandHandler {
     /**
      * Dumps the shell state.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
index 982cc00..eaed24d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
@@ -18,13 +18,12 @@
 
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
 
-import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.apppairs.AppPairsController;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
-import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
+import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import java.io.PrintWriter;
@@ -38,24 +37,24 @@
 public final class ShellCommandHandlerImpl {
     private static final String TAG = ShellCommandHandlerImpl.class.getSimpleName();
 
-    private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+    private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
     private final Optional<SplitScreenController> mSplitScreenOptional;
     private final Optional<Pip> mPipOptional;
-    private final Optional<OneHanded> mOneHandedOptional;
-    private final Optional<HideDisplayCutout> mHideDisplayCutout;
+    private final Optional<OneHandedController> mOneHandedOptional;
+    private final Optional<HideDisplayCutoutController> mHideDisplayCutout;
+    private final Optional<AppPairsController> mAppPairsOptional;
     private final ShellTaskOrganizer mShellTaskOrganizer;
-    private final Optional<AppPairs> mAppPairsOptional;
     private final ShellExecutor mMainExecutor;
     private final HandlerImpl mImpl = new HandlerImpl();
 
     public static ShellCommandHandler create(
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
             Optional<Pip> pipOptional,
-            Optional<OneHanded> oneHandedOptional,
-            Optional<HideDisplayCutout> hideDisplayCutout,
-            Optional<AppPairs> appPairsOptional,
+            Optional<OneHandedController> oneHandedOptional,
+            Optional<HideDisplayCutoutController> hideDisplayCutout,
+            Optional<AppPairsController> appPairsOptional,
             ShellExecutor mainExecutor) {
         return new ShellCommandHandlerImpl(shellTaskOrganizer, legacySplitScreenOptional,
                 splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout,
@@ -64,12 +63,12 @@
 
     private ShellCommandHandlerImpl(
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
             Optional<Pip> pipOptional,
-            Optional<OneHanded> oneHandedOptional,
-            Optional<HideDisplayCutout> hideDisplayCutout,
-            Optional<AppPairs> appPairsOptional,
+            Optional<OneHandedController> oneHandedOptional,
+            Optional<HideDisplayCutoutController> hideDisplayCutout,
+            Optional<AppPairsController> appPairsOptional,
             ShellExecutor mainExecutor) {
         mShellTaskOrganizer = shellTaskOrganizer;
         mLegacySplitScreenOptional = legacySplitScreenOptional;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index 925bf4b..7376d98 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -18,13 +18,12 @@
 
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
 
-import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.apppairs.AppPairsController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
 
@@ -39,9 +38,9 @@
     private final DisplayImeController mDisplayImeController;
     private final DragAndDropController mDragAndDropController;
     private final ShellTaskOrganizer mShellTaskOrganizer;
-    private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+    private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
     private final Optional<SplitScreenController> mSplitScreenOptional;
-    private final Optional<AppPairs> mAppPairsOptional;
+    private final Optional<AppPairsController> mAppPairsOptional;
     private final FullscreenTaskListener mFullscreenTaskListener;
     private final ShellExecutor mMainExecutor;
     private final Transitions mTransitions;
@@ -51,9 +50,9 @@
     public static ShellInit create(DisplayImeController displayImeController,
             DragAndDropController dragAndDropController,
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
-            Optional<AppPairs> appPairsOptional,
+            Optional<AppPairsController> appPairsOptional,
             FullscreenTaskListener fullscreenTaskListener,
             Transitions transitions,
             ShellExecutor mainExecutor) {
@@ -71,9 +70,9 @@
     private ShellInitImpl(DisplayImeController displayImeController,
             DragAndDropController dragAndDropController,
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
-            Optional<AppPairs> appPairsOptional,
+            Optional<AppPairsController> appPairsOptional,
             FullscreenTaskListener fullscreenTaskListener,
             Transitions transitions,
             ShellExecutor mainExecutor) {
@@ -97,7 +96,7 @@
         // Register the shell organizer
         mShellTaskOrganizer.registerOrganizer();
 
-        mAppPairsOptional.ifPresent(AppPairs::onOrganizerRegistered);
+        mAppPairsOptional.ifPresent(AppPairsController::onOrganizerRegistered);
         mSplitScreenOptional.ifPresent(SplitScreenController::onOrganizerRegistered);
 
         // Bind the splitscreen impl to the drag drop controller
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index a570c0a..b22f358 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -44,7 +44,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
+import com.android.wm.shell.sizecompatui.SizeCompatUIController;
 import com.android.wm.shell.startingsurface.StartingSurfaceDrawer;
 
 import java.io.PrintWriter;
@@ -108,20 +108,20 @@
      * compat.
      */
     @Nullable
-    private final SizeCompatUI mSizeCompatUI;
+    private final SizeCompatUIController mSizeCompatUI;
 
     public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
         this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */);
     }
 
     public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
-            SizeCompatUI sizeCompatUI) {
+            SizeCompatUIController sizeCompatUI) {
         this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI);
     }
 
     @VisibleForTesting
     ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
-            Context context, @Nullable SizeCompatUI sizeCompatUI) {
+            Context context, @Nullable SizeCompatUIController sizeCompatUI) {
         super(taskOrganizerController, mainExecutor);
         // TODO(b/131727939) temporarily live here, the starting surface drawer should be controlled
         //  by a controller, that class should be create while porting
@@ -342,8 +342,8 @@
     }
 
     /**
-     * Notifies {@link SizeCompatUI} about the size compat info changed on the give Task to update
-     * the UI accordingly.
+     * Notifies {@link SizeCompatUIController} about the size compat info changed on the give Task
+     * to update the UI accordingly.
      *
      * @param taskInfo the new Task info
      * @param taskListener listener to handle the Task Surface placement. {@code null} if task is
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
index a5dd79b..58ca1fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
@@ -30,6 +30,7 @@
 public class TaskViewFactoryController {
     private final ShellTaskOrganizer mTaskOrganizer;
     private final ShellExecutor mShellExecutor;
+    private final TaskViewFactory mImpl = new TaskViewFactoryImpl();
 
     public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
             ShellExecutor shellExecutor) {
@@ -37,8 +38,11 @@
         mShellExecutor = shellExecutor;
     }
 
+    public TaskViewFactory asTaskViewFactory() {
+        return mImpl;
+    }
+
     /** Creates an {@link TaskView} */
-    @ShellMainThread
     public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) {
         TaskView taskView = new TaskView(context, mTaskOrganizer);
         executor.execute(() -> {
@@ -46,10 +50,6 @@
         });
     }
 
-    public TaskViewFactory getTaskViewFactory() {
-        return new TaskViewFactoryImpl();
-    }
-
     private class TaskViewFactoryImpl implements TaskViewFactory {
         @ExternalThread
         public void create(@UiContext Context context,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
index abd9257..59271e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
@@ -27,6 +27,7 @@
 /**
  * The singleton wrapper to communicate between WindowManagerService and WMShell features
  * (e.g: PIP, SplitScreen, Bubble, OneHandedMode...etc)
+ * TODO: Remove once PinnedStackListenerForwarder can be removed
  */
 public class WindowManagerShellWrapper {
     private static final String TAG = WindowManagerShellWrapper.class.getSimpleName();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
index f5aa852..a9b1dbc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
@@ -35,8 +35,4 @@
     boolean pair(ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2);
     /** Unpairs any app-pair containing this task id. */
     void unpair(int taskId);
-    /** Dumps current status of app pairs. */
-    void dump(@NonNull PrintWriter pw, String prefix);
-    /** Called when the shell organizer has been registered. */
-    void onOrganizerRegistered();
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
index e380426..0415f12 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
@@ -51,18 +51,7 @@
     private final SparseArray<AppPair> mActiveAppPairs = new SparseArray<>();
     private final DisplayController mDisplayController;
 
-    /**
-     * Creates {@link AppPairs}, returns {@code null} if the feature is not supported.
-     */
-    @Nullable
-    public static AppPairs create(ShellTaskOrganizer organizer,
-            SyncTransactionQueue syncQueue, DisplayController displayController,
-            ShellExecutor mainExecutor) {
-        return new AppPairsController(organizer, syncQueue, displayController,
-                mainExecutor).mImpl;
-    }
-
-    AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
+    public AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
                 DisplayController displayController, ShellExecutor mainExecutor) {
         mTaskOrganizer = organizer;
         mSyncQueue = syncQueue;
@@ -70,18 +59,22 @@
         mMainExecutor = mainExecutor;
     }
 
-    void onOrganizerRegistered() {
+    public AppPairs asAppPairs() {
+        return mImpl;
+    }
+
+    public void onOrganizerRegistered() {
         if (mPairsPool == null) {
             setPairsPool(new AppPairsPool(this));
         }
     }
 
     @VisibleForTesting
-    void setPairsPool(AppPairsPool pool) {
+    public void setPairsPool(AppPairsPool pool) {
         mPairsPool = pool;
     }
 
-    boolean pair(int taskId1, int taskId2) {
+    public boolean pair(int taskId1, int taskId2) {
         final ActivityManager.RunningTaskInfo task1 = mTaskOrganizer.getRunningTaskInfo(taskId1);
         final ActivityManager.RunningTaskInfo task2 = mTaskOrganizer.getRunningTaskInfo(taskId2);
         if (task1 == null || task2 == null) {
@@ -90,13 +83,13 @@
         return pair(task1, task2);
     }
 
-    boolean pair(ActivityManager.RunningTaskInfo task1,
+    public boolean pair(ActivityManager.RunningTaskInfo task1,
             ActivityManager.RunningTaskInfo task2) {
         return pairInner(task1, task2) != null;
     }
 
     @VisibleForTesting
-    AppPair pairInner(
+    public AppPair pairInner(
             @NonNull ActivityManager.RunningTaskInfo task1,
             @NonNull ActivityManager.RunningTaskInfo task2) {
         final AppPair pair = mPairsPool.acquire();
@@ -109,11 +102,11 @@
         return pair;
     }
 
-    void unpair(int taskId) {
+    public void unpair(int taskId) {
         unpair(taskId, true /* releaseToPool */);
     }
 
-    void unpair(int taskId, boolean releaseToPool) {
+    public void unpair(int taskId, boolean releaseToPool) {
         AppPair pair = mActiveAppPairs.get(taskId);
         if (pair == null) {
             for (int i = mActiveAppPairs.size() - 1; i >= 0; --i) {
@@ -137,19 +130,19 @@
         }
     }
 
-    ShellTaskOrganizer getTaskOrganizer() {
+    public ShellTaskOrganizer getTaskOrganizer() {
         return mTaskOrganizer;
     }
 
-    SyncTransactionQueue getSyncTransactionQueue() {
+    public SyncTransactionQueue getSyncTransactionQueue() {
         return mSyncQueue;
     }
 
-    DisplayController getDisplayController() {
+    public DisplayController getDisplayController() {
         return mDisplayController;
     }
 
-    private void dump(@NonNull PrintWriter pw, String prefix) {
+    public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         final String childPrefix = innerPrefix + "  ";
         pw.println(prefix + this);
@@ -202,21 +195,5 @@
                 AppPairsController.this.unpair(taskId);
             });
         }
-
-        @Override
-        public void onOrganizerRegistered() {
-            mMainExecutor.execute(() -> {
-                AppPairsController.this.onOrganizerRegistered();
-            });
-        }
-
-        @Override
-        public void dump(@NonNull PrintWriter pw, String prefix) {
-            try {
-                mMainExecutor.executeBlocking(() -> AppPairsController.this.dump(pw, prefix));
-            } catch (InterruptedException e) {
-                Slog.e(TAG, "Failed to dump AppPairsController in 2s");
-            }
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d73fc6d..047df5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -86,6 +86,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
@@ -192,9 +193,9 @@
     private boolean mIsStatusBarShade = true;
 
     /**
-     * Injected constructor.
+     * Creates an instance of the BubbleController.
      */
-    public static Bubbles create(Context context,
+    public static BubbleController create(Context context,
             @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
             FloatingContentCoordinator floatingContentCoordinator,
             @Nullable IStatusBarService statusBarService,
@@ -211,14 +212,14 @@
         return new BubbleController(context, data, synchronizer, floatingContentCoordinator,
                 new BubbleDataRepository(context, launcherApps, mainExecutor),
                 statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
-                logger, organizer, positioner, mainExecutor, mainHandler).mImpl;
+                logger, organizer, positioner, mainExecutor, mainHandler);
     }
 
     /**
      * Testing constructor.
      */
     @VisibleForTesting
-    public BubbleController(Context context,
+    protected BubbleController(Context context,
             BubbleData data,
             @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -322,7 +323,7 @@
     }
 
     @VisibleForTesting
-    public Bubbles getImpl() {
+    public Bubbles asBubbles() {
         return mImpl;
     }
 
@@ -586,11 +587,15 @@
             // There were no bubbles saved for this used.
             return;
         }
-        for (BubbleEntry e : mSysuiProxy.getShouldRestoredEntries(savedBubbleKeys)) {
-            if (canLaunchInActivityView(mContext, e)) {
-                updateBubble(e, true /* suppressFlyout */, false /* showInShade */);
-            }
-        }
+        mSysuiProxy.getShouldRestoredEntries(savedBubbleKeys, (entries) -> {
+            mMainExecutor.execute(() -> {
+                for (BubbleEntry e : entries) {
+                    if (canLaunchInActivityView(mContext, e)) {
+                        updateBubble(e, true /* suppressFlyout */, false /* showInShade */);
+                    }
+                }
+            });
+        });
         // Finally, remove the entries for this user now that bubbles are restored.
         mSavedBubbleKeysPerUser.remove(mCurrentUserId);
     }
@@ -856,21 +861,24 @@
         }
     }
 
-    private void onRankingUpdated(RankingMap rankingMap) {
+    private void onRankingUpdated(RankingMap rankingMap,
+            HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) {
         if (mTmpRanking == null) {
             mTmpRanking = new NotificationListenerService.Ranking();
         }
         String[] orderedKeys = rankingMap.getOrderedKeys();
         for (int i = 0; i < orderedKeys.length; i++) {
             String key = orderedKeys[i];
-            BubbleEntry entry = mSysuiProxy.getPendingOrActiveEntry(key);
+            Pair<BubbleEntry, Boolean> entryData = entryDataByKey.get(key);
+            BubbleEntry entry = entryData.first;
+            boolean shouldBubbleUp = entryData.second;
             rankingMap.getRanking(key, mTmpRanking);
             boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
             if (isActiveBubble && !mTmpRanking.canBubble()) {
                 // If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason.
                 // This means that the app or channel's ability to bubble has been revoked.
                 mBubbleData.dismissBubbleWithKey(key, DISMISS_BLOCKED);
-            } else if (isActiveBubble && !mSysuiProxy.shouldBubbleUp(key)) {
+            } else if (isActiveBubble && !shouldBubbleUp) {
                 // If this entry is allowed to bubble, but cannot currently bubble up, dismiss it.
                 // This happens when DND is enabled and configured to hide bubbles. Dismissing with
                 // the reason DISMISS_NO_BUBBLE_UP will retain the underlying notification, so that
@@ -919,17 +927,20 @@
     private void setIsBubble(@NonNull final Bubble b, final boolean isBubble) {
         Objects.requireNonNull(b);
         b.setIsBubble(isBubble);
-        final BubbleEntry entry = mSysuiProxy.getPendingOrActiveEntry(b.getKey());
-        if (entry != null) {
-            // Updating the entry to be a bubble will trigger our normal update flow
-            setIsBubble(entry, isBubble, b.shouldAutoExpand());
-        } else if (isBubble) {
-            // If bubble doesn't exist, it's a persisted bubble so we need to add it to the
-            // stack ourselves
-            Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */);
-            inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */,
-                    !bubble.shouldAutoExpand() /* showInShade */);
-        }
+        mSysuiProxy.getPendingOrActiveEntry(b.getKey(), (entry) -> {
+            mMainExecutor.execute(() -> {
+                if (entry != null) {
+                    // Updating the entry to be a bubble will trigger our normal update flow
+                    setIsBubble(entry, isBubble, b.shouldAutoExpand());
+                } else if (isBubble) {
+                    // If bubble doesn't exist, it's a persisted bubble so we need to add it to the
+                    // stack ourselves
+                    Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */);
+                    inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */,
+                            !bubble.shouldAutoExpand() /* showInShade */);
+                }
+            });
+        });
     }
 
     @SuppressWarnings("FieldCanBeLocal")
@@ -992,14 +1003,17 @@
                     }
 
                 }
-                final BubbleEntry entry = mSysuiProxy.getPendingOrActiveEntry(bubble.getKey());
-                if (entry != null) {
-                    final String groupKey = entry.getStatusBarNotification().getGroupKey();
-                    if (getBubblesInGroup(groupKey).isEmpty()) {
-                        // Time to potentially remove the summary
-                        mSysuiProxy.notifyMaybeCancelSummary(bubble.getKey());
-                    }
-                }
+                mSysuiProxy.getPendingOrActiveEntry(bubble.getKey(), (entry) -> {
+                    mMainExecutor.execute(() -> {
+                        if (entry != null) {
+                            final String groupKey = entry.getStatusBarNotification().getGroupKey();
+                            if (getBubblesInGroup(groupKey).isEmpty()) {
+                                // Time to potentially remove the summary
+                                mSysuiProxy.notifyMaybeCancelSummary(bubble.getKey());
+                            }
+                        }
+                    });
+                });
             }
             mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository);
 
@@ -1121,23 +1135,6 @@
         mStackView.updateContentDescription();
     }
 
-    /**
-     * The task id of the expanded view, if the stack is expanded and not occluded by the
-     * status bar, otherwise returns {@link ActivityTaskManager#INVALID_TASK_ID}.
-     */
-    private int getExpandedTaskId() {
-        if (mStackView == null) {
-            return INVALID_TASK_ID;
-        }
-        final BubbleViewProvider expandedViewProvider = mStackView.getExpandedBubble();
-        if (expandedViewProvider != null && isStackExpanded()
-                && !mStackView.isExpansionAnimating()
-                && !mSysuiProxy.isNotificationShadeExpand()) {
-            return expandedViewProvider.getTaskId();
-        }
-        return INVALID_TASK_ID;
-    }
-
     @VisibleForTesting
     public BubbleStackView getStackView() {
         return mStackView;
@@ -1343,9 +1340,10 @@
         }
 
         @Override
-        public void onRankingUpdated(RankingMap rankingMap) {
+        public void onRankingUpdated(RankingMap rankingMap,
+                HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) {
             mMainExecutor.execute(() -> {
-                BubbleController.this.onRankingUpdated(rankingMap);
+                BubbleController.this.onRankingUpdated(rankingMap, entryDataByKey);
             });
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 6a1026b..8e061e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -26,6 +26,7 @@
 import android.os.Looper;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.util.ArraySet;
+import android.util.Pair;
 import android.view.View;
 
 import androidx.annotation.IntDef;
@@ -37,6 +38,7 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
+import java.util.HashMap;
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.function.BiConsumer;
@@ -182,8 +184,11 @@
      * permissions on the notification channel or the global setting.
      *
      * @param rankingMap the updated ranking map from NotificationListenerService
+     * @param entryDataByKey a map of ranking key to bubble entry and whether the entry should
+     *                       bubble up
      */
-    void onRankingUpdated(RankingMap rankingMap);
+    void onRankingUpdated(RankingMap rankingMap,
+            HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey);
 
     /**
      * Called when the status bar has become visible or invisible (either permanently or
@@ -243,14 +248,10 @@
 
     /** Callback to tell SysUi components execute some methods. */
     interface SysuiProxy {
-        @Nullable
-        BubbleEntry getPendingOrActiveEntry(String key);
+        void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback);
 
-        List<BubbleEntry> getShouldRestoredEntries(ArraySet<String> savedBubbleKeys);
-
-        boolean isNotificationShadeExpand();
-
-        boolean shouldBubbleUp(String key);
+        void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys,
+                Consumer<List<BubbleEntry>> callback);
 
         void setNotificationInterruption(String key);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
index 3a2f0da..60123ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
@@ -31,12 +31,6 @@
 public interface HideDisplayCutout {
     /**
      * Notifies {@link Configuration} changed.
-     * @param newConfig
      */
     void onConfigurationChanged(Configuration newConfig);
-
-    /**
-     * Dumps hide display cutout status.
-     */
-    void dump(@NonNull PrintWriter pw);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
index 12b8b87..23f76ca5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
@@ -44,20 +44,12 @@
     @VisibleForTesting
     boolean mEnabled;
 
-    HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer,
-            ShellExecutor mainExecutor) {
-        mContext = context;
-        mOrganizer = organizer;
-        mMainExecutor = mainExecutor;
-        updateStatus();
-    }
-
     /**
      * Creates {@link HideDisplayCutoutController}, returns {@code null} if the feature is not
      * supported.
      */
     @Nullable
-    public static HideDisplayCutout create(
+    public static HideDisplayCutoutController create(
             Context context, DisplayController displayController, ShellExecutor mainExecutor) {
         // The SystemProperty is set for devices that support this feature and is used to control
         // whether to create the HideDisplayCutout instance.
@@ -68,7 +60,19 @@
 
         HideDisplayCutoutOrganizer organizer =
                 new HideDisplayCutoutOrganizer(context, displayController, mainExecutor);
-        return new HideDisplayCutoutController(context, organizer, mainExecutor).mImpl;
+        return new HideDisplayCutoutController(context, organizer, mainExecutor);
+    }
+
+    HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer,
+            ShellExecutor mainExecutor) {
+        mContext = context;
+        mOrganizer = organizer;
+        mMainExecutor = mainExecutor;
+        updateStatus();
+    }
+
+    public HideDisplayCutout asHideDisplayCutout() {
+        return mImpl;
     }
 
     @VisibleForTesting
@@ -94,7 +98,7 @@
         updateStatus();
     }
 
-    private void dump(@NonNull PrintWriter pw) {
+    public void dump(@NonNull PrintWriter pw) {
         final String prefix = "  ";
         pw.print(TAG);
         pw.println(" states: ");
@@ -111,14 +115,5 @@
                 HideDisplayCutoutController.this.onConfigurationChanged(newConfig);
             });
         }
-
-        @Override
-        public void dump(@NonNull PrintWriter pw) {
-            try {
-                mMainExecutor.executeBlocking(() -> HideDisplayCutoutController.this.dump(pw));
-            } catch (InterruptedException e) {
-                Slog.e(TAG, "Failed to dump HideDisplayCutoutController in 2s");
-            }
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
index bca6deb..d25bef1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
@@ -115,21 +115,6 @@
     private volatile boolean mAdjustedForIme = false;
     private boolean mHomeStackResizable = false;
 
-    /**
-     * Creates {@link SplitScreen}, returns {@code null} if the feature is not supported.
-     */
-    @Nullable
-    public static LegacySplitScreen create(Context context,
-            DisplayController displayController, SystemWindows systemWindows,
-            DisplayImeController imeController, TransactionPool transactionPool,
-            ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
-            TaskStackListenerImpl taskStackListener, Transitions transitions,
-            ShellExecutor mainExecutor, AnimationHandler sfVsyncAnimationHandler) {
-        return new LegacySplitScreenController(context, displayController, systemWindows,
-                imeController, transactionPool, shellTaskOrganizer, syncQueue, taskStackListener,
-                transitions, mainExecutor, sfVsyncAnimationHandler).mImpl;
-    }
-
     public LegacySplitScreenController(Context context,
             DisplayController displayController, SystemWindows systemWindows,
             DisplayImeController imeController, TransactionPool transactionPool,
@@ -228,8 +213,12 @@
                     }
                 });
     }
+    
+    public LegacySplitScreen asLegacySplitScreen() {
+        return mImpl;
+    }
 
-    void onSplitScreenSupported() {
+    public void onSplitScreenSupported() {
         // Set starting tile bounds based on middle target
         final WindowContainerTransaction tct = new WindowContainerTransaction();
         int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
@@ -237,7 +226,7 @@
         mTaskOrganizer.applyTransaction(tct);
     }
 
-    private void onKeyguardVisibilityChanged(boolean showing) {
+    public void onKeyguardVisibilityChanged(boolean showing) {
         if (!isSplitActive() || mView == null) {
             return;
         }
@@ -293,19 +282,19 @@
         }
     }
 
-    boolean isMinimized() {
+    public boolean isMinimized() {
         return mMinimized;
     }
 
-    boolean isHomeStackResizable() {
+    public boolean isHomeStackResizable() {
         return mHomeStackResizable;
     }
 
-    DividerView getDividerView() {
+    public DividerView getDividerView() {
         return mView;
     }
 
-    boolean isDividerVisible() {
+    public boolean isDividerVisible() {
         return mView != null && mView.getVisibility() == View.VISIBLE;
     }
 
@@ -314,13 +303,13 @@
      * isDividerVisible because the divider is only visible once *everything* is in split mode
      * while this only cares if some things are (eg. while entering/exiting as well).
      */
-    private boolean isSplitActive() {
+    public boolean isSplitActive() {
         return mSplits.mPrimary != null && mSplits.mSecondary != null
                 && (mSplits.mPrimary.topActivityType != ACTIVITY_TYPE_UNDEFINED
                 || mSplits.mSecondary.topActivityType != ACTIVITY_TYPE_UNDEFINED);
     }
 
-    private void addDivider(Configuration configuration) {
+    public void addDivider(Configuration configuration) {
         Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId());
         mView = (DividerView)
                 LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);
@@ -338,14 +327,14 @@
         mWindowManager.add(mView, width, height, mContext.getDisplayId());
     }
 
-    private void removeDivider() {
+    public void removeDivider() {
         if (mView != null) {
             mView.onDividerRemoved();
         }
         mWindowManager.remove();
     }
 
-    private void update(Configuration configuration) {
+    public void update(Configuration configuration) {
         final boolean isDividerHidden = mView != null && mIsKeyguardShowing;
 
         removeDivider();
@@ -358,11 +347,11 @@
         mView.setHidden(isDividerHidden);
     }
 
-    void onTaskVanished() {
+    public void onTaskVanished() {
         removeDivider();
     }
 
-    private void updateVisibility(final boolean visible) {
+    public void updateVisibility(final boolean visible) {
         if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible);
         if (mVisible != visible) {
             mVisible = visible;
@@ -390,7 +379,7 @@
         }
     }
 
-    private void setMinimized(final boolean minimized) {
+    public void setMinimized(final boolean minimized) {
         if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible);
         mMainExecutor.execute(() -> {
             if (DEBUG) Slog.d(TAG, "run posted ext setMinimized " + minimized + " vis:" + mVisible);
@@ -401,7 +390,7 @@
         });
     }
 
-    private void setHomeMinimized(final boolean minimized) {
+    public void setHomeMinimized(final boolean minimized) {
         if (DEBUG) {
             Slog.d(TAG, "setHomeMinimized  min:" + mMinimized + "->" + minimized + " hrsz:"
                     + mHomeStackResizable + " split:" + isDividerVisible());
@@ -441,7 +430,7 @@
         }
     }
 
-    void setAdjustedForIme(boolean adjustedForIme) {
+    public void setAdjustedForIme(boolean adjustedForIme) {
         if (mAdjustedForIme == adjustedForIme) {
             return;
         }
@@ -449,30 +438,30 @@
         updateTouchable();
     }
 
-    private void updateTouchable() {
+    public void updateTouchable() {
         mWindowManager.setTouchable(!mAdjustedForIme);
     }
 
-    private void onUndockingTask() {
+    public void onUndockingTask() {
         if (mView != null) {
             mView.onUndockingTask();
         }
     }
 
-    private void onAppTransitionFinished() {
+    public void onAppTransitionFinished() {
         if (mView == null) {
             return;
         }
         mForcedResizableController.onAppTransitionFinished();
     }
 
-    private void dump(PrintWriter pw) {
+    public void dump(PrintWriter pw) {
         pw.print("  mVisible="); pw.println(mVisible);
         pw.print("  mMinimized="); pw.println(mMinimized);
         pw.print("  mAdjustedForIme="); pw.println(mAdjustedForIme);
     }
 
-    long getAnimDuration() {
+    public long getAnimDuration() {
         float transitionScale = Settings.Global.getFloat(mContext.getContentResolver(),
                 Settings.Global.TRANSITION_ANIMATION_SCALE,
                 mContext.getResources().getFloat(
@@ -482,14 +471,14 @@
         return (long) (transitionDuration * transitionScale);
     }
 
-    void registerInSplitScreenListener(Consumer<Boolean> listener) {
+    public void registerInSplitScreenListener(Consumer<Boolean> listener) {
         listener.accept(isDividerVisible());
         synchronized (mDockedStackExistsListeners) {
             mDockedStackExistsListeners.add(new WeakReference<>(listener));
         }
     }
 
-    void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
+    public void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
         synchronized (mDockedStackExistsListeners) {
             for (int i = mDockedStackExistsListeners.size() - 1; i >= 0; i--) {
                 if (mDockedStackExistsListeners.get(i) == listener) {
@@ -499,13 +488,13 @@
         }
     }
 
-    private void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
+    public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
         synchronized (mBoundsChangedListeners) {
             mBoundsChangedListeners.add(new WeakReference<>(listener));
         }
     }
 
-    private boolean splitPrimaryTask() {
+    public boolean splitPrimaryTask() {
         try {
             if (ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED
                     || isSplitActive()) {
@@ -538,12 +527,12 @@
                 topRunningTask.taskId, true /* onTop */);
     }
 
-    private void dismissSplitToPrimaryTask() {
+    public void dismissSplitToPrimaryTask() {
         startDismissSplit(true /* toPrimaryTask */);
     }
 
     /** Notifies the bounds of split screen changed. */
-    void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
+    public void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
         synchronized (mBoundsChangedListeners) {
             mBoundsChangedListeners.removeIf(wf -> {
                 BiConsumer<Rect, Rect> l = wf.get();
@@ -553,19 +542,19 @@
         }
     }
 
-    void startEnterSplit() {
+    public void startEnterSplit() {
         update(mDisplayController.getDisplayContext(
                 mContext.getDisplayId()).getResources().getConfiguration());
         // Set resizable directly here because applyEnterSplit already resizes home stack.
         mHomeStackResizable = mWindowManagerProxy.applyEnterSplit(mSplits, mSplitLayout);
     }
 
-    void prepareEnterSplitTransition(WindowContainerTransaction outWct) {
+    public void prepareEnterSplitTransition(WindowContainerTransaction outWct) {
         // Set resizable directly here because buildEnterSplit already resizes home stack.
         mHomeStackResizable = mWindowManagerProxy.buildEnterSplit(outWct, mSplits, mSplitLayout);
     }
 
-    void finishEnterSplitTransition(boolean minimized) {
+    public void finishEnterSplitTransition(boolean minimized) {
         update(mDisplayController.getDisplayContext(
                 mContext.getDisplayId()).getResources().getConfiguration());
         if (minimized) {
@@ -575,11 +564,11 @@
         }
     }
 
-    void startDismissSplit(boolean toPrimaryTask) {
+    public void startDismissSplit(boolean toPrimaryTask) {
         startDismissSplit(toPrimaryTask, false /* snapped */);
     }
 
-    void startDismissSplit(boolean toPrimaryTask, boolean snapped) {
+    public void startDismissSplit(boolean toPrimaryTask, boolean snapped) {
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             mSplits.getSplitTransitions().dismissSplit(
                     mSplits, mSplitLayout, !toPrimaryTask, snapped);
@@ -589,7 +578,7 @@
         }
     }
 
-    void onDismissSplit() {
+    public void onDismissSplit() {
         updateVisibility(false /* visible */);
         mMinimized = false;
         // Resets divider bar position to undefined, so new divider bar will apply default position
@@ -599,7 +588,7 @@
         mImePositionProcessor.reset();
     }
 
-    void ensureMinimizedSplit() {
+    public void ensureMinimizedSplit() {
         setHomeMinimized(true /* minimized */);
         if (mView != null && !isDividerVisible()) {
             // Wasn't in split-mode yet, so enter now.
@@ -610,7 +599,7 @@
         }
     }
 
-    void ensureNormalSplit() {
+    public void ensureNormalSplit() {
         setHomeMinimized(false /* minimized */);
         if (mView != null && !isDividerVisible()) {
             // Wasn't in split-mode, so enter now.
@@ -621,15 +610,15 @@
         }
     }
 
-    LegacySplitDisplayLayout getSplitLayout() {
+    public LegacySplitDisplayLayout getSplitLayout() {
         return mSplitLayout;
     }
 
-    WindowManagerProxy getWmProxy() {
+    public WindowManagerProxy getWmProxy() {
         return mWindowManagerProxy;
     }
 
-    WindowContainerToken getSecondaryRoot() {
+    public WindowContainerToken getSecondaryRoot() {
         if (mSplits == null || mSplits.mSecondary == null) {
             return null;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
index 94c6f01..c8f8987 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
@@ -39,7 +39,6 @@
 import android.window.TaskOrganizer;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
-import android.window.WindowOrganizer;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -116,7 +115,7 @@
     void applyResizeSplits(int position, LegacySplitDisplayLayout splitLayout) {
         WindowContainerTransaction t = new WindowContainerTransaction();
         splitLayout.resizeSplits(position, t);
-        new WindowOrganizer().applyTransaction(t);
+        applySyncTransaction(t);
     }
 
     boolean getHomeAndRecentsTasks(List<ActivityManager.RunningTaskInfo> out,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index e958648..11c11f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -69,9 +69,4 @@
      * 3 button navigation mode only
      */
     void registerGestureCallback(OneHandedGestureEventCallback callback);
-
-    /**
-     * Dump one handed status.
-     */
-    void dump(@NonNull PrintWriter pw);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index eaa704f..5a3c38b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -134,10 +134,10 @@
 
 
     /**
-     * Creates {@link OneHanded}, returns {@code null} if the feature is not supported.
+     * Creates {@link OneHandedController}, returns {@code null} if the feature is not supported.
      */
     @Nullable
-    public static OneHanded create(
+    public static OneHandedController create(
             Context context, DisplayController displayController,
             TaskStackListenerImpl taskStackListener, UiEventLogger uiEventLogger,
             ShellExecutor mainExecutor, Handler mainHandler) {
@@ -166,7 +166,7 @@
         return new OneHandedController(context, displayController,
                 oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler,
                 gestureHandler, timeoutHandler, oneHandedUiEventsLogger, overlayManager,
-                taskStackListener, mainExecutor, mainHandler).mImpl;
+                taskStackListener, mainExecutor, mainHandler);
     }
 
     @VisibleForTesting
@@ -228,6 +228,10 @@
                 mAccessibilityStateChangeListener);
     }
 
+    public OneHanded asOneHanded() {
+        return mImpl;
+    }
+
     /**
      * Set one handed enabled or disabled when user update settings
      */
@@ -468,7 +472,7 @@
         }
     }
 
-    private void dump(@NonNull PrintWriter pw) {
+    public void dump(@NonNull PrintWriter pw) {
         final String innerPrefix = "  ";
         pw.println(TAG + "states: ");
         pw.print(innerPrefix + "mOffSetFraction=");
@@ -561,12 +565,5 @@
                 OneHandedController.this.registerGestureCallback(callback);
             });
         }
-
-        @Override
-        public void dump(@NonNull PrintWriter pw) {
-            mMainExecutor.execute(() -> {
-                OneHandedController.this.dump(pw);
-            });
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index ad6f435..3064af6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -67,6 +67,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
 import com.android.wm.shell.transition.Transitions;
 
@@ -131,7 +132,7 @@
     private final PipUiEventLogger mPipUiEventLoggerLogger;
     private final int mEnterExitAnimationDuration;
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
-    private final Optional<LegacySplitScreen> mSplitScreenOptional;
+    private final Optional<LegacySplitScreenController> mSplitScreenOptional;
     protected final ShellTaskOrganizer mTaskOrganizer;
     protected final ShellExecutor mMainExecutor;
 
@@ -207,7 +208,7 @@
             @NonNull PipAnimationController pipAnimationController,
             @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
             @NonNull PipTransitionController pipTransitionController,
-            Optional<LegacySplitScreen> splitScreenOptional,
+            Optional<LegacySplitScreenController> splitScreenOptional,
             @NonNull DisplayController displayController,
             @NonNull PipUiEventLogger pipUiEventLogger,
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -1047,7 +1048,8 @@
     }
 
     /**
-     * Sync with {@link LegacySplitScreen} on destination bounds if PiP is going to split screen.
+     * Sync with {@link LegacySplitScreenController} on destination bounds if PiP is going to split
+     * screen.
      *
      * @param destinationBoundsOut contain the updated destination bounds if applicable
      * @return {@code true} if destinationBounds is altered for split screen
@@ -1057,7 +1059,7 @@
             return false;
         }
 
-        LegacySplitScreen legacySplitScreen = mSplitScreenOptional.get();
+        LegacySplitScreenController legacySplitScreen = mSplitScreenOptional.get();
         if (!legacySplitScreen.isDividerVisible()) {
             // fail early if system is not in split screen mode
             return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 53571ff..1ef9ffa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -558,8 +558,8 @@
                         || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
                     mLastResizeBounds.set(0, 0, mMaxSize.x, mMaxSize.y);
                 }
-                mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds,
-                        mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds()));
+                final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mLastResizeBounds);
+                mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
                 mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
                         PINCH_RESIZE_SNAP_DURATION, -mAngle, callback);
             } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
deleted file mode 100644
index 11f22ed..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.sizecompatui;
-
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.os.IBinder;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * Interface to engage size compat mode UI.
- */
-@ExternalThread
-public interface SizeCompatUI {
-    /**
-     * Called when the Task info changed. Creates and updates the restart button if there is an
-     * activity in size compat, or removes the restart button if there is no size compat activity.
-     *
-     * @param displayId display the task and activity are in.
-     * @param taskId task the activity is in.
-     * @param taskBounds task bounds to place the restart button in.
-     * @param sizeCompatActivity the size compat activity in the task. Can be {@code null} if the
-     *                           top activity in this Task is not in size compat.
-     * @param taskListener listener to handle the Task Surface placement.
-     */
-    void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
-            @Nullable IBinder sizeCompatActivity,
-            @Nullable ShellTaskOrganizer.TaskListener taskListener);
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
index 286c3b6..48ee86c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
@@ -48,7 +48,6 @@
     private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
 
     @VisibleForTesting
-    final SizeCompatUI mImpl = new SizeCompatUIImpl();
     private final Context mContext;
     private final ShellExecutor mMainExecutor;
     private final DisplayController mDisplayController;
@@ -57,17 +56,8 @@
     /** Only show once automatically in the process life. */
     private boolean mHasShownHint;
 
-    /** Creates the {@link SizeCompatUIController}. */
-    public static SizeCompatUI create(Context context,
-            DisplayController displayController,
-            DisplayImeController imeController,
-            ShellExecutor mainExecutor) {
-        return new SizeCompatUIController(context, displayController, imeController, mainExecutor)
-                .mImpl;
-    }
-
     @VisibleForTesting
-    SizeCompatUIController(Context context,
+    public SizeCompatUIController(Context context,
             DisplayController displayController,
             DisplayImeController imeController,
             ShellExecutor mainExecutor) {
@@ -79,7 +69,7 @@
         mImeController.addPositionProcessor(this);
     }
 
-    private void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
+    public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
             @Nullable IBinder sizeCompatActivity,
             @Nullable ShellTaskOrganizer.TaskListener taskListener) {
         // TODO Draw button on Task surface
@@ -177,15 +167,4 @@
         }
         return context;
     }
-
-    private class SizeCompatUIImpl implements SizeCompatUI {
-        @Override
-        public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
-                @Nullable IBinder sizeCompatActivity,
-                @Nullable ShellTaskOrganizer.TaskListener taskListener) {
-            mMainExecutor.execute(() ->
-                    SizeCompatUIController.this.onSizeCompatInfoChanged(displayId, taskId,
-                            taskBounds, sizeCompatActivity, taskListener));
-        }
-    }
 }
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 a6f44ef..b7fd3cb 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
@@ -43,6 +43,7 @@
 import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
 import static com.android.internal.policy.DecorView.getNavigationBarRect;
 
+import android.annotation.BinderThread;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManager.TaskDescription;
@@ -498,7 +499,7 @@
         }
     }
 
-    @ExternalThread
+    @BinderThread
     static class Window extends BaseIWindow {
         private TaskSnapshotWindow mOuter;
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
index 111362a..ecc066b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
@@ -24,7 +24,6 @@
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.BySelector
 import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
-import com.android.server.wm.flicker.helpers.closePipWindow
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow
 import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild
@@ -113,7 +112,7 @@
         if (isTelevision) {
             uiDevice.closeTvPipWindow()
         } else {
-            uiDevice.closePipWindow()
+            closePipWindow(WindowManagerStateHelper(mInstrumentation))
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
index a14b46e..d56ed02 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.wm.shell.flicker.helpers.FixedAppHelper
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
@@ -50,8 +48,7 @@
         @JvmStatic
         fun getParams(): List<Array<Any>> {
             val testApp = FixedAppHelper(instrumentation)
-            val baseConfig = getTransitionLaunch(eachRun = true)
-            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+            val testSpec = getTransition(eachRun = true) { configuration ->
                 setup {
                     eachRun {
                         testApp.launchViaIntent(wmHelper)
@@ -97,7 +94,7 @@
                     }
                 }
             }
-            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
                 testSpec, supportedRotations = listOf(Surface.ROTATION_0),
                 repetitions = 5)
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 99a40da..ff31ba7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
@@ -50,9 +48,8 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
-            val baseConfig = getTransitionLaunch(
-                eachRun = true, stringExtras = emptyMap())
-            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+            val testSpec = getTransition(eachRun = true,
+                stringExtras = emptyMap()) { configuration ->
                 transitions {
                     pipApp.clickEnterPipButton()
                     pipApp.expandPipWindow(wmHelper)
@@ -92,7 +89,7 @@
                 }
             }
 
-            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
                 testSpec, supportedRotations = listOf(Surface.ROTATION_0),
                 repetitions = 5)
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index 7576e24..f054e64 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.startRotation
@@ -48,8 +46,7 @@
         @JvmStatic
         fun getParams(): Collection<Array<Any>> {
             val imeApp = ImeAppHelper(instrumentation)
-            val baseConfig = getTransitionLaunch(eachRun = false)
-            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+            val testSpec = getTransition(eachRun = false) { configuration ->
                 setup {
                     test {
                         imeApp.launchViaIntent(wmHelper)
@@ -90,7 +87,7 @@
             }
 
             return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
-                baseConfig, testSpec, supportedRotations = listOf(Surface.ROTATION_0),
+                testSpec, supportedRotations = listOf(Surface.ROTATION_0),
                 repetitions = 5)
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index adab5e8..ade65ac 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.endRotation
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.setRotation
@@ -55,8 +53,7 @@
         @JvmStatic
         fun getParams(): Collection<Array<Any>> {
             val fixedApp = FixedAppHelper(instrumentation)
-            val baseConfig = getTransitionLaunch(eachRun = false)
-            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+            val testSpec = getTransition(eachRun = false) { configuration ->
                 setup {
                     test {
                         fixedApp.launchViaIntent(wmHelper)
@@ -112,8 +109,7 @@
             }
 
             return FlickerTestRunnerFactory.getInstance().buildRotationTest(instrumentation,
-                baseConfig, testSpec,
-                supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
+                testSpec, supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
                 repetitions = 5)
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
index 4b826ff..f2d5899 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.focusChanges
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
@@ -52,8 +50,7 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
-            val baseConfig = getTransitionLaunch(eachRun = true)
-            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+            val testSpec = getTransition(eachRun = true) { configuration ->
                 setup {
                     eachRun {
                         this.setRotation(configuration.startRotation)
@@ -110,7 +107,7 @@
                 }
             }
 
-            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
                 testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
index 62e8221..1b44377 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.focusChanges
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
@@ -52,8 +50,7 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
-            val baseConfig = getTransitionLaunch(eachRun = true)
-            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+            val testSpec = getTransition(eachRun = true) { configuration ->
                 setup {
                     eachRun {
                         this.setRotation(configuration.startRotation)
@@ -111,7 +108,7 @@
                 }
             }
 
-            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
                 testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt
index eb7bae1..b1e404e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt
@@ -22,8 +22,6 @@
 import android.view.Surface
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.closePipWindow
-import com.android.server.wm.flicker.helpers.hasPipWindow
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.repetitions
@@ -83,10 +81,6 @@
                 }
                 test {
                     removeAllTasksButHome()
-
-                    if (device.hasPipWindow()) {
-                        device.closePipWindow()
-                    }
                     pipApp.exit()
                 }
             }
@@ -98,11 +92,13 @@
      *
      * @param eachRun If the pip app should be launched in each run (otherwise only 1x per test)
      * @param stringExtras Arguments to pass to the PIP launch intent
+     * @param extraSpec Addicional segment of flicker specification
      */
     @JvmOverloads
-    fun getTransitionLaunch(
+    open fun getTransition(
         eachRun: Boolean,
-        stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true")
+        stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true"),
+        extraSpec: FlickerBuilder.(Bundle) -> Unit = {}
     ): FlickerBuilder.(Bundle) -> Unit {
         return { configuration ->
             setupAndTeardown(this, configuration)
@@ -135,6 +131,8 @@
                     removeAllTasksButHome()
                 }
             }
+
+            extraSpec(this, configuration)
         }
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 80ea9b9..176b33d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -51,7 +51,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
+import com.android.wm.shell.sizecompatui.SizeCompatUIController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -76,7 +76,7 @@
     @Mock
     private Context mContext;
     @Mock
-    private SizeCompatUI mSizeCompatUI;
+    private SizeCompatUIController mSizeCompatUI;
 
     ShellTaskOrganizer mOrganizer;
     private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 9430af9..d10c036 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -44,6 +44,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
 
 import org.junit.Before;
@@ -70,7 +71,7 @@
     @Mock private PipTransitionController mMockPipTransitionController;
     @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
     @Mock private PipUiEventLogger mMockPipUiEventLogger;
-    @Mock private Optional<LegacySplitScreen> mMockOptionalSplitScreen;
+    @Mock private Optional<LegacySplitScreenController> mMockOptionalSplitScreen;
     @Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
     private TestShellExecutor mMainExecutor;
     private PipBoundsState mPipBoundsState;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
index 98f01ff..0eb64e5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
@@ -86,7 +86,7 @@
         final Rect taskBounds = new Rect(0, 0, 1000, 2000);
 
         // Verify that the restart button is added with non-null size compat activity.
-        mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
                 mMockActivityToken, mMockTaskListener);
         mShellMainExecutor.flushAll();
 
@@ -94,7 +94,7 @@
         verify(mMockButton).updateLastTargetActivity(eq(mMockActivityToken));
 
         // Verify that the restart button is removed with null size compat activity.
-        mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, null, null, null);
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, null, null, null);
 
         mShellMainExecutor.flushAll();
         verify(mMockButton).remove();
@@ -104,7 +104,7 @@
     public void testChangeButtonVisibilityOnImeShowHide() {
         final int taskId = 12;
         final Rect taskBounds = new Rect(0, 0, 1000, 2000);
-        mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
                 mMockActivityToken, mMockTaskListener);
         mShellMainExecutor.flushAll();
 
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 1dc5cd9..2e4d7f62 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -239,14 +239,12 @@
 
 static jlong RuntimeShader_createShaderBuilder(JNIEnv* env, jobject, jstring sksl) {
     ScopedUtfChars strSksl(env, sksl);
-    auto result = SkRuntimeEffect::Make(SkString(strSksl.c_str()));
-    sk_sp<SkRuntimeEffect> effect = std::get<0>(result);
-    if (effect.get() == nullptr) {
-        const auto& err = std::get<1>(result);
-        doThrowIAE(env, err.c_str());
+    auto result = SkRuntimeEffect::Make(SkString(strSksl.c_str()), SkRuntimeEffect::Options{});
+    if (result.effect.get() == nullptr) {
+        doThrowIAE(env, result.errorText.c_str());
         return 0;
     }
-    return reinterpret_cast<jlong>(new SkRuntimeShaderBuilder(std::move(effect)));
+    return reinterpret_cast<jlong>(new SkRuntimeShaderBuilder(std::move(result.effect)));
 }
 
 static void SkRuntimeShaderBuilder_delete(SkRuntimeShaderBuilder* builder) {
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index 8f455fe..1842356 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -355,29 +355,41 @@
     env->SetStaticObjectField(cls, fid, typeface);
 }
 
+// Critical Native
+static jint Typeface_getFamilySize(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
+    return toTypeface(faceHandle)->fFontCollection->getFamilies().size();
+}
+
+// Critical Native
+static jlong Typeface_getFamily(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle, jint index) {
+    std::shared_ptr<minikin::FontFamily> family =
+            toTypeface(faceHandle)->fFontCollection->getFamilies()[index];
+    return reinterpret_cast<jlong>(new FontFamilyWrapper(std::move(family)));
+}
 
 ///////////////////////////////////////////////////////////////////////////////
 
 static const JNINativeMethod gTypefaceMethods[] = {
-    { "nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface },
-    { "nativeCreateFromTypefaceWithExactStyle", "(JIZ)J",
-            (void*)Typeface_createFromTypefaceWithExactStyle },
-    { "nativeCreateFromTypefaceWithVariation", "(JLjava/util/List;)J",
-            (void*)Typeface_createFromTypefaceWithVariation },
-    { "nativeCreateWeightAlias",  "(JI)J", (void*)Typeface_createWeightAlias },
-    { "nativeGetReleaseFunc",     "()J",  (void*)Typeface_getReleaseFunc },
-    { "nativeGetStyle",           "(J)I",  (void*)Typeface_getStyle },
-    { "nativeGetWeight",      "(J)I",  (void*)Typeface_getWeight },
-    { "nativeCreateFromArray",    "([JJII)J",
-                                           (void*)Typeface_createFromArray },
-    { "nativeSetDefault",         "(J)V",   (void*)Typeface_setDefault },
-    { "nativeGetSupportedAxes",   "(J)[I",  (void*)Typeface_getSupportedAxes },
-    { "nativeRegisterGenericFamily", "(Ljava/lang/String;J)V",
-          (void*)Typeface_registerGenericFamily },
-    { "nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces},
-    { "nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces},
-    { "nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V",
-          (void*)Typeface_forceSetStaticFinalField },
+        {"nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface},
+        {"nativeCreateFromTypefaceWithExactStyle", "(JIZ)J",
+         (void*)Typeface_createFromTypefaceWithExactStyle},
+        {"nativeCreateFromTypefaceWithVariation", "(JLjava/util/List;)J",
+         (void*)Typeface_createFromTypefaceWithVariation},
+        {"nativeCreateWeightAlias", "(JI)J", (void*)Typeface_createWeightAlias},
+        {"nativeGetReleaseFunc", "()J", (void*)Typeface_getReleaseFunc},
+        {"nativeGetStyle", "(J)I", (void*)Typeface_getStyle},
+        {"nativeGetWeight", "(J)I", (void*)Typeface_getWeight},
+        {"nativeCreateFromArray", "([JJII)J", (void*)Typeface_createFromArray},
+        {"nativeSetDefault", "(J)V", (void*)Typeface_setDefault},
+        {"nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes},
+        {"nativeRegisterGenericFamily", "(Ljava/lang/String;J)V",
+         (void*)Typeface_registerGenericFamily},
+        {"nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces},
+        {"nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces},
+        {"nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V",
+         (void*)Typeface_forceSetStaticFinalField},
+        {"nativeGetFamilySize", "(J)I", (void*)Typeface_getFamilySize},
+        {"nativeGetFamily", "(JI)J", (void*)Typeface_getFamily},
 };
 
 int register_android_graphics_Typeface(JNIEnv* env)
diff --git a/libs/incident/Android.bp b/libs/incident/Android.bp
index d291ec0..438a92e 100644
--- a/libs/incident/Android.bp
+++ b/libs/incident/Android.bp
@@ -95,7 +95,7 @@
     name: "libincident_test",
     test_config: "AndroidTest.xml",
     defaults: ["libincidentpriv_defaults"],
-    test_suites: ["device-tests", "mts"],
+    test_suites: ["device-tests", "mts-statsd"],
     compile_multilib: "both",
     multilib: {
         lib64: {
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index cf2f0f0..a7ed091 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -10,7 +10,7 @@
           "include-filter": "com.google.android.media.gts.WidevineGenericOpsTests"
         },
         {
-          "include-filter": "com.google.android.media.gts.WidevineYouTubePerformanceTests"
+          "include-filter": "com.google.android.media.gts.WidevineH264PlaybackTests"
         }
       ]
     }
diff --git a/media/java/android/media/DrmInitData.java b/media/java/android/media/DrmInitData.java
index 85b4ba5..3c48f8f 100644
--- a/media/java/android/media/DrmInitData.java
+++ b/media/java/android/media/DrmInitData.java
@@ -19,6 +19,7 @@
 import android.media.MediaDrm;
 
 import java.util.Arrays;
+import java.util.Objects;
 import java.util.UUID;
 
 /**
@@ -94,9 +95,9 @@
          * @param data The initialization data.
          */
         public SchemeInitData(@NonNull UUID uuid, @NonNull String mimeType, @NonNull byte[] data) {
-            this.uuid = uuid;
-            this.mimeType = mimeType;
-            this.data = data;
+            this.uuid = Objects.requireNonNull(uuid);
+            this.mimeType = Objects.requireNonNull(mimeType);
+            this.data = Objects.requireNonNull(data);
         }
 
         @Override
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 98b9ad8..740bc2d 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -2526,6 +2526,7 @@
          * Pauses TV program recording in the current recording session.
          *
          * @param params A set of extra parameters which might be handled with this event.
+         *        {@link TvRecordingClient#pauseRecording(Bundle)}.
          */
         void pauseRecording(@NonNull Bundle params) {
             if (mToken == null) {
@@ -2543,6 +2544,7 @@
          * Resumes TV program recording in the current recording session.
          *
          * @param params A set of extra parameters which might be handled with this event.
+         *        {@link TvRecordingClient#resumeRecording(Bundle)}.
          */
         void resumeRecording(@NonNull Bundle params) {
             if (mToken == null) {
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 65b64d7..4972529 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -180,6 +180,10 @@
         "libstagefright_foundation_headers",
     ],
 
+    // TunerService is a system service required for Tuner feature.
+    // TunerJNI is a client of TunerService so we build the dependency here.
+    required: ["mediatuner"],
+
     export_include_dirs: ["."],
 
     cflags: [
diff --git a/media/jni/tuner/ClientHelper.h b/media/jni/tuner/ClientHelper.h
index 185b2f6..508dccf 100644
--- a/media/jni/tuner/ClientHelper.h
+++ b/media/jni/tuner/ClientHelper.h
@@ -19,6 +19,7 @@
 
 #include <android/binder_parcel_utils.h>
 #include <android/hardware/tv/tuner/1.1/types.h>
+#include <utils/Log.h>
 
 using Status = ::ndk::ScopedAStatus;
 
@@ -37,6 +38,7 @@
         } else if (s.isOk()) {
             return Result::SUCCESS;
         }
+        ALOGE("Aidl exception code %s", s.getDescription().c_str());
         return Result::UNKNOWN_ERROR;
     }
 };
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index 8b4ca37..f618890 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -43,6 +43,7 @@
 using ::android::hardware::tv::tuner::V1_0::DemuxTpid;
 using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterSettings;
 using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterType;
+using ::android::hardware::tv::tuner::V1_1::DemuxFilterMonitorEvent;
 using ::android::hardware::tv::tuner::V1_1::ScramblingStatus;
 
 namespace android {
@@ -480,7 +481,7 @@
         case DemuxIpAddress::SrcIpAddress::hidl_discriminator::v4: {
             int size = ipAddr.srcIpAddress.v4().size();
             srcIpAddress.isIpV6 = false;
-            srcIpAddress.addr.resize(ipAddr.srcIpAddress.v4().size());
+            srcIpAddress.addr.resize(size);
             copy(&ipAddr.srcIpAddress.v4()[0], &ipAddr.srcIpAddress.v4()[size],
                     srcIpAddress.addr.begin());
             break;
@@ -493,8 +494,6 @@
                     srcIpAddress.addr.begin());
             break;
         }
-        default:
-            break;
     }
     switch (ipAddr.dstIpAddress.getDiscriminator()) {
         case DemuxIpAddress::DstIpAddress::hidl_discriminator::v4: {
@@ -513,8 +512,6 @@
                     dstIpAddress.addr.begin());
             break;
         }
-        default:
-            break;
     }
 }
 
@@ -696,8 +693,6 @@
             getHidlRestartEvent(filterEvents, eventExt);
             break;
         }
-        default:
-            break;
     }
 }
 
@@ -883,19 +878,18 @@
         DemuxFilterEventExt& eventExt) {
     auto monitor = filterEvents[0].get<TunerFilterEvent::monitor>();
     eventExt.events.resize(1);
+    DemuxFilterMonitorEvent monitorEvent;
     switch (monitor.getTag()) {
         case TunerFilterMonitorEvent::scramblingStatus: {
-            eventExt.events[0].monitorEvent().scramblingStatus(
-                    static_cast<ScramblingStatus>(monitor.scramblingStatus));
+            monitorEvent.scramblingStatus(static_cast<ScramblingStatus>(monitor.scramblingStatus));
+            eventExt.events[0].monitorEvent(monitorEvent);
             break;
         }
         case TunerFilterMonitorEvent::cid: {
-            eventExt.events[0].monitorEvent().cid(static_cast<uint32_t>(monitor.cid));
+            monitorEvent.cid(static_cast<uint32_t>(monitor.cid));
+            eventExt.events[0].monitorEvent(monitorEvent);
             break;
         }
-        default:
-            eventExt.events[0].noinit();
-            break;
     }
 }
 
diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp
index 3a00133..0613223 100644
--- a/media/jni/tuner/FrontendClient.cpp
+++ b/media/jni/tuner/FrontendClient.cpp
@@ -49,9 +49,12 @@
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtGuardInterval;
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtMode;
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtModulation;
+using ::android::hardware::tv::tuner::V1_0::FrontendModulationStatus;
 using ::android::hardware::tv::tuner::V1_0::FrontendScanAtsc3PlpInfo;
+using ::android::hardware::tv::tuner::V1_0::FrontendStatusAtsc3PlpInfo;
 using ::android::hardware::tv::tuner::V1_0::LnbVoltage;
 using ::android::hardware::tv::tuner::V1_1::Constant;
+using ::android::hardware::tv::tuner::V1_1::FrontendBandwidth;
 using ::android::hardware::tv::tuner::V1_1::FrontendCableTimeInterleaveMode;
 using ::android::hardware::tv::tuner::V1_1::FrontendDtmbBandwidth;
 using ::android::hardware::tv::tuner::V1_1::FrontendDtmbGuardInterval;
@@ -61,19 +64,22 @@
 using ::android::hardware::tv::tuner::V1_1::FrontendDvbcBandwidth;
 using ::android::hardware::tv::tuner::V1_1::FrontendDvbtConstellation;
 using ::android::hardware::tv::tuner::V1_1::FrontendDvbtTransmissionMode;
+using ::android::hardware::tv::tuner::V1_1::FrontendGuardInterval;
+using ::android::hardware::tv::tuner::V1_1::FrontendInterleaveMode;
 using ::android::hardware::tv::tuner::V1_1::FrontendModulation;
+using ::android::hardware::tv::tuner::V1_1::FrontendRollOff;
 using ::android::hardware::tv::tuner::V1_1::FrontendSpectralInversion;
+using ::android::hardware::tv::tuner::V1_1::FrontendTransmissionMode;
 using ::android::hardware::tv::tuner::V1_1::FrontendType;
 
 namespace android {
 
 /////////////// FrontendClient ///////////////////////
 
-FrontendClient::FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int id, int type) {
+FrontendClient::FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int type) {
     mTunerFrontend = tunerFrontend;
     mAidlCallback = NULL;
     mHidlCallback = NULL;
-    mId = id;
     mType = type;
 }
 
@@ -104,6 +110,11 @@
     mFrontend_1_1 = ::android::hardware::tv::tuner::V1_1::IFrontend::castFrom(mFrontend);
 }
 
+// TODO: move after migration is done
+void FrontendClient::setId(int id) {
+    mId = id;
+}
+
 Result FrontendClient::tune(const FrontendSettings& settings,
         const FrontendSettingsExt1_1& settingsExt1_1) {
     if (mTunerFrontend != NULL) {
@@ -333,13 +344,26 @@
 }
 
 int FrontendClient::getId() {
-    return mId;
+    if (mTunerFrontend != NULL) {
+        Status s = mTunerFrontend->getFrontendId(&mId);
+        if (ClientHelper::getServiceSpecificErrorCode(s) == Result::SUCCESS) {
+            return mId;
+        }
+        ALOGE("Failed to getFrontendId from Tuner Frontend");
+        return -1;
+    }
+
+    if (mFrontend != NULL) {
+        return mId;
+    }
+
+    return -1;
 }
 
 vector<FrontendStatus> FrontendClient::getHidlStatus(vector<TunerFrontendStatus>& aidlStatus) {
     vector<FrontendStatus> hidlStatus;
     for (TunerFrontendStatus s : aidlStatus) {
-        FrontendStatus status;
+        FrontendStatus status = FrontendStatus();
         switch (s.getTag()) {
             case TunerFrontendStatus::isDemodLocked: {
                 status.isDemodLocked(s.get<TunerFrontendStatus::isDemodLocked>());
@@ -389,25 +413,31 @@
             }
             case TunerFrontendStatus::modulation: {
                 auto aidlMod = s.get<TunerFrontendStatus::modulation>();
+                FrontendModulationStatus modulation;
                 switch (mType) {
                     case (int)FrontendType::DVBC:
-                        status.modulation().dvbc(static_cast<FrontendDvbcModulation>(aidlMod));
+                        modulation.dvbc(static_cast<FrontendDvbcModulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DVBS:
-                        status.modulation().dvbs(static_cast<FrontendDvbsModulation>(aidlMod));
+                        modulation.dvbs(static_cast<FrontendDvbsModulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBS:
-                        status.modulation().isdbs(static_cast<FrontendIsdbsModulation>(aidlMod));
+                        modulation.isdbs(static_cast<FrontendIsdbsModulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBS3:
-                        status.modulation().isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod));
+                        modulation.isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBT:
-                        status.modulation().isdbt(static_cast<FrontendIsdbtModulation>(aidlMod));
+                        modulation.isdbt(static_cast<FrontendIsdbtModulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     default:
@@ -466,7 +496,7 @@
             }
             case TunerFrontendStatus::hierarchy: {
                 status.hierarchy(static_cast<FrontendDvbtHierarchy>(
-                        s.get<TunerFrontendStatus::freqOffset>()));
+                        s.get<TunerFrontendStatus::hierarchy>()));
                 hidlStatus.push_back(status);
                 break;
             }
@@ -477,15 +507,16 @@
             }
             case TunerFrontendStatus::plpInfo: {
                 int size = s.get<TunerFrontendStatus::plpInfo>().size();
-                status.plpInfo().resize(size);
+                hidl_vec<FrontendStatusAtsc3PlpInfo> info(size);
                 for (int i = 0; i < size; i++) {
                     auto aidlInfo = s.get<TunerFrontendStatus::plpInfo>()[i];
-                    status.plpInfo()[i] = {
+                    info[i] = {
                         .plpId = (uint8_t)aidlInfo.plpId,
                         .isLocked = aidlInfo.isLocked,
                         .uec = (uint32_t)aidlInfo.uec,
                     };
                 }
+                status.plpInfo(info);
                 hidlStatus.push_back(status);
                 break;
             }
@@ -503,52 +534,54 @@
         FrontendStatusExt1_1 status;
         switch (s.getTag()) {
             case TunerFrontendStatus::modulations: {
+                vector<FrontendModulation> ms;
                 for (auto aidlMod : s.get<TunerFrontendStatus::modulations>()) {
-                    int size = status.modulations().size();
-                    status.modulations().resize(size + 1);
+                    FrontendModulation m;
                     switch (mType) {
                         case (int)FrontendType::DVBC:
-                            status.modulations()[size].dvbc(
-                                    static_cast<FrontendDvbcModulation>(aidlMod));
+                            m.dvbc(static_cast<FrontendDvbcModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::DVBS:
-                            status.modulations()[size].dvbs(
-                                    static_cast<FrontendDvbsModulation>(aidlMod));
+                            m.dvbs(static_cast<FrontendDvbsModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::DVBT:
-                            status.modulations()[size].dvbt(
-                                    static_cast<FrontendDvbtConstellation>(aidlMod));
+                            m.dvbt(static_cast<FrontendDvbtConstellation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ISDBS:
-                            status.modulations()[size].isdbs(
-                                    static_cast<FrontendIsdbsModulation>(aidlMod));
+                            m.isdbs(static_cast<FrontendIsdbsModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ISDBS3:
-                            status.modulations()[size].isdbs3(
-                                    static_cast<FrontendIsdbs3Modulation>(aidlMod));
+                            m.isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ISDBT:
-                            status.modulations()[size].isdbt(
-                                    static_cast<FrontendIsdbtModulation>(aidlMod));
+                            m.isdbt(static_cast<FrontendIsdbtModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ATSC:
-                            status.modulations()[size].atsc(
-                                    static_cast<FrontendAtscModulation>(aidlMod));
+                            m.atsc(static_cast<FrontendAtscModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ATSC3:
-                            status.modulations()[size].atsc3(
-                                    static_cast<FrontendAtsc3Modulation>(aidlMod));
+                            m.atsc3(static_cast<FrontendAtsc3Modulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::DTMB:
-                            status.modulations()[size].dtmb(
-                                    static_cast<FrontendDtmbModulation>(aidlMod));
+                            m.dtmb(static_cast<FrontendDtmbModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         default:
-                            status.modulations().resize(size);
                             break;
                     }
                 }
-                hidlStatus.push_back(status);
+                if (ms.size() > 0) {
+                    status.modulations(ms);
+                    hidlStatus.push_back(status);
+                }
                 break;
             }
             case TunerFrontendStatus::bers: {
@@ -571,25 +604,31 @@
             }
             case TunerFrontendStatus::bandwidth: {
                 auto aidlBand = s.get<TunerFrontendStatus::bandwidth>();
+                FrontendBandwidth band;
                 switch (mType) {
                     case (int)FrontendType::ATSC3:
-                        status.bandwidth().atsc3(static_cast<FrontendAtsc3Bandwidth>(aidlBand));
+                        band.atsc3(static_cast<FrontendAtsc3Bandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DVBC:
-                        status.bandwidth().dvbc(static_cast<FrontendDvbcBandwidth>(aidlBand));
+                        band.dvbc(static_cast<FrontendDvbcBandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DVBT:
-                        status.bandwidth().dvbt(static_cast<FrontendDvbtBandwidth>(aidlBand));
+                        band.dvbt(static_cast<FrontendDvbtBandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBT:
-                        status.bandwidth().isdbt(static_cast<FrontendIsdbtBandwidth>(aidlBand));
+                        band.isdbt(static_cast<FrontendIsdbtBandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DTMB:
-                        status.bandwidth().dtmb(static_cast<FrontendDtmbBandwidth>(aidlBand));
+                        band.dtmb(static_cast<FrontendDtmbBandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     default:
@@ -599,17 +638,21 @@
             }
             case TunerFrontendStatus::interval: {
                 auto aidlInter = s.get<TunerFrontendStatus::interval>();
+                FrontendGuardInterval inter;
                 switch (mType) {
                     case (int)FrontendType::DVBT:
-                        status.interval().dvbt(static_cast<FrontendDvbtGuardInterval>(aidlInter));
+                        inter.dvbt(static_cast<FrontendDvbtGuardInterval>(aidlInter));
+                        status.interval(inter);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBT:
-                        status.interval().isdbt(static_cast<FrontendIsdbtGuardInterval>(aidlInter));
+                        inter.isdbt(static_cast<FrontendIsdbtGuardInterval>(aidlInter));
+                        status.interval(inter);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DTMB:
-                        status.interval().dtmb(static_cast<FrontendDtmbGuardInterval>(aidlInter));
+                        inter.dtmb(static_cast<FrontendDtmbGuardInterval>(aidlInter));
+                        status.interval(inter);
                         hidlStatus.push_back(status);
                         break;
                     default:
@@ -619,19 +662,21 @@
             }
             case TunerFrontendStatus::transmissionMode: {
                 auto aidlTran = s.get<TunerFrontendStatus::transmissionMode>();
+                FrontendTransmissionMode trans;
                 switch (mType) {
                     case (int)FrontendType::DVBT:
-                        status.transmissionMode().dvbt(
-                                static_cast<FrontendDvbtTransmissionMode>(aidlTran));
+                        trans.dvbt(static_cast<FrontendDvbtTransmissionMode>(aidlTran));
+                        status.transmissionMode(trans);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBT:
-                        status.transmissionMode().isdbt(static_cast<FrontendIsdbtMode>(aidlTran));
+                        trans.isdbt(static_cast<FrontendIsdbtMode>(aidlTran));
+                        status.transmissionMode(trans);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DTMB:
-                        status.transmissionMode().dtmb(
-                                static_cast<FrontendDtmbTransmissionMode>(aidlTran));
+                        trans.dtmb(static_cast<FrontendDtmbTransmissionMode>(aidlTran));
+                        status.transmissionMode(trans);
                         hidlStatus.push_back(status);
                         break;
                     default:
@@ -650,28 +695,30 @@
                 break;
             }
             case TunerFrontendStatus::interleaving: {
+                vector<FrontendInterleaveMode> modes;
                 for (auto aidlInter : s.get<TunerFrontendStatus::interleaving>()) {
-                    int size = status.interleaving().size();
-                    status.interleaving().resize(size + 1);
+                    FrontendInterleaveMode mode;
                     switch (mType) {
                         case (int)FrontendType::DVBC:
-                            status.interleaving()[size].dvbc(
-                                    static_cast<FrontendCableTimeInterleaveMode>(aidlInter));
+                            mode.dvbc(static_cast<FrontendCableTimeInterleaveMode>(aidlInter));
+                            modes.push_back(mode);
                             break;
                         case (int)FrontendType::ATSC3:
-                            status.interleaving()[size].atsc3(
-                                    static_cast<FrontendAtsc3TimeInterleaveMode>(aidlInter));
+                            mode.atsc3(static_cast<FrontendAtsc3TimeInterleaveMode>(aidlInter));
+                            modes.push_back(mode);
                             break;
                         case (int)FrontendType::DTMB:
-                            status.interleaving()[size].dtmb(
-                                    static_cast<FrontendDtmbTimeInterleaveMode>(aidlInter));
+                            mode.dtmb(static_cast<FrontendDtmbTimeInterleaveMode>(aidlInter));
+                            modes.push_back(mode);
                             break;
                         default:
-                            status.interleaving().resize(size);
                             break;
                     }
                 }
-                hidlStatus.push_back(status);
+                if (modes.size() > 0) {
+                    status.interleaving(modes);
+                    hidlStatus.push_back(status);
+                }
                 break;
             }
             case TunerFrontendStatus::isdbtSegment: {
@@ -690,17 +737,21 @@
             }
             case TunerFrontendStatus::rollOff: {
                 auto aidlRoll = s.get<TunerFrontendStatus::rollOff>();
+                FrontendRollOff roll;
                 switch (mType) {
                     case (int)FrontendType::DVBS:
-                        status.rollOff().dvbs(static_cast<FrontendDvbsRolloff>(aidlRoll));
+                        roll.dvbs(static_cast<FrontendDvbsRolloff>(aidlRoll));
+                        status.rollOff(roll);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBS:
-                        status.rollOff().isdbs(static_cast<FrontendIsdbsRolloff>(aidlRoll));
+                        roll.isdbs(static_cast<FrontendIsdbsRolloff>(aidlRoll));
+                        status.rollOff(roll);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBS3:
-                        status.rollOff().isdbs3(static_cast<FrontendIsdbs3Rolloff>(aidlRoll));
+                        roll.isdbs3(static_cast<FrontendIsdbs3Rolloff>(aidlRoll));
+                        status.rollOff(roll);
                         hidlStatus.push_back(status);
                         break;
                     default:
diff --git a/media/jni/tuner/FrontendClient.h b/media/jni/tuner/FrontendClient.h
index 298b397..f71616c 100644
--- a/media/jni/tuner/FrontendClient.h
+++ b/media/jni/tuner/FrontendClient.h
@@ -108,7 +108,7 @@
 struct FrontendClient : public RefBase {
 
 public:
-    FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int id, int type);
+    FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int type);
     ~FrontendClient();
 
     /**
@@ -180,6 +180,7 @@
 
     shared_ptr<ITunerFrontend> getAidlFrontend();
 
+    void setId(int id);
     int getId();
 
 private:
diff --git a/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp
index 7f954b5..cf17ed6 100644
--- a/media/jni/tuner/TunerClient.cpp
+++ b/media/jni/tuner/TunerClient.cpp
@@ -46,13 +46,12 @@
     // Connect with Tuner Service.
     ::ndk::SpAIBinder binder(AServiceManager_getService("media.tuner"));
     mTunerService = ITunerService::fromBinder(binder);
-    // TODO: Remove after JNI migration is done.
-    mTunerService = NULL;
     if (mTunerService == NULL) {
         ALOGE("Failed to get tuner service");
     } else {
         // TODO: b/178124017 update TRM in TunerService independently.
         mTunerService->updateTunerResources();
+        mTunerService->getTunerHalVersion(&mTunerVersion);
     }
 }
 
@@ -115,7 +114,7 @@
         if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) {
             return NULL;
         }
-        return new FrontendClient(tunerFrontend, frontendHandle, aidlFrontendInfo.type);
+        return new FrontendClient(tunerFrontend, aidlFrontendInfo.type);
     }
 
     if (mTuner != NULL) {
@@ -127,8 +126,10 @@
             if (res != Result::SUCCESS) {
                 return NULL;
             }
-            sp<FrontendClient> frontendClient = new FrontendClient(NULL, id, (int)hidlInfo.type);
+            sp<FrontendClient> frontendClient = new FrontendClient(
+                    NULL, (int)hidlInfo.type);
             frontendClient->setHidlFrontend(hidlFrontend);
+            frontendClient->setId(id);
             return frontendClient;
         }
     }
@@ -358,7 +359,7 @@
 
 sp<ITuner> TunerClient::getHidlTuner() {
     if (mTuner == NULL) {
-        mTunerVersion = 0;
+        mTunerVersion = TUNER_HAL_VERSION_UNKNOWN;
         mTuner_1_1 = ::android::hardware::tv::tuner::V1_1::ITuner::getService();
 
         if (mTuner_1_1 == NULL) {
@@ -367,11 +368,11 @@
             if (mTuner == NULL) {
                 ALOGW("Failed to get tuner 1.0 service.");
             } else {
-                mTunerVersion = 1 << 16;
+                mTunerVersion = TUNER_HAL_VERSION_1_0;
             }
         } else {
             mTuner = static_cast<sp<ITuner>>(mTuner_1_1);
-            mTunerVersion = ((1 << 16) | 1);
+            mTunerVersion = TUNER_HAL_VERSION_1_1;
          }
      }
      return mTuner;
diff --git a/media/jni/tuner/TunerClient.h b/media/jni/tuner/TunerClient.h
index 744bf20..9671cf7 100644
--- a/media/jni/tuner/TunerClient.h
+++ b/media/jni/tuner/TunerClient.h
@@ -48,6 +48,10 @@
 
 namespace android {
 
+const static int TUNER_HAL_VERSION_UNKNOWN = 0;
+const static int TUNER_HAL_VERSION_1_0 = 1 << 16;
+const static int TUNER_HAL_VERSION_1_1 = (1 << 16) | 1;
+
 typedef enum {
     FRONTEND,
     LNB,
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 314bf29..7a18bd5 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -225,6 +225,7 @@
     AStorageManager_unmountObb;
     ASurfaceControl_create; # introduced=29
     ASurfaceControl_createFromWindow; # introduced=29
+    ASurfaceControl_acquire; # introduced=31
     ASurfaceControl_release; # introduced=29
     ASurfaceTexture_acquireANativeWindow; # introduced=28
     ASurfaceTexture_attachToGLContext; # introduced=28
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 189be80..c1b5f1d 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -185,10 +185,16 @@
     return reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
 }
 
-void ASurfaceControl_release(ASurfaceControl* aSurfaceControl) {
-    sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+void ASurfaceControl_acquire(ASurfaceControl* aSurfaceControl) {
+    SurfaceControl* surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
 
-    SurfaceControl_release(surfaceControl.get());
+    SurfaceControl_acquire(surfaceControl);
+}
+
+void ASurfaceControl_release(ASurfaceControl* aSurfaceControl) {
+    SurfaceControl* surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+
+    SurfaceControl_release(surfaceControl);
 }
 
 ASurfaceTransaction* ASurfaceTransaction_create() {
diff --git a/packages/PackageInstaller/OWNERS b/packages/PackageInstaller/OWNERS
index 252670a..8e1774b 100644
--- a/packages/PackageInstaller/OWNERS
+++ b/packages/PackageInstaller/OWNERS
@@ -1,5 +1,4 @@
 svetoslavganov@google.com
-moltmann@google.com
 toddke@google.com
 suprabh@google.com
 
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_call_strength_1.xml b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_1.xml
new file mode 100644
index 0000000..46e2d45
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_1.xml
@@ -0,0 +1,35 @@
+<!--
+     Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M13,8h2v3h-2z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16.5,5h2v6h-2z"
+        android:fillAlpha="0.3"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20,3h2v8h-2z"
+        android:fillAlpha="0.3"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_call_strength_2.xml b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_2.xml
new file mode 100644
index 0000000..d9cd590
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_2.xml
@@ -0,0 +1,34 @@
+<!--
+     Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M13,8h2v3h-2z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16.5,5h2v6h-2z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20,3h2v8h-2z"
+        android:fillAlpha="0.3"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_call_strength_3.xml b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_3.xml
new file mode 100644
index 0000000..e80fd08
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_3.xml
@@ -0,0 +1,33 @@
+<!--
+     Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M13,8h2v3h-2z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16.5,5h2v6h-2z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20,3h2v8h-2z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_call_strength_1.xml b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_1.xml
new file mode 100644
index 0000000..493912b
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_1.xml
@@ -0,0 +1,35 @@
+<!--
+     Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M18.05,9.59C17.69,9.22 17.19,9 16.64,9c-0.55,0 -1.05,0.22 -1.41,0.59L16.64,11L18.05,9.59z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16.64,7.5c0.96,0 1.84,0.39 2.47,1.03l1.42,-1.42c-1,-1 -2.37,-1.61 -3.89,-1.61c-1.52,0 -2.89,0.62 -3.89,1.61l1.42,1.42C14.8,7.89 15.67,7.5 16.64,7.5z"
+        android:fillAlpha="0.3"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16.64,4c1.93,0 3.68,0.79 4.95,2.05L23,4.64C21.37,3.01 19.12,2 16.64,2c-2.49,0 -4.74,1.01 -6.36,2.64l1.42,1.42C12.96,4.79 14.71,4 16.64,4z"
+        android:fillAlpha="0.3"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_call_strength_2.xml b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_2.xml
new file mode 100644
index 0000000..af677fb
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_2.xml
@@ -0,0 +1,34 @@
+<!--
+     Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M18.05,9.59C17.69,9.22 17.19,9 16.64,9c-0.55,0 -1.05,0.22 -1.41,0.59L16.64,11L18.05,9.59z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16.64,7.5c0.96,0 1.84,0.39 2.47,1.03l1.42,-1.42c-1,-1 -2.37,-1.61 -3.89,-1.61c-1.52,0 -2.89,0.62 -3.89,1.61l1.42,1.42C14.8,7.89 15.67,7.5 16.64,7.5z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16.64,4c1.93,0 3.68,0.79 4.95,2.05L23,4.64C21.37,3.01 19.12,2 16.64,2c-2.49,0 -4.74,1.01 -6.36,2.64l1.42,1.42C12.96,4.79 14.71,4 16.64,4z"
+        android:fillAlpha="0.3"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_call_strength_3.xml b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_3.xml
new file mode 100644
index 0000000..68b39da
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_3.xml
@@ -0,0 +1,33 @@
+<!--
+     Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M18.05,9.59C17.69,9.22 17.19,9 16.64,9c-0.55,0 -1.05,0.22 -1.41,0.59L16.64,11L18.05,9.59z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16.64,7.5c0.96,0 1.84,0.39 2.47,1.03l1.42,-1.42c-1,-1 -2.37,-1.61 -3.89,-1.61c-1.52,0 -2.89,0.62 -3.89,1.61l1.42,1.42C14.8,7.89 15.67,7.5 16.64,7.5z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16.64,4c1.93,0 3.68,0.79 4.95,2.05L23,4.64C21.37,3.01 19.12,2 16.64,2c-2.49,0 -4.74,1.01 -6.36,2.64l1.42,1.42C12.96,4.79 14.71,4 16.64,4z"/>
+</vector>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 7556ace..3866151 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1380,7 +1380,7 @@
     <!-- Label for adding a new guest in the user switcher [CHAR LIMIT=35] -->
     <string name="guest_new_guest">Add guest</string>
     <!-- Label for exiting and removing the guest session in the user switcher [CHAR LIMIT=35] -->
-    <string name="guest_exit_guest">End guest session</string>
+    <string name="guest_exit_guest">Remove guest</string>
     <!-- Name for the guest user [CHAR LIMIT=35] -->
     <string name="guest_nickname">Guest</string>
 
@@ -1485,4 +1485,7 @@
     <string name="accessibility_ethernet_disconnected">Ethernet disconnected.</string>
     <!-- Content description of the Ethernet connection when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_ethernet_connected">Ethernet.</string>
+
+    <!-- Content description of the no calling for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_no_calling">No calling.</string>
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
index 45028ff..eff9e74 100644
--- a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
@@ -48,6 +48,8 @@
 
     public static final int WIFI_NO_CONNECTION = R.string.accessibility_no_wifi;
 
+    public static final int NO_CALLING = R.string.accessibility_no_calling;
+
     public static final int[] ETHERNET_CONNECTION_VALUES = {
         R.string.accessibility_ethernet_disconnected,
         R.string.accessibility_ethernet_connected,
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
index 09f285b..14a7cfa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
@@ -65,7 +65,7 @@
                 return toIconKey(TelephonyManager.NETWORK_TYPE_LTE) + "_CA_Plus";
             case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA:
                 return toIconKey(TelephonyManager.NETWORK_TYPE_NR);
-            case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE:
+            case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED:
                 return toIconKey(TelephonyManager.NETWORK_TYPE_NR) + "_Plus";
             default:
                 return "unsupported";
@@ -180,7 +180,7 @@
                 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA),
                 TelephonyIcons.NR_5G);
         networkToIconLookup.put(toDisplayIconKey(
-                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE),
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED),
                 TelephonyIcons.NR_5G_PLUS);
         networkToIconLookup.put(toIconKey(
                 TelephonyManager.NETWORK_TYPE_NR),
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
index 4c7b898..0cd5e4d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
@@ -266,7 +266,7 @@
                                         serviceState.getDataRegState()) + ")")
                                         .append(',')
                 .append("signalStrength=").append(signalStrength == null ? ""
-                        : signalStrength.toString()).append(',')
+                        : signalStrength.getLevel()).append(',')
                 .append("telephonyDisplayInfo=").append(telephonyDisplayInfo == null ? ""
                         : telephonyDisplayInfo.toString()).append(']').toString();
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
index 0cb9906..e3413aa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
@@ -317,5 +317,21 @@
         ICON_NAME_TO_ICON.put("datadisable", DATA_DISABLED);
         ICON_NAME_TO_ICON.put("notdefaultdata", NOT_DEFAULT_DATA);
     }
+
+    public static final int[] WIFI_CALL_STRENGTH_ICONS = {
+        R.drawable.ic_wifi_call_strength_1,
+        R.drawable.ic_wifi_call_strength_1,
+        R.drawable.ic_wifi_call_strength_2,
+        R.drawable.ic_wifi_call_strength_3,
+        R.drawable.ic_wifi_call_strength_3
+    };
+
+    public static final int[] MOBILE_CALL_STRENGTH_ICONS = {
+        R.drawable.ic_mobile_call_strength_1,
+        R.drawable.ic_mobile_call_strength_1,
+        R.drawable.ic_mobile_call_strength_2,
+        R.drawable.ic_mobile_call_strength_3,
+        R.drawable.ic_mobile_call_strength_3
+    };
 }
 
diff --git a/packages/Shell/OWNERS b/packages/Shell/OWNERS
index 6ba1fcb..34901f5 100644
--- a/packages/Shell/OWNERS
+++ b/packages/Shell/OWNERS
@@ -6,7 +6,6 @@
 svetoslavganov@google.com
 hackbod@google.com
 yamasani@google.com
-moltmann@google.com
 toddke@google.com
 cbrubaker@google.com
 omakoto@google.com
diff --git a/packages/SystemUI/res/color/kg_user_avatar_frame.xml b/packages/SystemUI/res/color/kg_user_avatar_frame.xml
new file mode 100644
index 0000000..174981e
--- /dev/null
+++ b/packages/SystemUI/res/color/kg_user_avatar_frame.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_activated="true"
+        android:color="@color/kg_user_switcher_avatar_background" />
+    <item android:color="@color/kg_user_switcher_avatar_background" />
+</selector>
diff --git a/packages/SystemUI/res/drawable/end_guest_button_background.xml b/packages/SystemUI/res/drawable/end_guest_button_background.xml
new file mode 100644
index 0000000..5644b65
--- /dev/null
+++ b/packages/SystemUI/res/drawable/end_guest_button_background.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <stroke
+        android:width="@dimen/end_guest_button_border_size"
+        android:color="?android:attr/colorControlHighlight" />
+    <corners android:radius="@dimen/end_guest_button_corner_radius" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/kg_bg_avatar.xml b/packages/SystemUI/res/drawable/kg_bg_avatar.xml
new file mode 100644
index 0000000..addb3f7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/kg_bg_avatar.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="48dp"
+        android:viewportWidth="100"
+        android:viewportHeight="100">
+
+    <path
+        android:fillColor="@color/kg_user_switcher_avatar_background"
+        android:pathData="M50,50m-50,0a50,50 0,1 1,100 0a50,50 0,1 1,-100 0"/>
+
+</vector>
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index 416ee81..2789ed1 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -43,17 +43,12 @@
             <include layout="@layout/system_icons" />
         </FrameLayout>
 
-        <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch"
-            android:layout_width="@dimen/multi_user_switch_width_keyguard"
-            android:layout_height="match_parent"
-            android:background="@drawable/ripple_drawable"
-            android:layout_marginEnd="@dimen/multi_user_switch_keyguard_margin">
-            <ImageView android:id="@+id/multi_user_avatar"
-                android:layout_width="@dimen/multi_user_avatar_keyguard_size"
-                android:layout_height="@dimen/multi_user_avatar_keyguard_size"
-                android:layout_gravity="center"
-                android:scaleType="centerInside"/>
-        </com.android.systemui.statusbar.phone.MultiUserSwitch>
+
+        <ImageView android:id="@+id/multi_user_avatar"
+            android:layout_width="@dimen/multi_user_avatar_keyguard_size"
+            android:layout_height="@dimen/multi_user_avatar_keyguard_size"
+            android:layout_gravity="center"
+            android:scaleType="centerInside"/>
     </LinearLayout>
 
     <Space
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher.xml b/packages/SystemUI/res/layout/keyguard_user_switcher.xml
index 983ba6d..253c03e 100644
--- a/packages/SystemUI/res/layout/keyguard_user_switcher.xml
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher.xml
@@ -14,10 +14,50 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<view xmlns:android="http://schemas.android.com/apk/res/android"
-        class="com.android.systemui.statusbar.policy.KeyguardUserSwitcher$Container"
-        android:visibility="gone"
-        android:layout_height="match_parent"
-        android:layout_width="match_parent">
-    <!-- KeyguardUserSwitcher loads keyguard_user_switcher_inner.xml here -->
-</view>
\ No newline at end of file
+<!-- This is a view that shows a user switcher in Keyguard. -->
+<com.android.systemui.statusbar.policy.KeyguardUserSwitcherView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/keyguard_user_switcher_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="end">
+
+    <com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView
+        android:id="@+id/keyguard_user_switcher_list"
+        android:orientation="vertical"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_gravity="top|end"
+        android:gravity="end" />
+
+    <LinearLayout
+        android:id="@+id/end_guest_button"
+        android:layout_height="@dimen/end_guest_button_layout_height"
+        android:layout_width="wrap_content"
+        android:layout_gravity="center_horizontal|bottom"
+        android:layout_centerHorizontal="true"
+        android:layout_marginBottom="@dimen/end_guest_button_margin_bottom"
+        android:orientation="horizontal"
+        android:gravity="center"
+        android:paddingLeft="@dimen/end_guest_button_padding_horizontal"
+        android:paddingRight="@dimen/end_guest_button_padding_horizontal"
+        android:background="@drawable/end_guest_button_background"
+        android:visibility="gone">
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:src="@drawable/ic_exit_to_app"
+            android:background="@android:color/transparent"
+            android:color="?attr/wallpaperTextColor" />
+        <TextView
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:gravity="center"
+            android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
+            android:textColor="?attr/wallpaperTextColor"
+            android:textSize="13sp"
+            android:text="@string/guest_exit_button" />
+    </LinearLayout>
+
+</com.android.systemui.statusbar.policy.KeyguardUserSwitcherView>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_inner.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_inner.xml
deleted file mode 100644
index 4c1042e..0000000
--- a/packages/SystemUI/res/layout/keyguard_user_switcher_inner.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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
-  -->
-<com.android.keyguard.AlphaOptimizedLinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/keyguard_user_switcher_inner"
-    android:orientation="vertical"
-    android:layout_height="wrap_content"
-    android:layout_width="wrap_content"
-    android:layout_marginTop="@dimen/status_bar_header_height_keyguard"
-    android:layout_gravity="end"
-    android:gravity="end"
-    android:paddingTop="4dp">
-</com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
index 1cd1a04..aaa372a 100644
--- a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
@@ -19,29 +19,30 @@
 <!-- LinearLayout -->
 <com.android.systemui.statusbar.policy.KeyguardUserDetailItemView
         xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:sysui="http://schemas.android.com/apk/res-auto"
+        xmlns:systemui="http://schemas.android.com/apk/res-auto"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:padding="8dp"
         android:layout_marginEnd="8dp"
-        android:gravity="center_vertical"
+        android:gravity="end|center_vertical"
         android:clickable="true"
-        android:background="@drawable/ripple_drawable"
-        sysui:regularTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
-        sysui:activatedTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher.Activated">
-    <TextView android:id="@+id/user_name"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginEnd="13dp"
-            android:textAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
-            />
-    <com.android.systemui.statusbar.phone.UserAvatarView android:id="@+id/user_picture"
-            android:layout_width="@dimen/kg_framed_avatar_size"
-            android:layout_height="@dimen/kg_framed_avatar_size"
-            android:contentDescription="@null"
-            sysui:frameWidth="@dimen/keyguard_user_switcher_border_thickness"
-            sysui:framePadding="2.5dp"
-            sysui:badgeDiameter="18dp"
-            sysui:badgeMargin="1dp"
-            sysui:frameColor="@color/kg_user_switcher_rounded_background_color" />
+        android:background="@drawable/kg_user_switcher_rounded_bg"
+        systemui:activatedTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
+        systemui:regularTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher">
+    <TextView
+        android:id="@+id/user_name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="20dp"
+        android:layout_marginEnd="16dp" />
+    <com.android.systemui.statusbar.phone.UserAvatarView
+        android:id="@+id/user_picture"
+        android:layout_width="@dimen/kg_framed_avatar_size"
+        android:layout_height="@dimen/kg_framed_avatar_size"
+        systemui:avatarPadding="0dp"
+        systemui:badgeDiameter="18dp"
+        systemui:badgeMargin="1dp"
+        systemui:frameWidth="0dp"
+        systemui:framePadding="0dp"
+        systemui:frameColor="@color/kg_user_avatar_frame" />
 </com.android.systemui.statusbar.policy.KeyguardUserDetailItemView>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index d6385ff..859d904 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -31,6 +31,12 @@
         android:layout_height="match_parent"
         android:visibility="gone" />
 
+    <ViewStub
+        android:id="@+id/keyguard_user_switcher_stub"
+        android:layout="@layout/keyguard_user_switcher"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent" />
+
     <include
         layout="@layout/keyguard_status_view"
         android:visibility="gone" />
@@ -72,12 +78,6 @@
 
         <include layout="@layout/photo_preview_overlay" />
 
-        <ViewStub
-            android:id="@+id/keyguard_user_switcher"
-            android:layout="@layout/keyguard_user_switcher"
-            android:layout_height="match_parent"
-            android:layout_width="match_parent" />
-
         <include
             layout="@layout/keyguard_status_bar"
             android:visibility="invisible" />
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 3153d0d..37ec576 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -89,6 +89,8 @@
     <color name="kg_user_switcher_avatar_icon_color">@android:color/background_light</color>
     <!-- Icon color for selected user avatars in keyguard user switcher -->
     <color name="kg_user_switcher_selected_avatar_icon_color">#202124</color>
+    <!-- Color of background circle of user avatars in keyguard user switcher -->
+    <color name="kg_user_switcher_avatar_background">#3C4043</color>
     <!-- Icon color for user avatars in quick settings user switcher  -->
     <color name="qs_user_switcher_avatar_icon_color">@android:color/background_light</color>
     <!-- Icon color for selected user avatars in quick settings user switcher  -->
diff --git a/packages/SystemUI/res/values-sw600dp/styles.xml b/packages/SystemUI/res/values-sw600dp/styles.xml
index 02bd602..ee2b82d 100644
--- a/packages/SystemUI/res/values-sw600dp/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp/styles.xml
@@ -23,13 +23,6 @@
         <item name="numColumns">4</item>
     </style>
 
-    <style name="TextAppearance.StatusBar.Expanded.UserSwitcher">
-        <item name="android:textSize">@dimen/kg_user_switcher_text_size</item>
-        <item name="android:textStyle">normal</item>
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
-        <item name="android:textColor">?attr/wallpaperTextColor</item>
-    </style>
-
     <style name="TextAppearance.QS.UserSwitcher">
         <item name="android:textSize">@dimen/kg_user_switcher_text_size</item>
         <item name="android:textColor">?android:attr/textColorSecondary</item>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 8166e35..a1191ae 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -139,6 +139,10 @@
     <!-- Size of shadows/elevations on keyguard -->
     <attr name="shadowRadius" format="float" />
 
+    <attr name="handleThickness" format="dimension" />
+    <attr name="handleColor" format="color" />
+    <attr name="scrimColor" format="color" />
+
     <!-- Used display CarrierText in Keyguard or QS Footer -->
     <declare-styleable name="CarrierText">
         <attr name="allCaps" format="boolean" />
@@ -173,15 +177,15 @@
     </declare-styleable>
 
     <declare-styleable name="CropView">
-        <attr name="handleThickness" format="dimension" />
-        <attr name="handleColor" format="color" />
-        <attr name="scrimColor" format="color" />
+        <attr name="handleThickness" />
+        <attr name="handleColor" />
+        <attr name="scrimColor" />
     </declare-styleable>
 
     <declare-styleable name="MagnifierView">
-        <attr name="handleThickness" format="dimension" />
-        <attr name="handleColor" format="color" />
-        <attr name="scrimColor" format="color" />
+        <attr name="handleThickness" />
+        <attr name="handleColor" />
+        <attr name="scrimColor" />
         <attr name="borderThickness" format="dimension" />
         <attr name="borderColor" format="color" />
     </declare-styleable>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 5fb6de7..8bd9de9 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -66,9 +66,13 @@
     <!-- Color for rounded background for activated user in keyguard user switcher -->
     <color name="kg_user_switcher_activated_background_color">#26000000</color>
     <!-- Icon color for user avatars in keyguard user switcher -->
-    <color name="kg_user_switcher_avatar_icon_color">@android:color/background_light</color>
-    <!-- Icon color for selected user avatars in keyguard user switcher -->
-    <color name="kg_user_switcher_selected_avatar_icon_color">@android:color/background_light</color>
+    <color name="kg_user_switcher_avatar_icon_color">@color/GM2_grey_800</color>
+    <!-- Icon color for user avatars in keyguard user switcher that restricted
+         (e.g. cannot be switched to) -->
+    <color name="kg_user_switcher_restricted_avatar_icon_color">@color/GM2_grey_600</color>
+    <!-- Color of background circle of user avatars in keyguard user switcher -->
+    <color name="kg_user_switcher_avatar_background">@color/GM2_grey_300</color>
+
     <!-- Icon color for user avatars in user switcher quick settings -->
     <color name="qs_user_switcher_avatar_icon_color">#3C4043</color>
     <!-- Icon color for selected user avatars in user switcher quick settings -->
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index bb04c3b..6196225 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -107,7 +107,7 @@
 
     <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
     <string name="quick_settings_tiles_stock" translatable="false">
-        wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle
+        wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle,controls
     </string>
 
     <!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 08cd655..594fbdf 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -743,9 +743,6 @@
     <!-- end margin for system icons if multi user switch is hidden -->
     <dimen name="system_icons_switcher_hidden_expanded_margin">16dp</dimen>
 
-    <!-- The thickness of the colored border around the current user. -->
-    <dimen name="keyguard_user_switcher_border_thickness">2dp</dimen>
-
     <dimen name="data_usage_graph_marker_width">4dp</dimen>
 
     <!-- The padding bottom of the clock group when QS is expanded. -->
@@ -805,7 +802,7 @@
     <!-- Size of user icon + frame in the qs user picker (incl. frame) -->
     <dimen name="qs_framed_avatar_size">54dp</dimen>
     <!-- Size of user icon + frame in the keyguard user picker (incl. frame) -->
-    <dimen name="kg_framed_avatar_size">54dp</dimen>
+    <dimen name="kg_framed_avatar_size">32dp</dimen>
 
     <!-- Margin on the left side of the carrier text on Keyguard -->
     <dimen name="keyguard_carrier_text_margin">16dp</dimen>
@@ -1324,8 +1321,16 @@
     <dimen name="screenrecord_status_icon_height">17.5dp</dimen>
     <dimen name="screenrecord_status_icon_bg_radius">8dp</dimen>
 
+    <!-- Keyguard user switcher -->
     <dimen name="kg_user_switcher_text_size">16sp</dimen>
 
+    <!-- End guest session button -->
+    <dimen name="end_guest_button_layout_height">32dp</dimen>
+    <dimen name="end_guest_button_padding_horizontal">16dp</dimen>
+    <dimen name="end_guest_button_margin_bottom">96dp</dimen>
+    <dimen name="end_guest_button_border_size">1dp</dimen>
+    <dimen name="end_guest_button_corner_radius">16dp</dimen>
+
     <!-- Opacity at which the background for the shutdown UI will be drawn. -->
     <item name="shutdown_scrim_behind_alpha" format="float" type="dimen">0.95</item>
 
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 6196e4a..6d731f8 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -18,11 +18,12 @@
 <resources>
     <bool name="are_flags_overrideable">false</bool>
 
-    <bool name="flag_notification_pipeline2">false</bool>
+    <bool name="flag_notification_pipeline2">true</bool>
     <bool name="flag_notification_pipeline2_rendering">false</bool>
     <bool name="flag_notif_updates">false</bool>
 
     <bool name="flag_shade_is_opaque">false</bool>
+    <bool name="flag_monet">false</bool>
 
     <!-- b/171917882 -->
     <bool name="flag_notification_twocolumn">false</bool>
@@ -34,9 +35,6 @@
 
     <bool name="flag_brightness_slider">false</bool>
 
-    <!-- The new animations to/from lockscreen and AOD! -->
-    <bool name="flag_lockscreen_animations">false</bool>
-
     <!-- People Tile flag -->
     <bool name="flag_conversations">false</bool>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index abcf4e8..d997ca2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1112,14 +1112,17 @@
     <!-- Name for a freshly added user [CHAR LIMIT=30] -->
     <string name="user_new_user_name">New user</string>
 
+    <!-- Label for button that exits guest session and clears the guest user data [CHAR LIMIT=50]-->
+    <string name="guest_exit_button">End guest session</string>
+
     <!-- Title of the confirmation dialog when exiting guest session [CHAR LIMIT=NONE] -->
-    <string name="guest_exit_guest_dialog_title">End guest session?</string>
+    <string name="guest_exit_guest_dialog_title">Remove guest?</string>
 
     <!-- Message of the confirmation dialog when exiting guest session [CHAR LIMIT=NONE] -->
     <string name="guest_exit_guest_dialog_message">All apps and data in this session will be deleted.</string>
 
     <!-- Label for button in confirmation dialog when exiting guest session [CHAR LIMIT=35] -->
-    <string name="guest_exit_guest_dialog_remove">End session</string>
+    <string name="guest_exit_guest_dialog_remove">Remove</string>
 
     <!-- Title of the notification when resuming an existing guest session [CHAR LIMIT=NONE] -->
     <string name="guest_wipe_session_title">Welcome back, guest!</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7c72548..85c470f 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -114,12 +114,12 @@
     <style name="TextAppearance.StatusBar.Expanded.UserSwitcher">
         <item name="android:textSize">@dimen/kg_user_switcher_text_size</item>
         <item name="android:textStyle">normal</item>
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:textColor">?attr/wallpaperTextColor</item>
     </style>
 
     <style name="TextAppearance.StatusBar.Expanded.UserSwitcher.Activated">
         <item name="android:fontWeight">700</item>
-        <item name="android:textStyle">bold</item>
     </style>
 
     <style name="TextAppearance" />
@@ -764,6 +764,7 @@
     <style name="TextAppearance.PrivacyDialog">
         <item name="android:textSize">14sp</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
 
     <style name="UdfpsProgressBarStyle"
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index a5f364d..6fb6760 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -16,13 +16,9 @@
 
 package com.android.keyguard;
 
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-
 import android.util.Slog;
 import android.view.View;
 
-import com.android.systemui.Interpolators;
-import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -50,13 +46,12 @@
 
     private final KeyguardSliceViewController mKeyguardSliceViewController;
     private final KeyguardClockSwitchController mKeyguardClockSwitchController;
-    private final KeyguardStateController mKeyguardStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final ConfigurationController mConfigurationController;
     private final NotificationIconAreaController mNotificationIconAreaController;
     private final DozeParameters mDozeParameters;
+    private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
 
-    private boolean mKeyguardStatusViewVisibilityAnimating;
     private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
 
     @Inject
@@ -72,11 +67,12 @@
         super(keyguardStatusView);
         mKeyguardSliceViewController = keyguardSliceViewController;
         mKeyguardClockSwitchController = keyguardClockSwitchController;
-        mKeyguardStateController = keyguardStateController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mConfigurationController = configurationController;
         mNotificationIconAreaController = notificationIconAreaController;
         mDozeParameters = dozeParameters;
+        mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
+                dozeParameters);
     }
 
     @Override
@@ -144,7 +140,7 @@
      * Set keyguard status view alpha.
      */
     public void setAlpha(float alpha) {
-        if (!mKeyguardStatusViewVisibilityAnimating) {
+        if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
             mView.setAlpha(alpha);
         }
     }
@@ -200,7 +196,7 @@
     public void updatePosition(int x, int y, float scale, boolean animate) {
         // We animate the status view visible/invisible using Y translation, so don't change it
         // while the animation is running.
-        if (!mKeyguardStatusViewVisibilityAnimating) {
+        if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
             PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, CLOCK_ANIMATION_PROPERTIES,
                     animate);
         }
@@ -230,69 +226,8 @@
             boolean keyguardFadingAway,
             boolean goingToFullShade,
             int oldStatusBarState) {
-        mView.animate().cancel();
-        mKeyguardStatusViewVisibilityAnimating = false;
-        if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD
-                && statusBarState != KEYGUARD) || goingToFullShade) {
-            mKeyguardStatusViewVisibilityAnimating = true;
-            mView.animate()
-                    .alpha(0f)
-                    .setStartDelay(0)
-                    .setDuration(160)
-                    .setInterpolator(Interpolators.ALPHA_OUT)
-                    .withEndAction(
-                    mAnimateKeyguardStatusViewGoneEndRunnable);
-            if (keyguardFadingAway) {
-                mView.animate()
-                        .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
-                        .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
-                        .start();
-            }
-        } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
-            mView.setVisibility(View.VISIBLE);
-            mKeyguardStatusViewVisibilityAnimating = true;
-            mView.setAlpha(0f);
-            mView.animate()
-                    .alpha(1f)
-                    .setStartDelay(0)
-                    .setDuration(320)
-                    .setInterpolator(Interpolators.ALPHA_IN)
-                    .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
-        } else if (statusBarState == KEYGUARD) {
-            if (keyguardFadingAway) {
-                mKeyguardStatusViewVisibilityAnimating = true;
-                mView.animate()
-                        .alpha(0)
-                        .translationYBy(-getHeight() * 0.05f)
-                        .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
-                        .setDuration(125)
-                        .setStartDelay(0)
-                        .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable)
-                        .start();
-            } else if (mDozeParameters.shouldControlUnlockedScreenOff()) {
-                mKeyguardStatusViewVisibilityAnimating = true;
-
-                mView.setVisibility(View.VISIBLE);
-                mView.setAlpha(0f);
-
-                float curTranslationY = mView.getTranslationY();
-                mView.setTranslationY(curTranslationY - getHeight() * 0.1f);
-                mView.animate()
-                        .setStartDelay((int) (StackStateAnimator.ANIMATION_DURATION_WAKEUP * .6f))
-                        .setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP)
-                        .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                        .alpha(1f)
-                        .translationY(curTranslationY)
-                        .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable)
-                        .start();
-            } else {
-                mView.setVisibility(View.VISIBLE);
-                mView.setAlpha(1f);
-            }
-        } else {
-            mView.setVisibility(View.GONE);
-            mView.setAlpha(1f);
-        }
+        mKeyguardVisibilityHelper.setViewVisibility(
+                statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
     }
 
     private void refreshTime() {
@@ -393,19 +328,4 @@
             mView.updateLogoutView();
         }
     };
-
-    private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> {
-        mKeyguardStatusViewVisibilityAnimating = false;
-        mView.setVisibility(View.INVISIBLE);
-    };
-
-
-    private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> {
-        mKeyguardStatusViewVisibilityAnimating = false;
-        mView.setVisibility(View.GONE);
-    };
-
-    private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> {
-        mKeyguardStatusViewVisibilityAnimating = false;
-    };
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
new file mode 100644
index 0000000..724e1f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+
+import android.view.View;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+/**
+ * Helper class for updating visibility of keyguard views based on keyguard and status bar state.
+ * This logic is shared by both the keyguard status view and the keyguard user switcher.
+ */
+public class KeyguardVisibilityHelper {
+
+    private View mView;
+    private final KeyguardStateController mKeyguardStateController;
+    private final DozeParameters mDozeParameters;
+    private boolean mKeyguardViewVisibilityAnimating;
+
+    public KeyguardVisibilityHelper(View view, KeyguardStateController keyguardStateController,
+            DozeParameters dozeParameters) {
+        mView = view;
+        mKeyguardStateController = keyguardStateController;
+        mDozeParameters = dozeParameters;
+    }
+
+    public boolean isVisibilityAnimating() {
+        return mKeyguardViewVisibilityAnimating;
+    }
+
+    /**
+     * Set the visibility of a keyguard view based on some new state.
+     */
+    public void setViewVisibility(
+            int statusBarState,
+            boolean keyguardFadingAway,
+            boolean goingToFullShade,
+            int oldStatusBarState) {
+        mView.animate().cancel();
+        mKeyguardViewVisibilityAnimating = false;
+        if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD
+                && statusBarState != KEYGUARD) || goingToFullShade) {
+            mKeyguardViewVisibilityAnimating = true;
+            mView.animate()
+                    .alpha(0f)
+                    .setStartDelay(0)
+                    .setDuration(160)
+                    .setInterpolator(Interpolators.ALPHA_OUT)
+                    .withEndAction(
+                            mAnimateKeyguardStatusViewGoneEndRunnable);
+            if (keyguardFadingAway) {
+                mView.animate()
+                        .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
+                        .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
+                        .start();
+            }
+        } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
+            mView.setVisibility(View.VISIBLE);
+            mKeyguardViewVisibilityAnimating = true;
+            mView.setAlpha(0f);
+            mView.animate()
+                    .alpha(1f)
+                    .setStartDelay(0)
+                    .setDuration(320)
+                    .setInterpolator(Interpolators.ALPHA_IN)
+                    .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
+        } else if (statusBarState == KEYGUARD) {
+            if (keyguardFadingAway) {
+                mKeyguardViewVisibilityAnimating = true;
+                mView.animate()
+                        .alpha(0)
+                        .translationYBy(-mView.getHeight() * 0.05f)
+                        .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
+                        .setDuration(125)
+                        .setStartDelay(0)
+                        .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable)
+                        .start();
+            } else if (mDozeParameters.shouldControlUnlockedScreenOff()) {
+                mKeyguardViewVisibilityAnimating = true;
+
+                mView.setVisibility(View.VISIBLE);
+                mView.setAlpha(0f);
+
+                float curTranslationY = mView.getTranslationY();
+                mView.setTranslationY(curTranslationY - mView.getHeight() * 0.1f);
+                mView.animate()
+                        .setStartDelay((int) (StackStateAnimator.ANIMATION_DURATION_WAKEUP * .6f))
+                        .setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP)
+                        .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                        .alpha(1f)
+                        .translationY(curTranslationY)
+                        .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable)
+                        .start();
+            } else {
+                mView.setVisibility(View.VISIBLE);
+                mView.setAlpha(1f);
+            }
+        } else {
+            mView.setVisibility(View.GONE);
+            mView.setAlpha(1f);
+        }
+    }
+
+    private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> {
+        mKeyguardViewVisibilityAnimating = false;
+        mView.setVisibility(View.INVISIBLE);
+    };
+
+    private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> {
+        mKeyguardViewVisibilityAnimating = false;
+        mView.setVisibility(View.GONE);
+    };
+
+    private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> {
+        mKeyguardViewVisibilityAnimating = false;
+    };
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.java
new file mode 100644
index 0000000..730c14d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.dagger;
+
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Subcomponent for helping work with KeyguardUserSwitcher and its children.
+ */
+@Subcomponent(modules = {KeyguardUserSwitcherModule.class})
+@KeyguardUserSwitcherScope
+public interface KeyguardUserSwitcherComponent {
+    /** Simple factory for {@link KeyguardUserSwitcherComponent}. */
+    @Subcomponent.Factory
+    interface Factory {
+        KeyguardUserSwitcherComponent build(
+                @BindsInstance KeyguardUserSwitcherView keyguardUserSwitcherView);
+    }
+
+    /** Builds a {@link com.android.systemui.statusbar.policy.KeyguardUserSwitcherController}. */
+    KeyguardUserSwitcherController getKeyguardUserSwitcherController();
+}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java
similarity index 76%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java
index 14d57bf..b9184f4 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package com.android.keyguard.dagger;
 
-parcelable ExternalTimeSuggestion;
+import dagger.Module;
+
+/** Dagger module for {@link KeyguardUserSwitcherComponent}. */
+@Module
+public abstract class KeyguardUserSwitcherModule {
+}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java
similarity index 61%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java
index 14d57bf..864472e 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java
@@ -14,6 +14,19 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package com.android.keyguard.dagger;
 
-parcelable ExternalTimeSuggestion;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
+
+/**
+ * Scope annotation for singleton items within the KeyguardUserSwitcherComponent.
+ */
+@Documented
+@Retention(RUNTIME)
+@Scope
+public @interface KeyguardUserSwitcherScope {}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 247f25e..6b300f4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -30,6 +30,7 @@
 import android.service.controls.actions.FloatAction
 import android.util.Log
 import android.view.HapticFeedbackConstants
+import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
@@ -71,7 +72,7 @@
     }
 
     override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
-        bouncerOrRun(Action(cvh.cws.ci.controlId, {
+        bouncerOrRun(createAction(cvh.cws.ci.controlId, {
             cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
             cvh.action(BooleanAction(templateId, !isChecked))
         }, true /* blockable */))
@@ -79,7 +80,7 @@
 
     override fun touch(cvh: ControlViewHolder, templateId: String, control: Control) {
         val blockable = cvh.usePanel()
-        bouncerOrRun(Action(cvh.cws.ci.controlId, {
+        bouncerOrRun(createAction(cvh.cws.ci.controlId, {
             cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
             if (cvh.usePanel()) {
                 showDialog(cvh, control.getAppIntent().getIntent())
@@ -98,13 +99,13 @@
     }
 
     override fun setValue(cvh: ControlViewHolder, templateId: String, newValue: Float) {
-        bouncerOrRun(Action(cvh.cws.ci.controlId, {
+        bouncerOrRun(createAction(cvh.cws.ci.controlId, {
             cvh.action(FloatAction(templateId, newValue))
         }, false /* blockable */))
     }
 
     override fun longPress(cvh: ControlViewHolder) {
-        bouncerOrRun(Action(cvh.cws.ci.controlId, {
+        bouncerOrRun(createAction(cvh.cws.ci.controlId, {
             // Long press snould only be called when there is valid control state, otherwise ignore
             cvh.cws.control?.let {
                 cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
@@ -114,6 +115,7 @@
     }
 
     override fun runPendingAction(controlId: String) {
+        if (!keyguardStateController.isUnlocked()) return
         if (pendingAction?.controlId == controlId) {
             pendingAction?.invoke()
             pendingAction = null
@@ -135,7 +137,8 @@
             false
         }
 
-    private fun bouncerOrRun(action: Action) {
+    @VisibleForTesting
+    fun bouncerOrRun(action: Action) {
         if (keyguardStateController.isShowing()) {
             var closeDialog = !keyguardStateController.isUnlocked()
             if (closeDialog) {
@@ -190,6 +193,10 @@
         }
     }
 
+    @VisibleForTesting
+    fun createAction(controlId: String, f: () -> Unit, blockable: Boolean) =
+        Action(controlId, f, blockable)
+
     inner class Action(val controlId: String, val f: () -> Unit, val blockable: Boolean) {
         fun invoke() {
             if (!blockable || shouldRunAction(controlId)) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
index f533cfb..db68d17 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
@@ -28,11 +28,12 @@
 import com.android.systemui.Interpolators
 import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastDispatcher
+import javax.inject.Inject
 
 /**
  * Show the controls space inside a dialog, as from the lock screen.
  */
-class ControlsDialog(
+class ControlsDialog @Inject constructor(
     thisContext: Context,
     val broadcastDispatcher: BroadcastDispatcher
 ) : Dialog(thisContext, R.style.Theme_SystemUI_Dialog_Control_LockScreen) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 84dd259..f3726a3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -16,6 +16,11 @@
 
 package com.android.systemui.dagger;
 
+import android.content.Context;
+
+import com.android.systemui.SystemUIFactory;
+import com.android.systemui.tv.TvWMComponent;
+import com.android.systemui.wmshell.TvWMShellModule;
 import com.android.systemui.wmshell.WMShellModule;
 import com.android.wm.shell.ShellCommandHandler;
 import com.android.wm.shell.ShellInit;
@@ -34,7 +39,13 @@
 import dagger.Subcomponent;
 
 /**
- * Dagger Subcomponent for WindowManager.
+ * Dagger Subcomponent for WindowManager.  This class explicitly describes the interfaces exported
+ * from the WM component into the SysUI component (in
+ * {@link SystemUIFactory#init(Context, boolean)}), and references the specific dependencies
+ * provided by its particular device/form-factor SystemUI implementation.
+ *
+ * ie. {@link WMComponent} includes {@link WMShellModule}
+ *     and {@link TvWMComponent} includes {@link TvWMShellModule}
  */
 @WMSingleton
 @Subcomponent(modules = {WMShellModule.class})
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c55fdf4..91cf710 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -415,6 +415,7 @@
 
         @Override
         public void onUserSwitching(int userId) {
+            if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId));
             // Note that the mLockPatternUtils user has already been updated from setCurrentUser.
             // We need to force a reset of the views, since lockNow (called by
             // ActivityManagerService) will not reconstruct the keyguard if it is already showing.
@@ -432,6 +433,7 @@
 
         @Override
         public void onUserSwitchComplete(int userId) {
+            if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
             if (userId != UserHandle.USER_SYSTEM) {
                 UserInfo info = UserManager.get(mContext).getUserInfo(userId);
                 // Don't try to dismiss if the user has Pin/Patter/Password set
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 76281d8..9e5b225 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -30,6 +30,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardViewController;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
+import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.classifier.FalsingModule;
@@ -61,7 +62,7 @@
 /**
  * Dagger Module providing {@link StatusBar}.
  */
-@Module(subcomponents = {KeyguardStatusViewComponent.class},
+@Module(subcomponents = {KeyguardStatusViewComponent.class, KeyguardUserSwitcherComponent.class},
         includes = {FalsingModule.class})
 public class KeyguardModule {
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index f56a890..7820921 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -40,6 +40,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.brightness.BrightnessController;
 import com.android.systemui.settings.brightness.BrightnessSlider;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.tuner.TunerService;
 
@@ -92,9 +93,10 @@
             DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
             QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
             BrightnessSlider.Factory brightnessSliderFactory,
-            @Named(QS_LABELS_FLAG) boolean qsLabelsFlag) {
+            @Named(QS_LABELS_FLAG) boolean qsLabelsFlag,
+            FeatureFlags featureFlags) {
         super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
-                metricsLogger, uiEventLogger, qsLogger, dumpManager);
+                metricsLogger, uiEventLogger, qsLogger, dumpManager, featureFlags);
         mQsSecurityFooter = qsSecurityFooter;
         mTunerService = tunerService;
         mQsCustomizerController = qsCustomizerController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index b02799f..9426e71 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -34,6 +34,8 @@
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.util.Utils;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.animation.DisappearParameters;
 
@@ -63,6 +65,7 @@
     private final UiEventLogger mUiEventLogger;
     private final QSLogger mQSLogger;
     private final DumpManager mDumpManager;
+    private final FeatureFlags mFeatureFlags;
     protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
 
     private int mLastOrientation;
@@ -93,11 +96,18 @@
 
     private boolean mUsingHorizontalLayout;
 
-    protected QSPanelControllerBase(T view, QSTileHost host,
+    protected QSPanelControllerBase(
+            T view,
+            QSTileHost host,
             QSCustomizerController qsCustomizerController,
-            @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer, MediaHost mediaHost,
-            MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
-            DumpManager dumpManager) {
+            @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
+            MediaHost mediaHost,
+            MetricsLogger metricsLogger,
+            UiEventLogger uiEventLogger,
+            QSLogger qsLogger,
+            DumpManager dumpManager,
+            FeatureFlags featureFlags
+    ) {
         super(view);
         mHost = host;
         mQsCustomizerController = qsCustomizerController;
@@ -107,6 +117,7 @@
         mUiEventLogger = uiEventLogger;
         mQSLogger = qsLogger;
         mDumpManager = dumpManager;
+        mFeatureFlags = featureFlags;
     }
 
     @Override
@@ -334,9 +345,12 @@
     }
 
     boolean shouldUseHorizontalLayout() {
+        if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, getResources()))  {
+            return false;
+        }
         return mUsingMediaPlayer && mMediaHost.getVisible()
-                && getResources().getConfiguration().orientation
-                == Configuration.ORIENTATION_LANDSCAPE;
+                    && getResources().getConfiguration().orientation
+                    == Configuration.ORIENTATION_LANDSCAPE;
     }
 
     private void logTiles() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index a0db200..383e932 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -30,6 +30,7 @@
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.FeatureFlags;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -57,9 +58,11 @@
             @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
             @Named(QUICK_QS_PANEL) MediaHost mediaHost,
             MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
-            DumpManager dumpManager, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag) {
+            DumpManager dumpManager, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag,
+            FeatureFlags featureFlags
+    ) {
         super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
-                uiEventLogger, qsLogger, dumpManager);
+                uiEventLogger, qsLogger, dumpManager, featureFlags);
         mUseSideLabels = qsLabelsFlag;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
index eddcf8c..ae0b5d1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
@@ -39,6 +39,7 @@
     private ImageView mMobileSignal;
     private ImageView mMobileRoaming;
     private CellSignalState mLastSignalState;
+    private boolean mProviderModel;
 
     public QSCarrier(Context context) {
         super(context);
@@ -59,15 +60,20 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mMobileGroup = findViewById(R.id.mobile_combo);
         if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
-            mMobileRoaming = findViewById(R.id.mobile_roaming_large);
+            mProviderModel = true;
         } else {
-            mMobileRoaming = findViewById(R.id.mobile_roaming);
+            mProviderModel = false;
         }
+        mMobileGroup = findViewById(R.id.mobile_combo);
+        mMobileRoaming = findViewById(R.id.mobile_roaming);
         mMobileSignal = findViewById(R.id.mobile_signal);
         mCarrierText = findViewById(R.id.qs_carrier_text);
-        mMobileSignal.setImageDrawable(new SignalDrawable(mContext));
+        if (mProviderModel) {
+            mMobileSignal.setImageDrawable(mContext.getDrawable(R.drawable.ic_qs_no_calling_sms));
+        } else {
+            mMobileSignal.setImageDrawable(new SignalDrawable(mContext));
+        }
     }
 
     /**
@@ -85,22 +91,27 @@
                     android.R.attr.textColorPrimary);
             mMobileRoaming.setImageTintList(colorStateList);
             mMobileSignal.setImageTintList(colorStateList);
-            mMobileSignal.setImageLevel(state.mobileSignalIconId);
 
-            StringBuilder contentDescription = new StringBuilder();
-            if (state.contentDescription != null) {
-                contentDescription.append(state.contentDescription).append(", ");
+            if (mProviderModel) {
+                mMobileSignal.setImageDrawable(mContext.getDrawable(state.mobileSignalIconId));
+                mMobileSignal.setContentDescription(state.contentDescription);
+            } else {
+                mMobileSignal.setImageLevel(state.mobileSignalIconId);
+                StringBuilder contentDescription = new StringBuilder();
+                if (state.contentDescription != null) {
+                    contentDescription.append(state.contentDescription).append(", ");
+                }
+                if (state.roaming) {
+                    contentDescription
+                            .append(mContext.getString(R.string.data_connection_roaming))
+                            .append(", ");
+                }
+                // TODO: show mobile data off/no internet text for 5 seconds before carrier text
+                if (hasValidTypeContentDescription(state.typeContentDescription)) {
+                    contentDescription.append(state.typeContentDescription);
+                }
+                mMobileSignal.setContentDescription(contentDescription);
             }
-            if (state.roaming) {
-                contentDescription
-                        .append(mContext.getString(R.string.data_connection_roaming))
-                        .append(", ");
-            }
-            // TODO: show mobile data off/no internet text for 5 seconds before carrier text
-            if (hasValidTypeContentDescription(state.typeContentDescription)) {
-                contentDescription.append(state.typeContentDescription);
-            }
-            mMobileSignal.setContentDescription(contentDescription);
         }
         return true;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index 77200cc..a567f51 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -19,6 +19,7 @@
 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
 
 import android.annotation.MainThread;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Handler;
 import android.os.Looper;
@@ -26,6 +27,7 @@
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.view.View;
 import android.widget.TextView;
@@ -33,6 +35,9 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.keyguard.CarrierTextController;
+import com.android.settingslib.AccessibilityContentDescriptions;
+import com.android.settingslib.mobile.TelephonyIcons;
+import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -62,6 +67,9 @@
             new CellSignalState[SIM_SLOTS];
     private View[] mCarrierDividers = new View[SIM_SLOTS - 1];
     private QSCarrier[] mCarrierGroups = new QSCarrier[SIM_SLOTS];
+    private int[] mLastSignalLevel = new int[SIM_SLOTS];
+    private String[] mLastSignalLevelDescription = new String[SIM_SLOTS];
+    private final boolean mProviderModel;
 
     private final NetworkController.SignalCallback mSignalCallback =
             new NetworkController.SignalCallback() {
@@ -72,6 +80,9 @@
                         CharSequence typeContentDescription,
                         CharSequence typeContentDescriptionHtml, CharSequence description,
                         boolean isWide, int subId, boolean roaming, boolean showTriangle) {
+                    if (mProviderModel) {
+                        return;
+                    }
                     int slotIndex = getSlotIndex(subId);
                     if (slotIndex >= SIM_SLOTS) {
                         Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex);
@@ -92,6 +103,46 @@
                 }
 
                 @Override
+                public void setCallIndicator(NetworkController.IconState statusIcon, int subId) {
+                    if (!mProviderModel) {
+                        return;
+                    }
+                    int slotIndex = getSlotIndex(subId);
+                    if (slotIndex >= SIM_SLOTS) {
+                        Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex);
+                        return;
+                    }
+                    if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+                        Log.e(TAG, "Invalid SIM slot index for subscription: " + subId);
+                        return;
+                    }
+                    if (statusIcon.icon == R.drawable.ic_qs_no_calling_sms) {
+                        if (statusIcon.visible) {
+                            mInfos[slotIndex] = new CellSignalState(true,
+                                    statusIcon.icon, statusIcon.contentDescription, "", false);
+                        } else {
+                            // Whenever the no Calling & SMS state is cleared, switched to the last
+                            // known call strength icon.
+                            mInfos[slotIndex] = new CellSignalState(
+                                    true, mLastSignalLevel[slotIndex],
+                                    mLastSignalLevelDescription[slotIndex], "", false);
+                        }
+                        mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
+                    } else {
+                        mLastSignalLevel[slotIndex] = statusIcon.icon;
+                        mLastSignalLevelDescription[slotIndex] = statusIcon.contentDescription;
+                        // Only Shows the call strength icon when the no Calling & SMS icon is not
+                        // shown.
+                        if (mInfos[slotIndex].mobileSignalIconId
+                                != R.drawable.ic_qs_no_calling_sms) {
+                            mInfos[slotIndex] = new CellSignalState(true, statusIcon.icon,
+                                    statusIcon.contentDescription, "", false);
+                            mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
+                        }
+                    }
+                }
+
+                @Override
                 public void setNoSims(boolean hasNoSims, boolean simDetected) {
                     if (hasNoSims) {
                         for (int i = 0; i < SIM_SLOTS; i++) {
@@ -118,7 +169,12 @@
     private QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter,
             @Background Handler bgHandler, @Main Looper mainLooper,
             NetworkController networkController,
-            CarrierTextController.Builder carrierTextControllerBuilder) {
+            CarrierTextController.Builder carrierTextControllerBuilder, Context context) {
+        if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
+            mProviderModel = true;
+        } else {
+            mProviderModel = false;
+        }
         mActivityStarter = activityStarter;
         mBgHandler = bgHandler;
         mNetworkController = networkController;
@@ -149,7 +205,13 @@
         mCarrierDividers[1] = view.getCarrierDivider2();
 
         for (int i = 0; i < SIM_SLOTS; i++) {
-            mInfos[i] = new CellSignalState();
+            mInfos[i] = new CellSignalState(true, R.drawable.ic_qs_no_calling_sms,
+                    context.getText(AccessibilityContentDescriptions.NO_CALLING).toString(),
+                    "", false);
+            mLastSignalLevel[i] = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0];
+            mLastSignalLevelDescription[i] =
+                    context.getText(AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0])
+                            .toString();
             mCarrierGroups[i].setOnClickListener(onClickListener);
         }
         view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
@@ -305,16 +367,18 @@
         private final Looper mLooper;
         private final NetworkController mNetworkController;
         private final CarrierTextController.Builder mCarrierTextControllerBuilder;
+        private final Context mContext;
 
         @Inject
         public Builder(ActivityStarter activityStarter, @Background Handler handler,
                 @Main Looper looper, NetworkController networkController,
-                CarrierTextController.Builder carrierTextControllerBuilder) {
+                CarrierTextController.Builder carrierTextControllerBuilder, Context context) {
             mActivityStarter = activityStarter;
             mHandler = handler;
             mLooper = looper;
             mNetworkController = networkController;
             mCarrierTextControllerBuilder = carrierTextControllerBuilder;
+            mContext = context;
         }
 
         public Builder setQSCarrierGroup(QSCarrierGroup view) {
@@ -324,7 +388,7 @@
 
         public QSCarrierGroupController build() {
             return new QSCarrierGroupController(mView, mActivityStarter, mHandler, mLooper,
-                    mNetworkController, mCarrierTextControllerBuilder);
+                    mNetworkController, mCarrierTextControllerBuilder, mContext);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index abf230e..d4bab21 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -102,6 +102,12 @@
         public void onConfigChanged(Configuration newConfig) {
             mView.updateNavBackDrop(newConfig, mLightBarController);
             mView.updateResources();
+            if (mTileAdapter.updateNumColumns()) {
+                RecyclerView.LayoutManager lm = mView.getRecyclerView().getLayoutManager();
+                if (lm instanceof GridLayoutManager) {
+                    ((GridLayoutManager) lm).setSpanCount(mTileAdapter.getNumColumns());
+                }
+            }
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 21464fd..048fdc3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -98,7 +98,7 @@
     private final UiEventLogger mUiEventLogger;
     private final AccessibilityDelegateCompat mAccessibilityDelegate;
     private RecyclerView mRecyclerView;
-    private final int mNumColumns;
+    private int mNumColumns;
 
     @Inject
     public TileAdapter(Context context, QSTileHost qsHost, UiEventLogger uiEventLogger) {
@@ -123,6 +123,21 @@
         mRecyclerView = null;
     }
 
+    /**
+     * Update the number of columns to show, from resources.
+     *
+     * @return {@code true} if the number of columns changed, {@code false} otherwise
+     */
+    public boolean updateNumColumns() {
+        int numColumns = mContext.getResources().getInteger(R.integer.quick_settings_num_columns);
+        if (numColumns != mNumColumns) {
+            mNumColumns = numColumns;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     public int getNumColumns() {
         return mNumColumns;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 11e6330..6983b38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -37,6 +37,7 @@
 import com.android.systemui.qs.tiles.CellularTile;
 import com.android.systemui.qs.tiles.ColorInversionTile;
 import com.android.systemui.qs.tiles.DataSaverTile;
+import com.android.systemui.qs.tiles.DeviceControlsTile;
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.qs.tiles.FlashlightTile;
 import com.android.systemui.qs.tiles.HotspotTile;
@@ -89,6 +90,7 @@
     private final Provider<ReduceBrightColorsTile> mReduceBrightColorsTileProvider;
     private final Provider<CameraToggleTile> mCameraToggleTileProvider;
     private final Provider<MicrophoneToggleTile> mMicrophoneToggleTileProvider;
+    private final Provider<DeviceControlsTile> mDeviceControlsTileProvider;
 
     private final Lazy<QSHost> mQsHostLazy;
     private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
@@ -123,7 +125,8 @@
             Provider<ScreenRecordTile> screenRecordTileProvider,
             Provider<ReduceBrightColorsTile> reduceBrightColorsTileProvider,
             Provider<CameraToggleTile> cameraToggleTileProvider,
-            Provider<MicrophoneToggleTile> microphoneToggleTileProvider) {
+            Provider<MicrophoneToggleTile> microphoneToggleTileProvider,
+            Provider<DeviceControlsTile> deviceControlsTileProvider) {
         mQsHostLazy = qsHostLazy;
         mCustomTileBuilderProvider = customTileBuilderProvider;
 
@@ -153,6 +156,7 @@
         mReduceBrightColorsTileProvider = reduceBrightColorsTileProvider;
         mCameraToggleTileProvider = cameraToggleTileProvider;
         mMicrophoneToggleTileProvider = microphoneToggleTileProvider;
+        mDeviceControlsTileProvider = deviceControlsTileProvider;
     }
 
     public QSTile createTile(String tileSpec) {
@@ -212,6 +216,8 @@
                 return mCameraToggleTileProvider.get();
             case "mictoggle":
                 return mMicrophoneToggleTileProvider.get();
+            case "controls":
+                return mDeviceControlsTileProvider.get();
         }
 
         // Custom tiles
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
new file mode 100644
index 0000000..6176a57
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.content.Intent
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+import android.service.quicksettings.Tile
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.R
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.ControlsDialog
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.util.settings.GlobalSettings
+import java.util.concurrent.atomic.AtomicBoolean
+import javax.inject.Inject
+import javax.inject.Provider
+
+class DeviceControlsTile @Inject constructor(
+    host: QSHost,
+    @Background backgroundLooper: Looper,
+    @Main mainHandler: Handler,
+    metricsLogger: MetricsLogger,
+    statusBarStateController: StatusBarStateController,
+    activityStarter: ActivityStarter,
+    qsLogger: QSLogger,
+    private val controlsComponent: ControlsComponent,
+    private val featureFlags: FeatureFlags,
+    private val dialogProvider: Provider<ControlsDialog>,
+    globalSettings: GlobalSettings
+) : QSTileImpl<QSTile.State>(
+        host,
+        backgroundLooper,
+        mainHandler,
+        metricsLogger,
+        statusBarStateController,
+        activityStarter,
+        qsLogger
+) {
+
+    companion object {
+        const val SETTINGS_FLAG = "controls_lockscreen"
+    }
+
+    private val controlsLockscreen = globalSettings.getInt(SETTINGS_FLAG, 0) != 0
+    private var hasControlsApps = AtomicBoolean(false)
+    private val intent = Intent(Settings.ACTION_DEVICE_CONTROLS_SETTINGS)
+
+    private var controlsDialog: ControlsDialog? = null
+    private val icon = ResourceIcon.get(R.drawable.ic_device_light)
+
+    private val listingCallback = object : ControlsListingController.ControlsListingCallback {
+        override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+            if (hasControlsApps.compareAndSet(serviceInfos.isEmpty(), serviceInfos.isNotEmpty())) {
+                refreshState()
+            }
+        }
+    }
+
+    init {
+        controlsComponent.getControlsListingController().ifPresent {
+            it.observe(this, listingCallback)
+        }
+    }
+
+    override fun isAvailable(): Boolean {
+        return featureFlags.isKeyguardLayoutEnabled &&
+                controlsLockscreen &&
+                controlsComponent.getControlsUiController().isPresent
+    }
+
+    override fun newTileState(): QSTile.State {
+        return QSTile.State().also {
+            it.state = Tile.STATE_UNAVAILABLE // Start unavailable matching `hasControlsApps`
+        }
+    }
+
+    override fun handleDestroy() {
+        dismissDialog()
+        super.handleDestroy()
+    }
+
+    private fun createDialog() {
+        if (controlsDialog?.isShowing != true) {
+            controlsDialog = dialogProvider.get()
+        }
+    }
+
+    private fun dismissDialog() {
+        controlsDialog?.dismiss()?.also {
+            controlsDialog = null
+        }
+    }
+
+    override fun handleClick() {
+        if (state.state != Tile.STATE_UNAVAILABLE) {
+            mUiHandler.post {
+                createDialog()
+                controlsDialog?.show(controlsComponent.getControlsUiController().get())
+            }
+        }
+    }
+
+    override fun handleUpdateState(state: QSTile.State, arg: Any?) {
+        state.label = tileLabel
+        state.secondaryLabel = ""
+        state.stateDescription = ""
+        state.contentDescription = state.label
+        state.icon = icon
+        if (hasControlsApps.get()) {
+            state.state = Tile.STATE_ACTIVE
+            if (controlsDialog == null) {
+                mUiHandler.post(this::createDialog)
+            }
+        } else {
+            state.state = Tile.STATE_UNAVAILABLE
+            dismissDialog()
+        }
+    }
+
+    override fun getMetricsCategory(): Int {
+        return 0
+    }
+
+    override fun getLongClickIntent(): Intent {
+        return intent
+    }
+
+    override fun getTileLabel(): CharSequence {
+        return mContext.getText(R.string.quick_controls_title)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
index 6a8c6149..6ca550c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
@@ -41,7 +41,7 @@
     protected static int layoutResId = R.layout.qs_user_detail_item;
 
     private UserAvatarView mAvatar;
-    private TextView mName;
+    protected TextView mName;
     private int mActivatedStyle;
     private int mRegularStyle;
     private View mRestrictedPadlock;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
index c8afd0b..9383aef 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -16,17 +16,22 @@
 
 package com.android.systemui.screenshot;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.MathUtils;
 import android.view.MotionEvent;
 import android.view.View;
 
 import androidx.annotation.Nullable;
+import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
 
 import com.android.systemui.R;
 
@@ -35,6 +40,7 @@
  * cropped out.
  */
 public class CropView extends View {
+    private static final String TAG = "CropView";
     public enum CropBoundary {
         NONE, TOP, BOTTOM
     }
@@ -118,10 +124,7 @@
             case MotionEvent.ACTION_UP:
                 if (mCurrentDraggingBoundary != CropBoundary.NONE) {
                     // Commit the delta to the stored crop values.
-                    mTopCrop += mTopDelta;
-                    mBottomCrop += mBottomDelta;
-                    mTopDelta = 0;
-                    mBottomDelta = 0;
+                    commitDeltas();
                     updateListener(event);
                 }
         }
@@ -129,6 +132,42 @@
     }
 
     /**
+     * Animate the given boundary to the given value.
+     */
+    public void animateBoundaryTo(CropBoundary boundary, float value) {
+        if (boundary == CropBoundary.NONE) {
+            Log.w(TAG, "No boundary selected for animation");
+            return;
+        }
+        float totalDelta = (boundary == CropBoundary.TOP) ? (value - mTopCrop)
+                : (value - mBottomCrop);
+        ValueAnimator animator = new ValueAnimator();
+        animator.addUpdateListener(animation -> {
+            if (boundary == CropBoundary.TOP) {
+                mTopDelta = animation.getAnimatedFraction() * totalDelta;
+            } else {
+                mBottomDelta = animation.getAnimatedFraction() * totalDelta;
+            }
+            invalidate();
+        });
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                commitDeltas();
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                commitDeltas();
+            }
+        });
+        animator.setFloatValues(0f, 1f);
+        animator.setDuration(750);
+        animator.setInterpolator(new FastOutSlowInInterpolator());
+        animator.start();
+    }
+
+    /**
      * @return value [0,1] representing the position of the top crop boundary. Does not reflect
      * changes from any in-progress touch input.
      */
@@ -148,6 +187,13 @@
         mCropInteractionListener = listener;
     }
 
+    private void commitDeltas() {
+        mTopCrop += mTopDelta;
+        mBottomCrop += mBottomDelta;
+        mTopDelta = 0;
+        mBottomDelta = 0;
+    }
+
     private void updateListener(MotionEvent event) {
         if (mCropInteractionListener != null) {
             float boundaryPosition = (mCurrentDraggingBoundary == CropBoundary.TOP)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
index 212e6c8..a95c91b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
@@ -70,7 +70,7 @@
 
         RecordingCanvas canvas = mNode.beginRecording(w, h);
         canvas.save();
-        canvas.clipRect(0, 0, mLocation.right, mLocation.bottom);
+        canvas.clipRect(0, 0, mLocation.width(), mLocation.height());
         canvas.drawBitmap(Bitmap.wrapHardwareBuffer(mImage.getHardwareBuffer(), COLOR_SPACE),
                 0, 0, null);
         canvas.restore();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
index 20f8451..ae3cd99 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
@@ -21,6 +21,8 @@
 import android.graphics.Rect;
 import android.graphics.RenderNode;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.util.Log;
 
 import androidx.annotation.UiThread;
 
@@ -32,11 +34,14 @@
  * <p>
  * To display on-screen, use {@link #getDrawable()}.
  */
-@UiThread
 class ImageTileSet {
 
     private static final String TAG = "ImageTileSet";
 
+    ImageTileSet(@UiThread Handler handler) {
+        mHandler = handler;
+    }
+
     interface OnBoundsChangedListener {
         /**
          * Reports an update to the bounding box that contains all active tiles. These are virtual
@@ -54,6 +59,7 @@
 
     private final List<ImageTile> mTiles = new ArrayList<>();
     private final Rect mBounds = new Rect();
+    private final Handler mHandler;
 
     private OnContentChangedListener mOnContentChangedListener;
     private OnBoundsChangedListener mOnBoundsChangedListener;
@@ -73,13 +79,32 @@
         newBounds.union(newRect);
         if (!newBounds.equals(mBounds)) {
             mBounds.set(newBounds);
-            if (mOnBoundsChangedListener != null) {
-                mOnBoundsChangedListener.onBoundsChanged(
-                        newBounds.left, newBounds.top, newBounds.right, newBounds.bottom);
-            }
+            notifyBoundsChanged(mBounds);
         }
-        if (mOnContentChangedListener != null) {
+        notifyContentChanged();
+    }
+
+    void notifyContentChanged() {
+        if (mOnContentChangedListener == null) {
+            return;
+        }
+        if (mHandler.getLooper().isCurrentThread()) {
             mOnContentChangedListener.onContentChanged();
+        } else {
+            mHandler.post(() -> mOnContentChangedListener.onContentChanged());
+        }
+    }
+
+    void notifyBoundsChanged(Rect bounds) {
+        if (mOnBoundsChangedListener == null) {
+            return;
+        }
+        if (mHandler.getLooper().isCurrentThread()) {
+            mOnBoundsChangedListener.onBoundsChanged(
+                    bounds.left, bounds.top, bounds.right, bounds.bottom);
+        } else {
+            mHandler.post(() -> mOnBoundsChangedListener.onBoundsChanged(
+                    bounds.left, bounds.top, bounds.right, bounds.bottom));
         }
     }
 
@@ -117,22 +142,16 @@
      *               getHeight()).
      */
     Bitmap toBitmap(Rect bounds) {
+        Log.d(TAG, "exporting with bounds: " + bounds);
         if (mTiles.isEmpty()) {
             return null;
         }
         final RenderNode output = new RenderNode("Bitmap Export");
-        output.setPosition(0, 0, getWidth(), getHeight());
+        output.setPosition(0, 0, bounds.width(), bounds.height());
         RecordingCanvas canvas = output.beginRecording();
-        canvas.translate(-getLeft(), -getTop());
-        // Additional translation to account for the requested bounds
-        canvas.translate(-bounds.left, -bounds.top);
-        canvas.clipRect(bounds);
-        for (ImageTile tile : mTiles) {
-            canvas.save();
-            canvas.translate(tile.getLeft(), tile.getTop());
-            canvas.drawRenderNode(tile.getDisplayList());
-            canvas.restore();
-        }
+        Drawable drawable = getDrawable();
+        drawable.setBounds(bounds);
+        drawable.draw(canvas);
         output.endRecording();
         return HardwareRenderer.createHardwareBitmap(output, bounds.width(), bounds.height());
     }
@@ -162,14 +181,13 @@
     }
 
     void clear() {
-        mBounds.set(0, 0, 0, 0);
+        if (mBounds.isEmpty()) {
+            return;
+        }
+        mBounds.setEmpty();
         mTiles.forEach(ImageTile::close);
         mTiles.clear();
-        if (mOnBoundsChangedListener != null) {
-            mOnBoundsChangedListener.onBoundsChanged(0, 0, 0, 0);
-        }
-        if (mOnContentChangedListener != null) {
-            mOnContentChangedListener.onContentChanged();
-        }
+        notifyBoundsChanged(mBounds);
+        notifyContentChanged();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
index f887151..f8f1d3a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
@@ -144,7 +144,9 @@
                 setAlpha(0f);
                 setTranslationX((getParentWidth() - getWidth()) / 2);
                 setVisibility(View.VISIBLE);
-                animate().alpha(1f).translationX(0).scaleX(1f).scaleY(1f).start();
+                boolean touchOnRight = event.getX() > getParentWidth() / 2;
+                float translateXTarget = touchOnRight ? 0 : getParentWidth() - getWidth();
+                animate().alpha(1f).translationX(translateXTarget).scaleX(1f).scaleY(1f).start();
                 break;
             case MotionEvent.ACTION_MOVE:
                 mLastCropPosition = cropPosition;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
index bb07012..d56c806 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
@@ -50,8 +50,6 @@
 public class ScrollCaptureClient {
     private static final int TILE_SIZE_PX_MAX = 4 * (1024 * 1024);
     private static final int TILES_PER_PAGE = 2; // increase once b/174571735 is addressed
-    private static final int MAX_PAGES = 5;
-    private static final int MAX_IMAGE_COUNT = MAX_PAGES * TILES_PER_PAGE;
 
     @VisibleForTesting
     static final int MATCH_ANY_TASK = ActivityTaskManager.INVALID_TASK_ID;
@@ -66,10 +64,11 @@
         /**
          * Session start should be deferred until UI is active because of resource allocation and
          * potential visible side effects in the target window.
-
+         *
          * @param sessionConsumer listener to receive the session once active
+         * @param maxPages the capture buffer size expressed as a multiple of the content height
          */
-        void start(Consumer<Session> sessionConsumer);
+        void start(Consumer<Session> sessionConsumer, float maxPages);
 
         /**
          * Close the connection.
@@ -196,6 +195,7 @@
         private int mTileWidth;
         private Rect mRequestRect;
         private boolean mStarted;
+        private int mMaxTiles;
 
         private ControllerCallbacks(Consumer<Connection> connectionConsumer) {
             mConnectionConsumer = connectionConsumer;
@@ -285,12 +285,15 @@
         // ScrollCaptureController.Connection
 
         @Override
-        public void start(Consumer<Session> sessionConsumer) {
+        public void start(Consumer<Session> sessionConsumer, float maxPages) {
             if (DEBUG_SCROLL) {
-                Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + ")");
+                Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + ","
+                        + " maxPages=" + maxPages + ")"
+                        + " [maxHeight: " + (mMaxTiles * mTileHeight) + "px]");
             }
+            mMaxTiles = (int) Math.ceil(maxPages * TILES_PER_PAGE);
             mReader = ImageReader.newInstance(mTileWidth, mTileHeight, PixelFormat.RGBA_8888,
-                    MAX_IMAGE_COUNT, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+                    mMaxTiles, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
             mSessionConsumer = sessionConsumer;
             try {
                 mConnection.startCapture(mReader.getSurface());
@@ -345,7 +348,7 @@
 
         @Override
         public int getMaxTiles() {
-            return MAX_IMAGE_COUNT;
+            return mMaxTiles;
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 25438a6..6d64049 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.screenshot;
 
+import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
+
 import android.annotation.IdRes;
 import android.annotation.UiThread;
 import android.content.ComponentName;
@@ -24,16 +26,29 @@
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.ActionMode;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.SearchEvent;
 import android.view.View;
 import android.view.ViewTreeObserver.InternalInsetsInfo;
 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
 import android.view.Window;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
 import android.widget.ImageView;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
+import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
 import com.android.systemui.screenshot.ScrollCaptureClient.Connection;
 import com.android.systemui.screenshot.ScrollCaptureClient.Session;
 import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
@@ -44,13 +59,23 @@
 import java.util.UUID;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
-import java.util.function.Consumer;
 
 /**
  * Interaction controller between the UI and ScrollCaptureClient.
  */
 public class ScrollCaptureController implements OnComputeInternalInsetsListener {
     private static final String TAG = "ScrollCaptureController";
+    private static final float MAX_PAGES_DEFAULT = 3f;
+
+    private static final String SETTING_KEY_MAX_PAGES = "screenshot.scroll_max_pages";
+
+    private static final int UP = -1;
+    private static final int DOWN = 1;
+
+    private int mDirection = DOWN;
+    private boolean mAtBottomEdge;
+    private boolean mAtTopEdge;
+    private Session mSession;
 
     // TODO: Support saving without additional action.
     private enum PendingAction {
@@ -59,7 +84,6 @@
         SAVE
     }
 
-    public static final int MAX_PAGES = 5;
     public static final int MAX_HEIGHT = 12000;
 
     private final Connection mConnection;
@@ -91,7 +115,7 @@
         mBgExecutor = bgExecutor;
         mImageExporter = exporter;
         mUiEventLogger = uiEventLogger;
-        mImageTileSet = new ImageTileSet();
+        mImageTileSet = new ImageTileSet(context.getMainThreadHandler());
     }
 
     /**
@@ -112,8 +136,10 @@
         mCallback = callback;
 
         setContentView(R.layout.long_screenshot);
+        mWindow.setCallback(new WindowCallbacks(this::doFinish));
         mWindow.getDecorView().getViewTreeObserver()
                 .addOnComputeInternalInsetsListener(this);
+
         mPreview = findViewById(R.id.preview);
 
         mSave = findViewById(R.id.save);
@@ -129,7 +155,9 @@
         mEdit.setOnClickListener(this::onClicked);
         mShare.setOnClickListener(this::onClicked);
 
-        mConnection.start(this::startCapture);
+        float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(),
+                SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT);
+        mConnection.start(this::startCapture, maxPages);
     }
 
 
@@ -232,41 +260,82 @@
         return mWindow.findViewById(res);
     }
 
+
+    private void onCaptureResult(CaptureResult result) {
+        Log.d(TAG, "onCaptureResult: " + result);
+        boolean emptyResult = result.captured.height() == 0;
+        boolean partialResult = !emptyResult
+                && result.captured.height() < result.requested.height();
+        boolean finish = false;
+
+        if (partialResult) {
+            // Potentially reached a vertical boundary. Extend in the other direction.
+            switch (mDirection) {
+                case DOWN:
+                    Log.d(TAG, "Reached bottom edge.");
+                    mAtBottomEdge = true;
+                    mDirection = UP;
+                    break;
+                case UP:
+                    Log.d(TAG, "Reached top edge.");
+                    mAtTopEdge = true;
+                    mDirection = DOWN;
+                    break;
+            }
+
+            if (mAtTopEdge && mAtBottomEdge) {
+                Log.d(TAG, "Reached both top and bottom edge, ending.");
+                finish = true;
+            } else {
+                // only reverse if the edge was relatively close to the starting point
+                if (mImageTileSet.getHeight() < mSession.getPageHeight() * 3) {
+                    Log.d(TAG, "Restarting in reverse direction.");
+
+                    // Because of temporary limitations, we cannot just jump to the opposite edge
+                    // and continue there. Instead, clear the results and start over capturing from
+                    // here in the other direction.
+                    mImageTileSet.clear();
+                } else {
+                    Log.d(TAG, "Capture is tall enough, stopping here.");
+                    finish = true;
+                }
+            }
+        }
+
+        if (!emptyResult) {
+            mImageTileSet.addTile(new ImageTile(result.image, result.captured));
+        }
+
+        Log.d(TAG, "bounds: " + mImageTileSet.getLeft() + "," + mImageTileSet.getTop()
+                + " - " +  mImageTileSet.getRight() + "," + mImageTileSet.getBottom()
+                + " (" + mImageTileSet.getWidth() + "x" + mImageTileSet.getHeight() + ")");
+
+
+        // Stop when "too tall"
+        if (mImageTileSet.size() >= mSession.getMaxTiles()
+                || mImageTileSet.getHeight() > MAX_HEIGHT) {
+            Log.d(TAG, "Max height and/or tile count reached.");
+            finish = true;
+        }
+
+        if (finish) {
+            Session session = mSession;
+            mSession = null;
+            Log.d(TAG, "Stop.");
+            mUiExecutor.execute(() -> afterCaptureComplete(session));
+            return;
+        }
+
+        int nextTop = (mDirection == DOWN) ? result.captured.bottom
+                : result.captured.top - mSession.getTileHeight();
+        Log.d(TAG, "requestTile: " + nextTop);
+        mSession.requestTile(nextTop, /* consumer */ this::onCaptureResult);
+    }
+
     private void startCapture(Session session) {
-        Log.d(TAG, "startCapture");
-        Consumer<ScrollCaptureClient.CaptureResult> consumer =
-                new Consumer<ScrollCaptureClient.CaptureResult>() {
-
-                    int mFrameCount = 0;
-                    int mTop = 0;
-
-                    @Override
-                    public void accept(ScrollCaptureClient.CaptureResult result) {
-                        mFrameCount++;
-
-                        boolean emptyFrame = result.captured.height() == 0;
-                        if (!emptyFrame) {
-                            ImageTile tile = new ImageTile(result.image, result.captured);
-                            Log.d(TAG, "Adding tile: " + tile);
-                            mImageTileSet.addTile(tile);
-                            Log.d(TAG, "New dimens: w=" + mImageTileSet.getWidth() + ", "
-                                    + "h=" + mImageTileSet.getHeight());
-                        }
-
-                        if (emptyFrame || mFrameCount >= MAX_PAGES
-                                || mTop + session.getTileHeight() > MAX_HEIGHT) {
-
-                            mUiExecutor.execute(() -> afterCaptureComplete(session));
-                            return;
-                        }
-                        mTop += result.captured.height();
-                        session.requestTile(mTop, /* consumer */ this);
-                    }
-                };
-
-        // fire it up!
-        session.requestTile(0, consumer);
-    };
+        mSession = session;
+        session.requestTile(0, this::onCaptureResult);
+    }
 
     @UiThread
     void afterCaptureComplete(Session session) {
@@ -277,6 +346,133 @@
         } else {
             mPreview.setImageDrawable(mImageTileSet.getDrawable());
             mMagnifierView.setImageTileset(mImageTileSet);
+            mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, 0.5f);
+        }
+    }
+
+    private static class WindowCallbacks implements Window.Callback {
+
+        private final Runnable mFinish;
+
+        WindowCallbacks(Runnable finish) {
+            mFinish = finish;
+        }
+
+        @Override
+        public boolean dispatchKeyEvent(KeyEvent event) {
+            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+                if (DEBUG_INPUT) {
+                    Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK");
+                }
+                mFinish.run();
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+            return false;
+        }
+
+        @Override
+        public boolean dispatchTouchEvent(MotionEvent event) {
+            return false;
+        }
+
+        @Override
+        public boolean dispatchTrackballEvent(MotionEvent event) {
+            return false;
+        }
+
+        @Override
+        public boolean dispatchGenericMotionEvent(MotionEvent event) {
+            return false;
+        }
+
+        @Override
+        public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+            return false;
+        }
+
+        @Nullable
+        @Override
+        public View onCreatePanelView(int featureId) {
+            return null;
+        }
+
+        @Override
+        public boolean onCreatePanelMenu(int featureId, @NonNull Menu menu) {
+            return false;
+        }
+
+        @Override
+        public boolean onPreparePanel(int featureId, @Nullable View view, @NonNull Menu menu) {
+            return false;
+        }
+
+        @Override
+        public boolean onMenuOpened(int featureId, @NonNull Menu menu) {
+            return false;
+        }
+
+        @Override
+        public boolean onMenuItemSelected(int featureId, @NonNull MenuItem item) {
+            return false;
+        }
+
+        @Override
+        public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) {
+        }
+
+        @Override
+        public void onContentChanged() {
+        }
+
+        @Override
+        public void onWindowFocusChanged(boolean hasFocus) {
+        }
+
+        @Override
+        public void onAttachedToWindow() {
+        }
+
+        @Override
+        public void onDetachedFromWindow() {
+        }
+
+        @Override
+        public void onPanelClosed(int featureId, @NonNull Menu menu) {
+        }
+
+        @Override
+        public boolean onSearchRequested() {
+            return false;
+        }
+
+        @Override
+        public boolean onSearchRequested(SearchEvent searchEvent) {
+            return false;
+        }
+
+        @Nullable
+        @Override
+        public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
+            return null;
+        }
+
+        @Nullable
+        @Override
+        public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) {
+            return null;
+        }
+
+        @Override
+        public void onActionModeStarted(ActionMode mode) {
+        }
+
+        @Override
+        public void onActionModeFinished(ActionMode mode) {
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java b/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java
index 72f489b..4ec8eb2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java
@@ -56,7 +56,7 @@
             mNode = new RenderNode("TiledImageDrawable");
         }
         mNode.setPosition(0, 0, mTiles.getWidth(), mTiles.getHeight());
-        Canvas canvas = mNode.beginRecording(mTiles.getWidth(), mTiles.getHeight());
+        Canvas canvas = mNode.beginRecording();
         // Align content (virtual) top/left with 0,0, within the render node
         canvas.translate(-mTiles.getLeft(), -mTiles.getTop());
         for (int i = 0; i < mTiles.size(); i++) {
@@ -79,8 +79,8 @@
         if (canvas.isHardwareAccelerated()) {
             Rect bounds = getBounds();
             canvas.save();
-            canvas.clipRect(bounds);
-            canvas.translate(bounds.left, bounds.top);
+            canvas.clipRect(0, 0, bounds.width(), bounds.height());
+            canvas.translate(-bounds.left, -bounds.top);
             canvas.drawRenderNode(mNode);
             canvas.restore();
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 43bb343..0bfc8e5 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -41,7 +41,7 @@
 import android.util.Log;
 import android.util.MathUtils;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.RestrictedLockUtilsInternal;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 778f813b..7aa41e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -67,10 +67,6 @@
         return mFlagReader.isEnabled(R.bool.flag_brightness_slider);
     }
 
-    public boolean useNewLockscreenAnimations() {
-        return mFlagReader.isEnabled(R.bool.flag_lockscreen_animations);
-    }
-
     public boolean isPeopleTileEnabled() {
         return mFlagReader.isEnabled(R.bool.flag_conversations);
     }
@@ -78,4 +74,8 @@
     public boolean isToastStyleEnabled() {
         return mFlagReader.isEnabled(R.bool.flag_toast_style);
     }
+
+    public boolean isMonetEnabled() {
+        return mFlagReader.isEnabled(R.bool.flag_monet);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index c1feaca..2f0f90d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -11,10 +11,14 @@
 import android.graphics.PorterDuffXfermode
 import android.graphics.RadialGradient
 import android.graphics.Shader
+import android.os.SystemProperties
 import android.util.AttributeSet
 import android.view.View
 import com.android.systemui.Interpolators
 
+val enableLightReveal =
+        SystemProperties.getBoolean("persist.sysui.show_new_screen_on_transitions", false)
+
 /**
  * Provides methods to modify the various properties of a [LightRevealScrim] to reveal between 0% to
  * 100% of the view(s) underneath the scrim.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
index 0465ebf..edcf6d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
@@ -18,23 +18,19 @@
 
 import static android.service.notification.NotificationListenerService.Ranking;
 
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.ENABLE_NAS_FEEDBACK;
+
 import android.app.NotificationManager;
-import android.content.ContentResolver;
 import android.content.Context;
-import android.database.ContentObserver;
-import android.net.Uri;
 import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
 import android.util.Pair;
 
-import androidx.annotation.Nullable;
-
 import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.DeviceConfigProxy;
 
 import javax.inject.Inject;
 
@@ -45,10 +41,10 @@
  * should show an indicator.
  */
 @SysUISingleton
-public class AssistantFeedbackController extends ContentObserver {
-    private final Uri FEEDBACK_URI
-            = Settings.Global.getUriFor(Settings.Global.NOTIFICATION_FEEDBACK_ENABLED);
-    private ContentResolver mResolver;
+public class AssistantFeedbackController {
+    private final Context mContext;
+    private final Handler mHandler;
+    private final DeviceConfigProxy mDeviceConfigProxy;
 
     public static final int STATUS_UNCHANGED = 0;
     public static final int STATUS_ALERTED = 1;
@@ -56,34 +52,39 @@
     public static final int STATUS_PROMOTED = 3;
     public static final int STATUS_DEMOTED = 4;
 
-    private boolean mFeedbackEnabled;
+    private volatile boolean mFeedbackEnabled;
+
+    private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
+            new DeviceConfig.OnPropertiesChangedListener() {
+                @Override
+                public void onPropertiesChanged(DeviceConfig.Properties properties) {
+                    if (properties.getKeyset().contains(ENABLE_NAS_FEEDBACK)) {
+                        mFeedbackEnabled = properties.getBoolean(
+                                ENABLE_NAS_FEEDBACK, false);
+                    }
+                }
+            };
 
     /** Injected constructor */
     @Inject
-    public AssistantFeedbackController(Context context) {
-        super(new Handler(Looper.getMainLooper()));
-        mResolver = context.getContentResolver();
-        mResolver.registerContentObserver(FEEDBACK_URI, false, this, UserHandle.USER_ALL);
-        update(null);
+    public AssistantFeedbackController(@Main Handler handler,
+            Context context, DeviceConfigProxy proxy) {
+        mHandler = handler;
+        mContext = context;
+        mDeviceConfigProxy = proxy;
+        mFeedbackEnabled = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                ENABLE_NAS_FEEDBACK, false);
+        mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+                this::postToHandler, mPropertiesChangedListener);
     }
 
-    @Override
-    public void onChange(boolean selfChange, @Nullable Uri uri, int flags) {
-        update(uri);
-    }
-
-    @VisibleForTesting
-    public void update(@Nullable Uri uri) {
-        if (uri == null || FEEDBACK_URI.equals(uri)) {
-            mFeedbackEnabled = Settings.Global.getInt(mResolver,
-                    Settings.Global.NOTIFICATION_FEEDBACK_ENABLED, 0)
-                    != 0;
-        }
+    private void postToHandler(Runnable r) {
+        this.mHandler.post(r);
     }
 
     /**
-     * Determines whether to show any user controls related to the assistant. This is based on the
-     * settings flag {@link Settings.Global.NOTIFICATION_FEEDBACK_ENABLED}
+     * Determines whether to show any user controls related to the assistant based on the
+     * DeviceConfig flag value
      */
     public boolean isFeedbackEnabled() {
         return mFeedbackEnabled;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 85d8df8..8c2fa33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -29,7 +29,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.doze.DozeScreenState;
-import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.tuner.TunerService;
 
@@ -55,7 +54,6 @@
     private final AlwaysOnDisplayPolicy mAlwaysOnPolicy;
     private final Resources mResources;
     private final BatteryController mBatteryController;
-    private final FeatureFlags mFeatureFlags;
 
     private boolean mDozeAlwaysOn;
     private boolean mControlScreenOffAnimation;
@@ -67,8 +65,7 @@
             AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
             PowerManager powerManager,
             BatteryController batteryController,
-            TunerService tunerService,
-            FeatureFlags featureFlags) {
+            TunerService tunerService) {
         mResources = resources;
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
         mAlwaysOnPolicy = alwaysOnDisplayPolicy;
@@ -77,7 +74,6 @@
         mControlScreenOffAnimation = !getDisplayNeedsBlanking();
         mPowerManager = powerManager;
         mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation);
-        mFeatureFlags = featureFlags;
 
         tunerService.addTunable(
                 this,
@@ -204,7 +200,8 @@
      * then abruptly showing AOD.
      */
     public boolean shouldControlUnlockedScreenOff() {
-        return getAlwaysOn() && mFeatureFlags.useNewLockscreenAnimations();
+        return getAlwaysOn() && SystemProperties.getBoolean(
+                "persist.sysui.show_new_screen_on_transitions", false);
     }
 
     private boolean getBoolean(String propName, int resId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 57a64e4..e0df4f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -27,6 +27,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView;
 
 /**
  * Utility class to calculate the clock position and top padding of notifications on Keyguard.
@@ -55,6 +56,12 @@
     private int mKeyguardStatusHeight;
 
     /**
+     * Height of {@link KeyguardUserSwitcherListView} when it
+     * is closed and only the current user's icon is visible.
+     */
+    private int mKeyguardUserSwitcherHeight;
+
+    /**
      * Preferred Y position of clock.
      */
     private int mClockPreferredY;
@@ -173,17 +180,20 @@
      * Sets up algorithm values.
      */
     public void setup(int statusBarMinHeight, int maxShadeBottom, int notificationStackHeight,
-            float panelExpansion, int parentHeight, int keyguardStatusHeight, int clockPreferredY,
-            boolean hasCustomClock, boolean hasVisibleNotifs, float dark, float emptyDragAmount,
-            boolean bypassEnabled, int unlockedStackScrollerPadding, boolean showLockIcon,
-            float qsExpansion, int cutoutTopInset) {
+            float panelExpansion, int parentHeight, int keyguardStatusHeight,
+            int keyguardUserSwitcherHeight, int clockPreferredY, boolean hasCustomClock,
+            boolean hasVisibleNotifs, float dark, float emptyDragAmount, boolean bypassEnabled,
+            int unlockedStackScrollerPadding, boolean showLockIcon, float qsExpansion,
+            int cutoutTopInset) {
         mMinTopMargin = statusBarMinHeight + (showLockIcon
-                ? mContainerTopPaddingWithLockIcon : mContainerTopPaddingWithoutLockIcon);
+                ? mContainerTopPaddingWithLockIcon : mContainerTopPaddingWithoutLockIcon)
+                + keyguardUserSwitcherHeight;
         mMaxShadeBottom = maxShadeBottom;
         mNotificationStackHeight = notificationStackHeight;
         mPanelExpansion = panelExpansion;
         mHeight = parentHeight;
         mKeyguardStatusHeight = keyguardStatusHeight;
+        mKeyguardUserSwitcherHeight = keyguardUserSwitcherHeight;
         mClockPreferredY = clockPreferredY;
         mHasCustomClock = hasCustomClock;
         mHasVisibleNotifs = hasVisibleNotifs;
@@ -246,7 +256,8 @@
         final int availableHeight = mMaxShadeBottom - mMinTopMargin;
         final int containerCenter = mMinTopMargin + availableHeight / 2;
 
-        float y = containerCenter - mKeyguardStatusHeight * CLOCK_HEIGHT_WEIGHT
+        float y = containerCenter
+                - (mKeyguardStatusHeight + mKeyguardUserSwitcherHeight) * CLOCK_HEIGHT_WEIGHT
                 - mClockNotificationsMargin - mNotificationStackHeight / 2;
         if (y < mMinTopMargin) {
             y = mMinTopMargin;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 5f547b5..33798d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
 
 import android.annotation.ColorInt;
@@ -25,6 +26,7 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.UserManager;
 import android.util.AttributeSet;
 import android.util.Pair;
 import android.util.TypedValue;
@@ -45,18 +47,15 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.qs.QSDetailDisplayer;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -75,18 +74,16 @@
 
     private boolean mShowPercentAvailable;
     private boolean mBatteryCharging;
-    private boolean mKeyguardUserSwitcherShowing;
     private boolean mBatteryListening;
 
     private TextView mCarrierLabel;
-    private MultiUserSwitch mMultiUserSwitch;
     private ImageView mMultiUserAvatar;
     private BatteryMeterView mBatteryView;
     private StatusIconContainer mStatusIconContainer;
 
     private BatteryController mBatteryController;
-    private KeyguardUserSwitcher mKeyguardUserSwitcher;
-    private UserSwitcherController mUserSwitcherController;
+    private boolean mKeyguardUserSwitcherEnabled;
+    private final UserManager mUserManager;
 
     private int mSystemIconsSwitcherHiddenExpandedMargin;
     private int mSystemIconsBaseMargin;
@@ -109,13 +106,13 @@
 
     public KeyguardStatusBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mUserManager = UserManager.get(getContext());
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         mSystemIconsContainer = findViewById(R.id.system_icons_container);
-        mMultiUserSwitch = findViewById(R.id.multi_user_switch);
         mMultiUserAvatar = findViewById(R.id.multi_user_avatar);
         mCarrierLabel = findViewById(R.id.keyguard_carrier_text);
         mBatteryView = mSystemIconsContainer.findViewById(R.id.battery);
@@ -124,7 +121,6 @@
         mStatusIconContainer = findViewById(R.id.statusIcons);
 
         loadDimens();
-        updateUserSwitcher();
         mBatteryController = Dependency.get(BatteryController.class);
     }
 
@@ -137,14 +133,6 @@
                 R.dimen.multi_user_avatar_keyguard_size);
         mMultiUserAvatar.setLayoutParams(lp);
 
-        // Multi-user switch
-        lp = (MarginLayoutParams) mMultiUserSwitch.getLayoutParams();
-        lp.width = getResources().getDimensionPixelSize(
-                R.dimen.multi_user_switch_width_keyguard);
-        lp.setMarginEnd(getResources().getDimensionPixelSize(
-                R.dimen.multi_user_switch_keyguard_margin));
-        mMultiUserSwitch.setLayoutParams(lp);
-
         // System icons
         lp = (MarginLayoutParams) mSystemIconsContainer.getLayoutParams();
         lp.setMarginStart(getResources().getDimensionPixelSize(
@@ -194,22 +182,28 @@
     }
 
     private void updateVisibilities() {
-        if (mMultiUserSwitch.getParent() != mStatusIconArea && !mKeyguardUserSwitcherShowing) {
-            if (mMultiUserSwitch.getParent() != null) {
-                getOverlay().remove(mMultiUserSwitch);
+        if (mMultiUserAvatar.getParent() != mStatusIconArea
+                && !mKeyguardUserSwitcherEnabled) {
+            if (mMultiUserAvatar.getParent() != null) {
+                getOverlay().remove(mMultiUserAvatar);
             }
-            mStatusIconArea.addView(mMultiUserSwitch, 0);
-        } else if (mMultiUserSwitch.getParent() == mStatusIconArea && mKeyguardUserSwitcherShowing) {
-            mStatusIconArea.removeView(mMultiUserSwitch);
+            mStatusIconArea.addView(mMultiUserAvatar, 0);
+        } else if (mMultiUserAvatar.getParent() == mStatusIconArea
+                && mKeyguardUserSwitcherEnabled) {
+            mStatusIconArea.removeView(mMultiUserAvatar);
         }
-        if (mKeyguardUserSwitcher == null) {
+        if (!mKeyguardUserSwitcherEnabled) {
             // If we have no keyguard switcher, the screen width is under 600dp. In this case,
             // we only show the multi-user switch if it's enabled through UserManager as well as
             // by the user.
-            if (mMultiUserSwitch.isMultiUserEnabled()) {
-                mMultiUserSwitch.setVisibility(View.VISIBLE);
+            // TODO(b/138661450) Move IPC calls to background
+            boolean isMultiUserEnabled = whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled(
+                    mContext.getResources().getBoolean(
+                            R.bool.qs_show_user_switcher_for_single_user)));
+            if (isMultiUserEnabled) {
+                mMultiUserAvatar.setVisibility(View.VISIBLE);
             } else {
-                mMultiUserSwitch.setVisibility(View.GONE);
+                mMultiUserAvatar.setVisibility(View.GONE);
             }
         }
         mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable);
@@ -220,11 +214,12 @@
                 (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
         // If the avatar icon is gone, we need to have some end margin to display the system icons
         // correctly.
-        int baseMarginEnd = mMultiUserSwitch.getVisibility() == View.GONE
+        int baseMarginEnd = mMultiUserAvatar.getVisibility() == View.GONE
                 ? mSystemIconsBaseMargin
                 : 0;
-        int marginEnd = mKeyguardUserSwitcherShowing ? mSystemIconsSwitcherHiddenExpandedMargin :
-                baseMarginEnd;
+        int marginEnd =
+                mKeyguardUserSwitcherEnabled ? mSystemIconsSwitcherHiddenExpandedMargin
+                        : baseMarginEnd;
         marginEnd = calculateMargin(marginEnd, mPadding.second);
         if (marginEnd != lp.getMarginEnd()) {
             lp.setMarginEnd(marginEnd);
@@ -334,20 +329,11 @@
         }
     }
 
-    private void updateUserSwitcher() {
-        boolean keyguardSwitcherAvailable = mKeyguardUserSwitcher != null;
-        mMultiUserSwitch.setClickable(keyguardSwitcherAvailable);
-        mMultiUserSwitch.setFocusable(keyguardSwitcherAvailable);
-        mMultiUserSwitch.setKeyguardMode(keyguardSwitcherAvailable);
-    }
-
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         UserInfoController userInfoController = Dependency.get(UserInfoController.class);
         userInfoController.addCallback(this);
-        mUserSwitcherController = Dependency.get(UserSwitcherController.class);
-        mMultiUserSwitch.setUserSwitcherController(mUserSwitcherController);
         userInfoController.reloadUserInfo();
         Dependency.get(ConfigurationController.class).addCallback(this);
         mIconManager = new TintedIconManager(findViewById(R.id.statusIcons),
@@ -369,11 +355,6 @@
         mMultiUserAvatar.setImageDrawable(picture);
     }
 
-    /** */
-    public void setQSDetailDisplayer(QSDetailDisplayer detailDisplayer) {
-        mMultiUserSwitch.setQSDetailDisplayer(detailDisplayer);
-    }
-
     @Override
     public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
         if (mBatteryCharging != charging) {
@@ -387,54 +368,42 @@
         // could not care less
     }
 
-    public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
-        mKeyguardUserSwitcher = keyguardUserSwitcher;
-        mMultiUserSwitch.setKeyguardUserSwitcher(keyguardUserSwitcher);
-        updateUserSwitcher();
-    }
-
-    public void setKeyguardUserSwitcherShowing(boolean showing, boolean animate) {
-        mKeyguardUserSwitcherShowing = showing;
-        if (animate) {
-            animateNextLayoutChange();
-        }
-        updateVisibilities();
-        updateLayoutConsideringCutout();
-        updateSystemIconsLayoutParams();
+    public void setKeyguardUserSwitcherEnabled(boolean enabled) {
+        mKeyguardUserSwitcherEnabled = enabled;
     }
 
     private void animateNextLayoutChange() {
         final int systemIconsCurrentX = mSystemIconsContainer.getLeft();
-        final boolean userSwitcherVisible = mMultiUserSwitch.getParent() == mStatusIconArea;
+        final boolean userAvatarVisible = mMultiUserAvatar.getParent() == mStatusIconArea;
         getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
             @Override
             public boolean onPreDraw() {
                 getViewTreeObserver().removeOnPreDrawListener(this);
-                boolean userSwitcherHiding = userSwitcherVisible
-                        && mMultiUserSwitch.getParent() != mStatusIconArea;
+                boolean userAvatarHiding = userAvatarVisible
+                        && mMultiUserAvatar.getParent() != mStatusIconArea;
                 mSystemIconsContainer.setX(systemIconsCurrentX);
                 mSystemIconsContainer.animate()
                         .translationX(0)
                         .setDuration(400)
-                        .setStartDelay(userSwitcherHiding ? 300 : 0)
+                        .setStartDelay(userAvatarHiding ? 300 : 0)
                         .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
                         .start();
-                if (userSwitcherHiding) {
-                    getOverlay().add(mMultiUserSwitch);
-                    mMultiUserSwitch.animate()
+                if (userAvatarHiding) {
+                    getOverlay().add(mMultiUserAvatar);
+                    mMultiUserAvatar.animate()
                             .alpha(0f)
                             .setDuration(300)
                             .setStartDelay(0)
                             .setInterpolator(Interpolators.ALPHA_OUT)
                             .withEndAction(() -> {
-                                mMultiUserSwitch.setAlpha(1f);
-                                getOverlay().remove(mMultiUserSwitch);
+                                mMultiUserAvatar.setAlpha(1f);
+                                getOverlay().remove(mMultiUserAvatar);
                             })
                             .start();
 
                 } else {
-                    mMultiUserSwitch.setAlpha(0f);
-                    mMultiUserSwitch.animate()
+                    mMultiUserAvatar.setAlpha(0f);
+                    mMultiUserAvatar.animate()
                             .alpha(1f)
                             .setDuration(300)
                             .setStartDelay(200)
@@ -452,8 +421,8 @@
         if (visibility != View.VISIBLE) {
             mSystemIconsContainer.animate().cancel();
             mSystemIconsContainer.setTranslationX(0);
-            mMultiUserSwitch.animate().cancel();
-            mMultiUserSwitch.setAlpha(1f);
+            mMultiUserAvatar.animate().cancel();
+            mMultiUserAvatar.setAlpha(1f);
         } else {
             updateVisibilities();
             updateSystemIconsLayoutParams();
@@ -523,9 +492,9 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("KeyguardStatusBarView:");
         pw.println("  mBatteryCharging: " + mBatteryCharging);
-        pw.println("  mKeyguardUserSwitcherShowing: " + mKeyguardUserSwitcherShowing);
         pw.println("  mBatteryListening: " + mBatteryListening);
         pw.println("  mLayoutState: " + mLayoutState);
+        pw.println("  mKeyguardUserSwitcherEnabled: " + mKeyguardUserSwitcherEnabled);
         if (mBatteryView != null) {
             mBatteryView.dump(fd, pw, args);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index 480d3f4..d9cb9ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -35,7 +35,6 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.qs.QSDetailDisplayer;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 
 /**
@@ -44,8 +43,6 @@
 public class MultiUserSwitch extends FrameLayout implements View.OnClickListener {
 
     protected QSDetailDisplayer mQSDetailDisplayer;
-    private KeyguardUserSwitcher mKeyguardUserSwitcher;
-    private boolean mKeyguardMode;
     private UserSwitcherController.BaseUserAdapter mUserListener;
 
     final UserManager mUserManager;
@@ -85,15 +82,6 @@
         refreshContentDescription();
     }
 
-    public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
-        mKeyguardUserSwitcher = keyguardUserSwitcher;
-    }
-
-    public void setKeyguardMode(boolean keyguardShowing) {
-        mKeyguardMode = keyguardShowing;
-        registerListener();
-    }
-
     public boolean isMultiUserEnabled() {
         // TODO(b/138661450) Move IPC calls to background
         return whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled(
@@ -123,11 +111,7 @@
 
     @Override
     public void onClick(View v) {
-        if (mKeyguardMode) {
-            if (mKeyguardUserSwitcher != null) {
-                mKeyguardUserSwitcher.show(true /* animate */);
-            }
-        } else if (mQSDetailDisplayer != null && mUserSwitcherController != null) {
+        if (mQSDetailDisplayer != null && mUserSwitcherController != null) {
             View center = getChildCount() > 0 ? getChildAt(0) : this;
 
             int[] tmpInt = new int[2];
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index e0ef3b6..3b09eda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -48,6 +48,7 @@
 import android.os.Bundle;
 import android.os.PowerManager;
 import android.os.SystemClock;
+import android.os.UserManager;
 import android.util.Log;
 import android.util.MathUtils;
 import android.view.DisplayCutout;
@@ -57,6 +58,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
+import android.view.ViewStub;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityManager;
@@ -76,6 +78,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
+import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
@@ -95,7 +98,6 @@
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.qs.QSDetailDisplayer;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.GestureRecorder;
@@ -132,8 +134,10 @@
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.util.Utils;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 
 import java.io.FileDescriptor;
@@ -283,6 +287,22 @@
                 }
     };
 
+    final KeyguardUserSwitcherController.KeyguardUserSwitcherListener
+            mKeyguardUserSwitcherListener =
+            new KeyguardUserSwitcherController.KeyguardUserSwitcherListener() {
+                @Override
+                public void onKeyguardUserSwitcherChanged(boolean open) {
+                    if (mKeyguardUserSwitcherController != null
+                            && mKeyguardUserSwitcherController.isSimpleUserSwitcher()) {
+                        return;
+                    }
+
+                    updateUserSwitcherVisibility(open
+                            && mKeyguardStateController.isShowing()
+                            && !mKeyguardStateController.isKeyguardFadingAway());
+                }
+            };
+
     private final LayoutInflater mLayoutInflater;
     private final PowerManager mPowerManager;
     private final AccessibilityManager mAccessibilityManager;
@@ -295,7 +315,7 @@
     private final MediaHierarchyManager mMediaHierarchyManager;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
-    private final QSDetailDisplayer mQSDetailDisplayer;
+    private final KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
     private final FeatureFlags mFeatureFlags;
     private final ScrimController mScrimController;
     private final ControlsComponent mControlsComponent;
@@ -307,7 +327,7 @@
     private int mMaxAllowedKeyguardNotifications;
 
     private KeyguardAffordanceHelper mAffordanceHelper;
-    private KeyguardUserSwitcher mKeyguardUserSwitcher;
+    private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
     private KeyguardStatusBarView mKeyguardStatusBar;
     private ViewGroup mBigClockContainer;
     private QS mQs;
@@ -332,6 +352,7 @@
     private boolean mQsExpandedWhenExpandingStarted;
     private boolean mQsFullyExpanded;
     private boolean mKeyguardShowing;
+    private boolean mKeyguardUserSwitcherEnabled;
     private boolean mDozing;
     private boolean mDozingOnDown;
     private int mBarState;
@@ -464,6 +485,7 @@
 
     private final CommandQueue mCommandQueue;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
+    private final UserManager mUserManager;
     private final ShadeController mShadeController;
     private final MediaDataManager mMediaDataManager;
     private int mDisplayId;
@@ -556,11 +578,12 @@
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             NotificationStackScrollLayoutController notificationStackScrollLayoutController,
             KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
+            KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
             NotificationGroupManagerLegacy groupManager,
             NotificationIconAreaController notificationIconAreaController,
             AuthController authController,
-            QSDetailDisplayer qsDetailDisplayer,
             ScrimController scrimController,
+            UserManager userManager,
             MediaDataManager mediaDataManager,
             AmbientState ambientState,
             FeatureFlags featureFlags,
@@ -581,8 +604,10 @@
         mGroupManager = groupManager;
         mNotificationIconAreaController = notificationIconAreaController;
         mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
-        mQSDetailDisplayer = qsDetailDisplayer;
         mFeatureFlags = featureFlags;
+        mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory;
+        mKeyguardUserSwitcherEnabled = mResources.getBoolean(
+                com.android.internal.R.bool.config_keyguardUserSwitcher);
         mView.setWillNotDraw(!DEBUG);
         mLayoutInflater = layoutInflater;
         mFalsingManager = falsingManager;
@@ -598,6 +623,7 @@
         mDozeParameters = dozeParameters;
         mBiometricUnlockController = biometricUnlockController;
         mScrimController = scrimController;
+        mUserManager = userManager;
         mMediaDataManager = mediaDataManager;
         mControlsComponent = controlsComponent;
         pulseExpansionHandler.setPulseExpandAbortListener(() -> {
@@ -660,9 +686,17 @@
     private void onFinishInflate() {
         loadDimens();
         mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
-        mKeyguardStatusBar.setQSDetailDisplayer(mQSDetailDisplayer);
         mBigClockContainer = mView.findViewById(R.id.big_clock_container);
-        updateViewControllers(mView.findViewById(R.id.keyguard_status_view));
+
+        KeyguardUserSwitcherView keyguardUserSwitcherView = null;
+
+        if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled()) {
+            ViewStub userSwitcherStub = mView.findViewById(R.id.keyguard_user_switcher_stub);
+            keyguardUserSwitcherView = (KeyguardUserSwitcherView) userSwitcherStub.inflate();
+        }
+
+        updateViewControllers(mView.findViewById(R.id.keyguard_status_view),
+                keyguardUserSwitcherView);
         mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
         NotificationStackScrollLayout stackScrollLayout = mView.findViewById(
                 R.id.notification_stack_scroller);
@@ -735,7 +769,8 @@
                 R.dimen.heads_up_status_bar_padding);
     }
 
-    private void updateViewControllers(KeyguardStatusView keyguardStatusView) {
+    private void updateViewControllers(KeyguardStatusView keyguardStatusView,
+            KeyguardUserSwitcherView keyguardUserSwitcherView) {
         // Re-associate the KeyguardStatusViewController
         KeyguardStatusViewComponent statusViewComponent =
                 mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
@@ -746,6 +781,28 @@
         KeyguardClockSwitchController keyguardClockSwitchController =
                 statusViewComponent.getKeyguardClockSwitchController();
         keyguardClockSwitchController.setBigClockContainer(mBigClockContainer);
+
+        if (mKeyguardUserSwitcherController != null) {
+            // Try to close the switcher so that callbacks are triggered if necessary.
+            // Otherwise, NPV can get into a state where some of the views are still hidden
+            mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(false);
+            mKeyguardUserSwitcherController.removeCallback();
+        }
+
+        // Re-associate the KeyguardUserSwitcherController
+        if (keyguardUserSwitcherView != null) {
+            KeyguardUserSwitcherComponent userSwitcherComponent =
+                    mKeyguardUserSwitcherComponentFactory.build(keyguardUserSwitcherView);
+
+            mKeyguardUserSwitcherController =
+                    userSwitcherComponent.getKeyguardUserSwitcherController();
+            mKeyguardUserSwitcherController.setCallback(mKeyguardUserSwitcherListener);
+            mKeyguardUserSwitcherController.init();
+            mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(true);
+        } else {
+            mKeyguardUserSwitcherController = null;
+            mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(false);
+        }
     }
 
     /**
@@ -783,18 +840,13 @@
             mNotificationStackScrollLayoutController.setLayoutParams(lp);
         }
 
-        if (shouldUseSplitNotificationShade()) {
+        if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
             // In order to change the constraints at runtime, all children of the Constraint Layout
             // must have ids.
             ensureAllViewsHaveIds(mNotificationContainerParent);
         }
     }
 
-    private boolean shouldUseSplitNotificationShade() {
-        return mFeatureFlags.isTwoColumnNotificationShadeEnabled()
-                && mResources.getBoolean(R.bool.config_use_split_notification_shade);
-    }
-
     private static void ensureAllViewsHaveIds(ViewGroup parentView) {
         for (int i = 0; i < parentView.getChildCount(); i++) {
             View childView = parentView.getChildAt(i);
@@ -805,6 +857,7 @@
     }
 
     private void reInflateViews() {
+        if (DEBUG) Log.d(TAG, "reInflateViews");
         // Re-inflate the status view group.
         KeyguardStatusView keyguardStatusView = mView.findViewById(R.id.keyguard_status_view);
         int index = mView.indexOfChild(keyguardStatusView);
@@ -813,8 +866,27 @@
                 R.layout.keyguard_status_view, mView, false);
         mView.addView(keyguardStatusView, index);
 
+        // Re-inflate the keyguard user switcher group.
+        boolean showUserSwitcher =
+                mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled();
+        KeyguardUserSwitcherView keyguardUserSwitcherView = mView.findViewById(
+                R.id.keyguard_user_switcher_view);
+        if (keyguardUserSwitcherView != null) {
+            index = mView.indexOfChild(keyguardUserSwitcherView);
+            mView.removeView(keyguardUserSwitcherView);
+            if (showUserSwitcher) {
+                keyguardUserSwitcherView = (KeyguardUserSwitcherView) mLayoutInflater.inflate(
+                        R.layout.keyguard_user_switcher, mView, false);
+                mView.addView(keyguardUserSwitcherView, index);
+            }
+        } else if (showUserSwitcher) {
+            // It's possible the user switcher was never inflated if the configuration changed
+            ViewStub userSwitcherStub = mView.findViewById(R.id.keyguard_user_switcher_stub);
+            keyguardUserSwitcherView = (KeyguardUserSwitcherView) userSwitcherStub.inflate();
+        }
+
         mBigClockContainer.removeAllViews();
-        updateViewControllers(keyguardStatusView);
+        updateViewControllers(keyguardStatusView, keyguardUserSwitcherView);
 
         // Update keyguard bottom area
         index = mView.indexOfChild(mKeyguardBottomArea);
@@ -838,6 +910,13 @@
                 false,
                 false,
                 mBarState);
+        if (mKeyguardUserSwitcherController != null) {
+            mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
+                    mBarState,
+                    false,
+                    false,
+                    mBarState);
+        }
         setKeyguardBottomAreaVisibility(mBarState, false);
     }
 
@@ -942,6 +1021,8 @@
                             ? mKeyguardStatusViewController.getHeight()
                             : (int) (mKeyguardStatusViewController.getHeight()
                                     - mShelfHeight / 2.0f - mDarkIconSize / 2.0f),
+                    mKeyguardUserSwitcherController == null
+                            ? 0 : mKeyguardUserSwitcherController.getUserIconHeight(),
                     clockPreferredY, hasCustomClock(),
                     hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
                     bypassEnabled, getUnlockedStackScrollerPadding(),
@@ -952,6 +1033,13 @@
             mKeyguardStatusViewController.updatePosition(
                     mClockPositionResult.clockX, mClockPositionResult.clockY,
                     mClockPositionResult.clockScale, animateClock);
+            if (mKeyguardUserSwitcherController != null) {
+                mKeyguardUserSwitcherController.updatePosition(
+                        mClockPositionResult.clockX,
+                        mClockPositionResult.clockY
+                                - mKeyguardUserSwitcherController.getUserIconHeight(),
+                        animateClock);
+            }
             updateNotificationTranslucency();
             updateClock();
             stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
@@ -1092,6 +1180,9 @@
 
     private void updateClock() {
         mKeyguardStatusViewController.setAlpha(mClockPositionResult.clockAlpha);
+        if (mKeyguardUserSwitcherController != null) {
+            mKeyguardUserSwitcherController.setAlpha(mClockPositionResult.clockAlpha);
+        }
     }
 
     public void animateToFullShade(long delay) {
@@ -1773,8 +1864,9 @@
                 mBarState != KEYGUARD && (!mQsExpanded
                         || mQsExpansionFromOverscroll));
 
-        if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
-            mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
+        if (mKeyguardUserSwitcherController != null && mQsExpanded
+                && !mStackScrollerOverscrolling) {
+            mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(true);
         }
         if (mQs == null) return;
         mQs.setExpanded(mQsExpanded);
@@ -2608,10 +2700,6 @@
         }
     }
 
-    public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
-        mKeyguardUserSwitcher = keyguardUserSwitcher;
-    }
-
     public void onScreenTurningOn() {
         mKeyguardStatusViewController.dozeTimeTick();
     }
@@ -3078,6 +3166,13 @@
                 true /* keyguardFadingAway */,
                 false /* goingToFullShade */,
                 mBarState);
+        if (mKeyguardUserSwitcherController != null) {
+            mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
+                    mBarState,
+                    true /* keyguardFadingAway */,
+                    false /* goingToFullShade */,
+                    mBarState);
+        }
     }
 
     /**
@@ -3320,6 +3415,44 @@
         return mNotificationStackScrollLayoutController;
     }
 
+    /**
+     * Close the keyguard user switcher if it is open and capable of closing.
+     *
+     * Has no effect if user switcher isn't supported, if the user switcher is already closed, or
+     * if the user switcher uses "simple" mode. The simple user switcher cannot be closed.
+     *
+     * @return true if the keyguard user switcher was open, and is now closed
+     */
+    public boolean closeUserSwitcherIfOpen() {
+        if (mKeyguardUserSwitcherController != null) {
+            return mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(
+                    true /* animate */);
+        }
+        return false;
+    }
+
+    private void updateUserSwitcherVisibility(boolean open) {
+        if (open) {
+            animateKeyguardStatusBarOut();
+            mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+                    mBarState,
+                    true /* keyguardFadingAway */,
+                    true /* goingToFullShade */,
+                    mBarState);
+            setKeyguardBottomAreaVisibility(mBarState, true);
+            mNotificationContainerParent.setVisibility(View.GONE);
+        } else {
+            animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+            mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+                    StatusBarState.KEYGUARD,
+                    false,
+                    false,
+                    StatusBarState.SHADE_LOCKED);
+            setKeyguardBottomAreaVisibility(mBarState, false);
+            mNotificationContainerParent.setVisibility(View.VISIBLE);
+        }
+    }
+
     private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
         @Override
         public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
@@ -3620,6 +3753,7 @@
     private class ConfigurationListener implements ConfigurationController.ConfigurationListener {
         @Override
         public void onThemeChanged() {
+            if (DEBUG) Log.d(TAG, "onThemeChanged");
             final int themeResId = mView.getContext().getThemeResId();
             if (mThemeResId == themeResId) {
                 return;
@@ -3631,11 +3765,15 @@
 
         @Override
         public void onOverlayChanged() {
+            if (DEBUG) Log.d(TAG, "onOverlayChanged");
             reInflateViews();
         }
 
         @Override
-        public void onUiModeChanged() {}
+        public void onDensityOrFontScaleChanged() {
+            if (DEBUG) Log.d(TAG, "onDensityOrFontScaleChanged");
+            reInflateViews();
+        }
     }
 
     private class StatusBarStateListener implements StateListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index b367406..e394ebc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -22,8 +22,6 @@
 import android.graphics.Canvas;
 import android.util.AttributeSet;
 import android.view.View;
-import android.view.ViewStub;
-import android.view.ViewStub.OnInflateListener;
 import android.view.WindowInsets;
 import android.widget.FrameLayout;
 
@@ -44,14 +42,11 @@
  * The container with notification stack scroller and quick settings inside.
  */
 public class NotificationsQuickSettingsContainer extends ConstraintLayout
-        implements OnInflateListener, FragmentListener,
-        AboveShelfObserver.HasViewAboveShelfChangedListener {
+        implements FragmentListener, AboveShelfObserver.HasViewAboveShelfChangedListener {
 
     private FrameLayout mQsFrame;
-    private View mUserSwitcher;
     private NotificationStackScrollLayout mStackScroller;
     private View mKeyguardStatusBar;
-    private boolean mInflated;
     private boolean mQsExpanded;
     private boolean mCustomizerAnimating;
 
@@ -73,9 +68,6 @@
         mStackScroller = findViewById(R.id.notification_stack_scroller);
         mStackScrollerMargin = ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin;
         mKeyguardStatusBar = findViewById(R.id.keyguard_header);
-        ViewStub userSwitcher = findViewById(R.id.keyguard_user_switcher);
-        userSwitcher.setOnInflateListener(this);
-        mUserSwitcher = userSwitcher;
     }
 
     @Override
@@ -119,10 +111,6 @@
         // touches first but the panel gets drawn above.
         mDrawingOrderedChildren.clear();
         mLayoutDrawingOrder.clear();
-        if (mInflated && mUserSwitcher.getVisibility() == View.VISIBLE) {
-            mDrawingOrderedChildren.add(mUserSwitcher);
-            mLayoutDrawingOrder.add(mUserSwitcher);
-        }
         if (mKeyguardStatusBar.getVisibility() == View.VISIBLE) {
             mDrawingOrderedChildren.add(mKeyguardStatusBar);
             mLayoutDrawingOrder.add(mKeyguardStatusBar);
@@ -158,14 +146,6 @@
     }
 
     @Override
-    public void onInflate(ViewStub stub, View inflated) {
-        if (stub == mUserSwitcher) {
-            mUserSwitcher = inflated;
-            mInflated = true;
-        }
-    }
-
-    @Override
     public void onFragmentViewCreated(String tag, Fragment fragment) {
         QS container = (QS) fragment;
         container.setContainer(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 1e19bee..e63902f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -35,6 +35,7 @@
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
+import static com.android.systemui.statusbar.LightRevealScrimKt.getEnableLightReveal;
 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
@@ -89,7 +90,6 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.provider.Settings;
@@ -180,7 +180,6 @@
 import com.android.systemui.statusbar.BackDropView;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CrossFadeHelper;
-import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.GestureRecorder;
 import com.android.systemui.statusbar.KeyboardShortcuts;
 import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -226,7 +225,6 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
@@ -442,7 +440,6 @@
     private final KeyguardViewMediator mKeyguardViewMediator;
     protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     private final BrightnessSlider.Factory mBrightnessSliderFactory;
-    private final FeatureFlags mFeatureFlags;
 
     private final List<ExpansionChangedListener> mExpansionChangedListeners;
 
@@ -623,7 +620,6 @@
         }
     };
 
-    private KeyguardUserSwitcher mKeyguardUserSwitcher;
     private final UserSwitcherController mUserSwitcherController;
     private final NetworkController mNetworkController;
     private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
@@ -766,8 +762,7 @@
             Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
             NotificationIconAreaController notificationIconAreaController,
-            BrightnessSlider.Factory brightnessSliderFactory,
-            FeatureFlags featureFlags) {
+            BrightnessSlider.Factory brightnessSliderFactory) {
         super(context);
         mNotificationsController = notificationsController;
         mLightBarController = lightBarController;
@@ -845,7 +840,6 @@
         mDemoModeController = demoModeController;
         mNotificationIconAreaController = notificationIconAreaController;
         mBrightnessSliderFactory = brightnessSliderFactory;
-        mFeatureFlags = featureFlags;
 
         mExpansionChangedListeners = new ArrayList<>();
 
@@ -1187,11 +1181,9 @@
 
         mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
 
-        if (mFeatureFlags.useNewLockscreenAnimations() && mDozeParameters.getAlwaysOn()) {
+        if (getEnableLightReveal()) {
             mLightRevealScrim.setVisibility(View.VISIBLE);
             mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE);
-        } else {
-            mLightRevealScrim.setVisibility(View.GONE);
         }
 
         mNotificationPanelViewController.initDependencies(
@@ -1212,9 +1204,6 @@
         });
 
         mNotificationPanelViewController.setUserSetupComplete(mUserSetup);
-        if (UserManager.get(mContext).isUserSwitcherEnabled()) {
-            createUserSwitcher();
-        }
 
         // Set up the quick settings tile panel
         final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
@@ -1441,9 +1430,6 @@
         // TODO: Bring these out of StatusBar.
         mUserInfoControllerImpl.onDensityOrFontScaleChanged();
         mUserSwitcherController.onDensityOrFontScaleChanged();
-        if (mKeyguardUserSwitcher != null) {
-            mKeyguardUserSwitcher.onDensityOrFontScaleChanged();
-        }
         mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
         mHeadsUpManager.onDensityOrFontScaleChanged();
     }
@@ -1477,13 +1463,6 @@
         }
     }
 
-    protected void createUserSwitcher() {
-        mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
-                mNotificationShadeWindowView.findViewById(R.id.keyguard_user_switcher),
-                mNotificationShadeWindowView.findViewById(R.id.keyguard_header),
-                mNotificationPanelViewController);
-    }
-
     private void inflateStatusBarWindow() {
         mNotificationShadeWindowView = mSuperStatusBarViewFactory.getNotificationShadeWindowView();
         StatusBarComponent statusBarComponent = mStatusBarComponentBuilder.get()
@@ -3266,7 +3245,7 @@
         mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
         if (mUserSwitcherController != null && mUserSwitcherController.useFullscreenUserSwitcher()) {
             mStatusBarStateController.setState(StatusBarState.FULLSCREEN_USER_SWITCHER);
-        } else if (!mPulseExpansionHandler.isWakingToShadeLocked()){
+        } else if (!mPulseExpansionHandler.isWakingToShadeLocked()) {
             mStatusBarStateController.setState(StatusBarState.KEYGUARD);
         }
         updatePanelExpansionForKeyguard();
@@ -3565,15 +3544,15 @@
             }
             return true;
         }
+        if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) {
+            return true;
+        }
         if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) {
             if (mNotificationPanelViewController.canPanelBeCollapsed()) {
                 mShadeController.animateCollapsePanels();
             }
             return true;
         }
-        if (mKeyguardUserSwitcher != null && mKeyguardUserSwitcher.hideIfNotSimple(true)) {
-            return true;
-        }
         return false;
     }
 
@@ -3622,20 +3601,8 @@
         updateTheme();
         mNavigationBarController.touchAutoDim(mDisplayId);
         Trace.beginSection("StatusBar#updateKeyguardState");
-        if (mState == StatusBarState.KEYGUARD) {
-            if (mKeyguardUserSwitcher != null) {
-                mKeyguardUserSwitcher.setKeyguard(true,
-                        mStatusBarStateController.fromShadeLocked());
-            }
-            if (mStatusBarView != null) mStatusBarView.removePendingHideExpandedRunnables();
-        } else {
-            if (mKeyguardUserSwitcher != null) {
-                mKeyguardUserSwitcher.setKeyguard(false,
-                        mStatusBarStateController.goingToFullShade() ||
-                                mState == StatusBarState.SHADE_LOCKED ||
-                                mStatusBarStateController.fromShadeLocked());
-            }
-
+        if (mState == StatusBarState.KEYGUARD && mStatusBarView != null) {
+            mStatusBarView.removePendingHideExpandedRunnables();
         }
         updateDozingState();
         checkBarModes();
@@ -3647,7 +3614,7 @@
 
     @Override
     public void onDozeAmountChanged(float linear, float eased) {
-        if (mFeatureFlags.useNewLockscreenAnimations()) {
+        if (getEnableLightReveal()) {
             mLightRevealScrim.setRevealAmount(1f - linear);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 00acd7b..8620376 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -42,8 +42,8 @@
 import com.android.systemui.statusbar.StatusBarMobileView;
 import com.android.systemui.statusbar.StatusBarWifiView;
 import com.android.systemui.statusbar.StatusIconDisplayable;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 
 import java.util.List;
@@ -66,7 +66,7 @@
     /**
      * Display the no calling & SMS icons.
      */
-    void setNoCallingIcons(String slot, List<NoCallingIconState> states);
+    void setCallIndicatorIcons(String slot, List<CallIndicatorIconState> states);
     public void setIconVisibility(String slot, boolean b);
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 5e8d590..f0c8527 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -34,8 +34,8 @@
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusIconDisplayable;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -206,6 +206,7 @@
         Collections.reverse(iconStates);
 
         for (MobileIconState state : iconStates) {
+
             StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId);
             if (holder == null) {
                 holder = StatusBarIconHolder.fromMobileIconState(state);
@@ -218,23 +219,25 @@
     }
 
     /**
-     * Accept a list of NoCallingIconStates, and show them in the same slot
+     * Accept a list of CallIndicatorIconStates, and show them in the same slot
      * @param slot StatusBar slot
      * @param states All of the no Calling & SMS icon states
      */
     @Override
-    public void setNoCallingIcons(String slot, List<NoCallingIconState> states) {
+    public void setCallIndicatorIcons(String slot, List<CallIndicatorIconState> states) {
         Slot noCallingSlot = getSlot(slot);
         int slotIndex = getSlotIndex(slot);
-
-        for (NoCallingIconState state : states) {
+        for (CallIndicatorIconState state : states) {
             StatusBarIconHolder holder = noCallingSlot.getHolderForTag(state.subId);
             if (holder == null) {
-                holder = StatusBarIconHolder.fromNoCallingState(mContext, state);
-                holder.setVisible(state.visible);
+                holder = StatusBarIconHolder.fromCallIndicatorState(mContext, state);
                 setIcon(slotIndex, holder);
             } else {
-                holder.setVisible(state.visible);
+                int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId;
+                String contentDescription = state.isNoCalling
+                        ? state.noCallingDescription : state.callStrengthDescription;
+                holder.setIcon(new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
+                        Icon.createWithResource(mContext, resId), 0, 0, contentDescription));
                 setIcon(slotIndex, holder);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index 36a0e63..a1a2d30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -22,8 +22,8 @@
 import android.os.UserHandle;
 
 import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 
 /**
@@ -72,14 +72,18 @@
     }
 
     /**
-     * Creates a new StatusBarIconHolder from a NoCallingIconState.
+     * Creates a new StatusBarIconHolder from a CallIndicatorIconState.
      */
-    public static StatusBarIconHolder fromNoCallingState(
-            Context context, NoCallingIconState state) {
+    public static StatusBarIconHolder fromCallIndicatorState(
+            Context context, CallIndicatorIconState state) {
         StatusBarIconHolder holder = new StatusBarIconHolder();
+        int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId;
+        String contentDescription = state.isNoCalling
+                ? state.noCallingDescription : state.callStrengthDescription;
         holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(),
-                Icon.createWithResource(context, state.resId), 0, 0, null);
+                Icon.createWithResource(context, resId), 0, 0, contentDescription);
         holder.mTag = state.subId;
+        holder.setVisible(true);
         return holder;
     }
 
@@ -92,6 +96,10 @@
         return mIcon;
     }
 
+    public void setIcon(StatusBarIcon icon) {
+        mIcon = icon;
+    }
+
     @Nullable
     public WifiIconState getWifiState() {
         return mWifiState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index f6165f6..7bc1bb3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -22,6 +22,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.NetworkController;
@@ -39,7 +40,7 @@
 public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallback,
         SecurityController.SecurityControllerCallback, Tunable {
     private static final String TAG = "StatusBarSignalPolicy";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.INFO);
 
     private final String mSlotAirplane;
     private final String mSlotMobile;
@@ -67,7 +68,8 @@
     private boolean mWifiVisible = false;
 
     private ArrayList<MobileIconState> mMobileStates = new ArrayList<MobileIconState>();
-    private ArrayList<NoCallingIconState> mNoCallingStates = new ArrayList<NoCallingIconState>();
+    private ArrayList<CallIndicatorIconState> mCallIndicatorStates =
+            new ArrayList<CallIndicatorIconState>();
     private WifiIconState mWifiIconState = new WifiIconState();
 
     public StatusBarSignalPolicy(Context context, StatusBarIconController iconController) {
@@ -201,19 +203,25 @@
     }
 
     @Override
-    public void setNoCallingStatus(boolean noCalling, int subId) {
+    public void setCallIndicator(IconState statusIcon, int subId) {
         if (DEBUG) {
-            Log.d(TAG, "setNoCallingStatus: "
-                    + "noCalling = " + noCalling + ","
+            Log.d(TAG, "setCallIndicator: "
+                    + "statusIcon = " + statusIcon + ","
                     + "subId = " + subId);
         }
-        NoCallingIconState state = getNoCallingState(subId);
+        CallIndicatorIconState state = getNoCallingState(subId);
         if (state == null) {
             return;
         }
-        state.visible = noCalling;
-        mIconController.setNoCallingIcons(
-                mSlotNoCalling, NoCallingIconState.copyStates(mNoCallingStates));
+        if (statusIcon.icon == R.drawable.ic_qs_no_calling_sms) {
+            state.isNoCalling = statusIcon.visible;
+            state.noCallingDescription = statusIcon.contentDescription;
+        } else {
+            state.callStrengthResId = statusIcon.icon;
+            state.callStrengthDescription = statusIcon.contentDescription;
+        }
+        mIconController.setCallIndicatorIcons(
+                mSlotNoCalling, CallIndicatorIconState.copyStates(mCallIndicatorStates));
     }
 
     @Override
@@ -273,8 +281,8 @@
         }
     }
 
-    private NoCallingIconState getNoCallingState(int subId) {
-        for (NoCallingIconState state : mNoCallingStates) {
+    private CallIndicatorIconState getNoCallingState(int subId) {
+        for (CallIndicatorIconState state : mCallIndicatorStates) {
             if (state.subId == subId) {
                 return state;
             }
@@ -315,23 +323,25 @@
         }
 
         mIconController.removeAllIconsForSlot(mSlotMobile);
+        mIconController.removeAllIconsForSlot(mSlotNoCalling);
         mMobileStates.clear();
-        List<NoCallingIconState> noCallingStates = new ArrayList<NoCallingIconState>();
-        noCallingStates.addAll(mNoCallingStates);
-        mNoCallingStates.clear();
+        List<CallIndicatorIconState> noCallingStates = new ArrayList<CallIndicatorIconState>();
+        noCallingStates.addAll(mCallIndicatorStates);
+        mCallIndicatorStates.clear();
         final int n = subs.size();
         for (int i = 0; i < n; i++) {
             mMobileStates.add(new MobileIconState(subs.get(i).getSubscriptionId()));
             boolean isNewSub = true;
-            for (NoCallingIconState state : noCallingStates) {
+            for (CallIndicatorIconState state : noCallingStates) {
                 if (state.subId == subs.get(i).getSubscriptionId()) {
-                    mNoCallingStates.add(state);
+                    mCallIndicatorStates.add(state);
                     isNewSub = false;
                     break;
                 }
             }
             if (isNewSub) {
-                mNoCallingStates.add(new NoCallingIconState(subs.get(i).getSubscriptionId()));
+                mCallIndicatorStates.add(
+                        new CallIndicatorIconState(subs.get(i).getSubscriptionId()));
             }
         }
     }
@@ -425,14 +435,18 @@
     /**
      * Stores the StatusBar state for no Calling & SMS.
      */
-    public static class NoCallingIconState {
-        public boolean visible;
-        public int resId;
+    public static class CallIndicatorIconState {
+        public boolean isNoCalling;
+        public int noCallingResId;
+        public int callStrengthResId;
         public int subId;
+        public String noCallingDescription;
+        public String callStrengthDescription;
 
-        private NoCallingIconState(int subId) {
+        private CallIndicatorIconState(int subId) {
             this.subId = subId;
-            this.resId = R.drawable.ic_qs_no_calling_sms;
+            this.noCallingResId = R.drawable.ic_qs_no_calling_sms;
+            this.callStrengthResId = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0];
         }
 
         @Override
@@ -441,27 +455,36 @@
             if (o == null || getClass() != o.getClass()) {
                 return false;
             }
-            NoCallingIconState that = (NoCallingIconState) o;
-            return visible == that.visible
-                    && resId == that.resId
-                    && subId == that.subId;
+            CallIndicatorIconState that = (CallIndicatorIconState) o;
+            return  isNoCalling == that.isNoCalling
+                    && noCallingResId == that.noCallingResId
+                    && callStrengthResId == that.callStrengthResId
+                    && subId == that.subId
+                    && noCallingDescription == that.noCallingDescription
+                    && callStrengthDescription == that.callStrengthDescription;
+
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(visible, resId, subId);
+            return Objects.hash(isNoCalling, noCallingResId,
+                    callStrengthResId, subId, noCallingDescription, callStrengthDescription);
         }
 
-        private void copyTo(NoCallingIconState other) {
-            other.visible = visible;
-            other.resId = resId;
+        private void copyTo(CallIndicatorIconState other) {
+            other.isNoCalling = isNoCalling;
+            other.noCallingResId = noCallingResId;
+            other.callStrengthResId = callStrengthResId;
             other.subId = subId;
+            other.noCallingDescription = noCallingDescription;
+            other.callStrengthDescription = callStrengthDescription;
         }
 
-        private static List<NoCallingIconState> copyStates(List<NoCallingIconState> inStates) {
-            ArrayList<NoCallingIconState> outStates = new ArrayList<>();
-            for (NoCallingIconState state : inStates) {
-                NoCallingIconState copy = new NoCallingIconState(state.subId);
+        private static List<CallIndicatorIconState> copyStates(
+                List<CallIndicatorIconState> inStates) {
+            ArrayList<CallIndicatorIconState> outStates = new ArrayList<>();
+            for (CallIndicatorIconState state : inStates) {
+                CallIndicatorIconState copy = new CallIndicatorIconState(state.subId);
                 state.copyTo(copy);
                 outStates.add(copy);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index b572c57..9e9533d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -47,7 +47,6 @@
 import com.android.systemui.settings.brightness.BrightnessSlider;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -201,8 +200,7 @@
             DismissCallbackRegistry dismissCallbackRegistry,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
             NotificationIconAreaController notificationIconAreaController,
-            BrightnessSlider.Factory brightnessSliderFactory,
-            FeatureFlags featureFlags) {
+            BrightnessSlider.Factory brightnessSliderFactory) {
         return new StatusBar(
                 context,
                 notificationsController,
@@ -281,7 +279,6 @@
                 notificationShadeDepthController,
                 statusBarTouchableRegionManager,
                 notificationIconAreaController,
-                brightnessSliderFactory,
-                featureFlags);
+                brightnessSliderFactory);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
index ccaa1f4..5ff8970 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
@@ -36,6 +36,7 @@
  * the current or specified Looper.
  */
 public class CallbackHandler extends Handler implements EmergencyListener, SignalCallback {
+    private static final String TAG = "CallbackHandler";
     private static final int MSG_EMERGENCE_CHANGED           = 0;
     private static final int MSG_SUBS_CHANGED                = 1;
     private static final int MSG_NO_SIM_VISIBLE_CHANGED      = 2;
@@ -198,17 +199,17 @@
     }
 
     @Override
-    public void setNoCallingStatus(boolean noCalling, int subId) {
+    public void setCallIndicator(IconState statusIcon, int subId) {
         String log = new StringBuilder()
                 .append(SSDF.format(System.currentTimeMillis())).append(",")
-                .append("setNoCallingStatus: ")
-                .append("noCalling=").append(noCalling).append(",")
+                .append("setCallIndicator: ")
+                .append("statusIcon=").append(statusIcon).append(",")
                 .append("subId=").append(subId)
                 .toString();
         recordLastCallback(log);
         post(() -> {
             for (SignalCallback signalCluster : mSignalCallbacks) {
-                signalCluster.setNoCallingStatus(noCalling, subId);
+                signalCluster.setCallIndicator(statusIcon, subId);
             }
         });
     }
@@ -226,24 +227,11 @@
 
     @Override
     public void setNoSims(boolean show, boolean simDetected) {
-        String log = new StringBuilder()
-                .append(SSDF.format(System.currentTimeMillis())).append(",")
-                .append("setNoSims: ")
-                .append("show=").append(show).append(",")
-                .append("simDetected=").append(simDetected)
-                .toString();
-        recordLastCallback(log);
         obtainMessage(MSG_NO_SIM_VISIBLE_CHANGED, show ? 1 : 0, simDetected ? 1 : 0).sendToTarget();
     }
 
     @Override
     public void setMobileDataEnabled(boolean enabled) {
-        String log = new StringBuilder()
-                .append(SSDF.format(System.currentTimeMillis())).append(",")
-                .append("setMobileDataEnabled: ")
-                .append("enabled=").append(enabled)
-                .toString();
-        recordLastCallback(log);
         obtainMessage(MSG_MOBILE_DATA_ENABLED_CHANGED, enabled ? 1 : 0, 0).sendToTarget();
     }
 
@@ -283,7 +271,8 @@
     }
 
     protected void recordLastCallback(String callback) {
-        mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)] = callback;
+        mHistory[mHistoryIndex] = callback;
+        mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
     }
 
     /**
@@ -293,7 +282,9 @@
         pw.println("  - CallbackHandler -----");
         int size = 0;
         for (int i = 0; i < HISTORY_SIZE; i++) {
-            if (mHistory[i] != null) size++;
+            if (mHistory[i] != null) {
+                size++;
+            }
         }
         // Print out the previous states in ordered number.
         for (int i = mHistoryIndex + HISTORY_SIZE - 1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
index 07433e1..0649478 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
@@ -17,8 +17,15 @@
 package com.android.systemui.statusbar.policy;
 
 import android.content.Context;
+import android.graphics.Color;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
 
+import androidx.core.graphics.ColorUtils;
+
+import com.android.keyguard.KeyguardConstants;
+import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.qs.tiles.UserDetailItemView;
 
@@ -27,6 +34,14 @@
  */
 public class KeyguardUserDetailItemView extends UserDetailItemView {
 
+    private static final String TAG = "KeyguardUserDetailItemView";
+    private static final boolean DEBUG = KeyguardConstants.DEBUG;
+
+    private static final int ANIMATION_DURATION_FADE_NAME = 240;
+
+    private float mDarkAmount;
+    private int mTextColor;
+
     public KeyguardUserDetailItemView(Context context) {
         this(context, null);
     }
@@ -48,4 +63,89 @@
     protected int getFontSizeDimen() {
         return R.dimen.kg_user_switcher_text_size;
     }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mTextColor = mName.getCurrentTextColor();
+        updateDark();
+    }
+
+    /**
+     * Update visibility of this view.
+     *
+     * @param showItem If true, this item is visible on the screen to the user. Generally this
+     *                 means that the item would be clickable. If false, item visibility will be
+     *                 set to GONE and hidden entirely.
+     * @param showTextName Whether or not the name should be shown next to the icon. If false,
+     *                     only the icon is shown.
+     * @param animate Whether the transition should be animated. Note, this only applies to
+     *                animating the text name. The item itself will not animate (i.e. fade in/out).
+     *                Instead, we delegate that to the parent view.
+     */
+    void updateVisibilities(boolean showItem, boolean showTextName, boolean animate) {
+        if (DEBUG) {
+            Log.d(TAG, String.format("updateVisibilities itemIsShown=%b nameIsShown=%b animate=%b",
+                    showItem, showTextName, animate));
+        }
+
+        getBackground().setAlpha((showItem && showTextName) ? 255 : 0);
+
+        if (showItem) {
+            if (showTextName) {
+                mName.setVisibility(View.VISIBLE);
+                if (animate) {
+                    mName.setAlpha(0f);
+                    mName.animate()
+                            .alpha(1f)
+                            .setDuration(ANIMATION_DURATION_FADE_NAME)
+                            .setInterpolator(Interpolators.ALPHA_IN);
+                } else {
+                    mName.setAlpha(1f);
+                }
+            } else {
+                if (animate) {
+                    mName.setVisibility(View.VISIBLE);
+                    mName.setAlpha(1f);
+                    mName.animate()
+                            .alpha(0f)
+                            .setDuration(ANIMATION_DURATION_FADE_NAME)
+                            .setInterpolator(Interpolators.ALPHA_OUT)
+                            .withEndAction(() -> {
+                                mName.setVisibility(View.GONE);
+                                mName.setAlpha(1f);
+                            });
+                } else {
+                    mName.setVisibility(View.GONE);
+                    mName.setAlpha(1f);
+                }
+            }
+            setVisibility(View.VISIBLE);
+            setAlpha(1f);
+        } else {
+            // If item isn't shown, don't animate. The parent class will animate the view instead
+            setVisibility(View.GONE);
+            setAlpha(1f);
+            mName.setVisibility(showTextName ? View.VISIBLE : View.GONE);
+            mName.setAlpha(1f);
+        }
+    }
+
+    /**
+     * Set the amount (ratio) that the device has transitioned to doze.
+     *
+     * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
+     */
+    public void setDarkAmount(float darkAmount) {
+        if (mDarkAmount == darkAmount) {
+            return;
+        }
+        mDarkAmount = darkAmount;
+        updateDark();
+    }
+
+    private void updateDark() {
+        final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
+        mName.setTextColor(blendedTextColor);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
deleted file mode 100644
index 90f5577..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.policy;
-
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA;
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.database.DataSetObserver;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.widget.FrameLayout;
-
-import com.android.settingslib.animation.AppearAnimationUtils;
-import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.qs.tiles.UserDetailItemView;
-import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
-
-import java.util.ArrayList;
-
-/**
- * Manages the user switcher on the Keyguard.
- */
-public class KeyguardUserSwitcher {
-
-    private static final String TAG = "KeyguardUserSwitcher";
-    private static final boolean ALWAYS_ON = false;
-
-    private final Container mUserSwitcherContainer;
-    private final KeyguardStatusBarView mStatusBarView;
-    private final KeyguardUserAdapter mAdapter;
-    private final AppearAnimationUtils mAppearAnimationUtils;
-    private final KeyguardUserSwitcherScrim mBackground;
-
-    private ViewGroup mUserSwitcher;
-    private ObjectAnimator mBgAnimator;
-    private UserSwitcherController mUserSwitcherController;
-    private boolean mAnimating;
-
-    public KeyguardUserSwitcher(Context context, ViewStub userSwitcher,
-            KeyguardStatusBarView statusBarView,
-            NotificationPanelViewController panelViewController) {
-        boolean keyguardUserSwitcherEnabled =
-                context.getResources().getBoolean(
-                        com.android.internal.R.bool.config_keyguardUserSwitcher) || ALWAYS_ON;
-        UserSwitcherController userSwitcherController = Dependency.get(UserSwitcherController.class);
-        if (userSwitcherController != null && keyguardUserSwitcherEnabled) {
-            mUserSwitcherContainer = (Container) userSwitcher.inflate();
-            mBackground = new KeyguardUserSwitcherScrim(context);
-            reinflateViews();
-            mStatusBarView = statusBarView;
-            mStatusBarView.setKeyguardUserSwitcher(this);
-            panelViewController.setKeyguardUserSwitcher(this);
-            mAdapter = new KeyguardUserAdapter(context, userSwitcherController, this);
-            mAdapter.registerDataSetObserver(mDataSetObserver);
-            mUserSwitcherController = userSwitcherController;
-            mAppearAnimationUtils = new AppearAnimationUtils(context, 400, -0.5f, 0.5f,
-                    Interpolators.FAST_OUT_SLOW_IN);
-            mUserSwitcherContainer.setKeyguardUserSwitcher(this);
-        } else {
-            mUserSwitcherContainer = null;
-            mStatusBarView = null;
-            mAdapter = null;
-            mAppearAnimationUtils = null;
-            mBackground = null;
-        }
-    }
-
-    private void reinflateViews() {
-        if (mUserSwitcher != null) {
-            mUserSwitcher.setBackground(null);
-            mUserSwitcher.removeOnLayoutChangeListener(mBackground);
-        }
-        mUserSwitcherContainer.removeAllViews();
-
-        LayoutInflater.from(mUserSwitcherContainer.getContext())
-                .inflate(R.layout.keyguard_user_switcher_inner, mUserSwitcherContainer);
-
-        mUserSwitcher = (ViewGroup) mUserSwitcherContainer.findViewById(
-                R.id.keyguard_user_switcher_inner);
-        mUserSwitcher.addOnLayoutChangeListener(mBackground);
-        mUserSwitcher.setBackground(mBackground);
-    }
-
-    public void setKeyguard(boolean keyguard, boolean animate) {
-        if (mUserSwitcher != null) {
-            if (keyguard && shouldExpandByDefault()) {
-                show(animate);
-            } else {
-                hide(animate);
-            }
-        }
-    }
-
-    /**
-     * @return true if the user switcher should be expanded by default on the lock screen.
-     * @see android.os.UserManager#isUserSwitcherEnabled()
-     */
-    private boolean shouldExpandByDefault() {
-        return (mUserSwitcherController != null) && mUserSwitcherController.isSimpleUserSwitcher();
-    }
-
-    public void show(boolean animate) {
-        if (mUserSwitcher != null && mUserSwitcherContainer.getVisibility() != View.VISIBLE) {
-            cancelAnimations();
-            mAdapter.refresh();
-            mUserSwitcherContainer.setVisibility(View.VISIBLE);
-            mStatusBarView.setKeyguardUserSwitcherShowing(true, animate);
-            if (animate) {
-                startAppearAnimation();
-            }
-        }
-    }
-
-    private boolean hide(boolean animate) {
-        if (mUserSwitcher != null && mUserSwitcherContainer.getVisibility() == View.VISIBLE) {
-            cancelAnimations();
-            if (animate) {
-                startDisappearAnimation();
-            } else {
-                mUserSwitcherContainer.setVisibility(View.GONE);
-            }
-            mStatusBarView.setKeyguardUserSwitcherShowing(false, animate);
-            return true;
-        }
-        return false;
-    }
-
-    private void cancelAnimations() {
-        int count = mUserSwitcher.getChildCount();
-        for (int i = 0; i < count; i++) {
-            mUserSwitcher.getChildAt(i).animate().cancel();
-        }
-        if (mBgAnimator != null) {
-            mBgAnimator.cancel();
-        }
-        mUserSwitcher.animate().cancel();
-        mAnimating = false;
-    }
-
-    private void startAppearAnimation() {
-        int count = mUserSwitcher.getChildCount();
-        View[] objects = new View[count];
-        for (int i = 0; i < count; i++) {
-            objects[i] = mUserSwitcher.getChildAt(i);
-        }
-        mUserSwitcher.setClipChildren(false);
-        mUserSwitcher.setClipToPadding(false);
-        mAppearAnimationUtils.startAnimation(objects, new Runnable() {
-            @Override
-            public void run() {
-                mUserSwitcher.setClipChildren(true);
-                mUserSwitcher.setClipToPadding(true);
-            }
-        });
-        mAnimating = true;
-        mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 0, 255);
-        mBgAnimator.setDuration(400);
-        mBgAnimator.setInterpolator(Interpolators.ALPHA_IN);
-        mBgAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mBgAnimator = null;
-                mAnimating = false;
-            }
-        });
-        mBgAnimator.start();
-    }
-
-    private void startDisappearAnimation() {
-        mAnimating = true;
-        mUserSwitcher.animate()
-                .alpha(0f)
-                .setDuration(300)
-                .setInterpolator(Interpolators.ALPHA_OUT)
-                .withEndAction(new Runnable() {
-                    @Override
-                    public void run() {
-                        mUserSwitcherContainer.setVisibility(View.GONE);
-                        mUserSwitcher.setAlpha(1f);
-                        mAnimating = false;
-                    }
-                });
-    }
-
-    private void refresh() {
-        final int childCount = mUserSwitcher.getChildCount();
-        final int adapterCount = mAdapter.getCount();
-        final int N = Math.max(childCount, adapterCount);
-        for (int i = 0; i < N; i++) {
-            if (i < adapterCount) {
-                View oldView = null;
-                if (i < childCount) {
-                    oldView = mUserSwitcher.getChildAt(i);
-                }
-                View newView = mAdapter.getView(i, oldView, mUserSwitcher);
-                if (oldView == null) {
-                    // We ran out of existing views. Add it at the end.
-                    mUserSwitcher.addView(newView);
-                } else if (oldView != newView) {
-                    // We couldn't rebind the view. Replace it.
-                    mUserSwitcher.removeViewAt(i);
-                    mUserSwitcher.addView(newView, i);
-                }
-            } else {
-                int lastIndex = mUserSwitcher.getChildCount() - 1;
-                mUserSwitcher.removeViewAt(lastIndex);
-            }
-        }
-    }
-
-    public boolean hideIfNotSimple(boolean animate) {
-        if (mUserSwitcherContainer != null && !mUserSwitcherController.isSimpleUserSwitcher()) {
-            return hide(animate);
-        }
-        return false;
-    }
-
-    boolean isAnimating() {
-        return mAnimating;
-    }
-
-    public final DataSetObserver mDataSetObserver = new DataSetObserver() {
-        @Override
-        public void onChanged() {
-            refresh();
-        }
-    };
-
-    public void onDensityOrFontScaleChanged() {
-        if (mUserSwitcherContainer != null) {
-            reinflateViews();
-            refresh();
-        }
-    }
-
-    static class KeyguardUserAdapter extends
-            UserSwitcherController.BaseUserAdapter implements View.OnClickListener {
-
-        private Context mContext;
-        private KeyguardUserSwitcher mKeyguardUserSwitcher;
-        private View mCurrentUserView;
-        // List of users where the first entry is always the current user
-        private ArrayList<UserSwitcherController.UserRecord> mUsersOrdered = new ArrayList<>();
-
-        KeyguardUserAdapter(Context context, UserSwitcherController controller,
-                KeyguardUserSwitcher kgu) {
-            super(controller);
-            mContext = context;
-            mKeyguardUserSwitcher = kgu;
-        }
-
-        @Override
-        public void notifyDataSetChanged() {
-            refreshUserOrder();
-            super.notifyDataSetChanged();
-        }
-
-        void refreshUserOrder() {
-            ArrayList<UserSwitcherController.UserRecord> users = super.getUsers();
-            mUsersOrdered = new ArrayList<>(users.size());
-            for (int i = 0; i < users.size(); i++) {
-                UserSwitcherController.UserRecord record = users.get(i);
-                if (record.isCurrent) {
-                    mUsersOrdered.add(0, record);
-                } else {
-                    mUsersOrdered.add(record);
-                }
-            }
-        }
-
-        @Override
-        protected ArrayList<UserSwitcherController.UserRecord> getUsers() {
-            return mUsersOrdered;
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            UserSwitcherController.UserRecord item = getItem(position);
-            return createUserDetailItemView(convertView, parent, item);
-        }
-
-        KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) {
-            if (!(convertView instanceof KeyguardUserDetailItemView)
-                    || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) {
-                convertView = LayoutInflater.from(mContext).inflate(
-                        R.layout.keyguard_user_switcher_item, parent, false);
-            }
-            return (KeyguardUserDetailItemView) convertView;
-        }
-
-        UserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
-                UserSwitcherController.UserRecord item) {
-            KeyguardUserDetailItemView v = convertOrInflate(convertView, parent);
-            if (!item.isCurrent || item.isGuest) {
-                v.setOnClickListener(this);
-            } else {
-                v.setOnClickListener(null);
-                v.setClickable(false);
-            }
-
-            String name = getName(mContext, item);
-            if (item.picture == null) {
-                v.bind(name, getDrawable(mContext, item).mutate(), item.resolveId());
-            } else {
-                int avatarSize =
-                        (int) mContext.getResources().getDimension(R.dimen.kg_framed_avatar_size);
-                Drawable drawable = new CircleFramedDrawable(item.picture, avatarSize);
-                drawable.setColorFilter(
-                        item.isSwitchToEnabled ? null : getDisabledUserAvatarColorFilter());
-                v.bind(name, drawable, item.info.id);
-            }
-            v.setActivated(item.isCurrent);
-            v.setDisabledByAdmin(item.isDisabledByAdmin);
-            v.setEnabled(item.isSwitchToEnabled);
-            v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA);
-
-            if (item.isCurrent) {
-                mCurrentUserView = v;
-            }
-            v.setTag(item);
-            return v;
-        }
-
-        private static Drawable getDrawable(Context context,
-                UserSwitcherController.UserRecord item) {
-            Drawable drawable = getIconDrawable(context, item);
-            int iconColorRes;
-            if (item.isCurrent) {
-                iconColorRes = R.color.kg_user_switcher_selected_avatar_icon_color;
-            } else if (!item.isSwitchToEnabled) {
-                iconColorRes = R.color.GM2_grey_600;
-            } else {
-                iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
-            }
-            drawable.setTint(context.getResources().getColor(iconColorRes, context.getTheme()));
-
-            if (item.isCurrent) {
-                Drawable bg = context.getDrawable(R.drawable.bg_avatar_selected);
-                drawable = new LayerDrawable(new Drawable[]{bg, drawable});
-            }
-
-            return drawable;
-        }
-
-        @Override
-        public void onClick(View v) {
-            UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag();
-            if (user.isCurrent && !user.isGuest) {
-                // Close the switcher if tapping the current user. Guest is excluded because
-                // tapping the guest user while it's current clears the session.
-                mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
-            } else if (user.isSwitchToEnabled) {
-                if (!user.isAddUser && !user.isRestricted && !user.isDisabledByAdmin) {
-                    if (mCurrentUserView != null) {
-                        mCurrentUserView.setActivated(false);
-                    }
-                    v.setActivated(true);
-                }
-                onUserListItemClicked(user);
-            }
-        }
-    }
-
-    public static class Container extends FrameLayout {
-
-        private KeyguardUserSwitcher mKeyguardUserSwitcher;
-
-        public Container(Context context, AttributeSet attrs) {
-            super(context, attrs);
-            setClipChildren(false);
-        }
-
-        public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
-            mKeyguardUserSwitcher = keyguardUserSwitcher;
-        }
-
-        @Override
-        public boolean onTouchEvent(MotionEvent ev) {
-            // Hide switcher if it didn't handle the touch event (and let the event go through).
-            if (mKeyguardUserSwitcher != null && !mKeyguardUserSwitcher.isAnimating()) {
-                mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
-            }
-            return false;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
new file mode 100644
index 0000000..b76e451
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -0,0 +1,639 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA;
+import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.android.keyguard.KeyguardConstants;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.KeyguardVisibilityHelper;
+import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
+import com.android.settingslib.drawable.CircleFramedDrawable;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.ViewController;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+
+/**
+ * Manages the user switcher on the Keyguard.
+ */
+@KeyguardUserSwitcherScope
+public class KeyguardUserSwitcherController extends ViewController<KeyguardUserSwitcherView> {
+
+    private static final String TAG = "KeyguardUserSwitcherController";
+    private static final boolean DEBUG = KeyguardConstants.DEBUG;
+
+    private static final AnimationProperties ANIMATION_PROPERTIES =
+            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+
+    private final Context mContext;
+    private final UserSwitcherController mUserSwitcherController;
+    private final ScreenLifecycle mScreenLifecycle;
+    private final KeyguardUserAdapter mAdapter;
+    private final KeyguardStateController mKeyguardStateController;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private WeakReference<KeyguardUserSwitcherListener> mKeyguardUserSwitcherCallback;
+    protected final SysuiStatusBarStateController mStatusBarStateController;
+    private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
+
+    // Child views of KeyguardUserSwitcherView
+    private KeyguardUserSwitcherListView mListView;
+    private LinearLayout mEndGuestButton;
+
+    // State info for the user switcher
+    private boolean mUserSwitcherOpen;
+    private int mCurrentUserId = UserHandle.USER_NULL;
+    private boolean mCurrentUserIsGuest;
+    private int mBarState;
+    private float mDarkAmount;
+
+    private final KeyguardUpdateMonitorCallback mInfoCallback =
+            new KeyguardUpdateMonitorCallback() {
+                @Override
+                public void onKeyguardVisibilityChanged(boolean showing) {
+                    if (DEBUG) Log.d(TAG, String.format("onKeyguardVisibilityChanged %b", showing));
+                    // Any time the keyguard is hidden, try to close the user switcher menu to
+                    // restore keyguard to the default state
+                    if (!showing) {
+                        closeSwitcherIfOpenAndNotSimple(false);
+                    }
+                }
+
+                @Override
+                public void onUserSwitching(int userId) {
+                    closeSwitcherIfOpenAndNotSimple(false);
+                }
+            };
+
+    private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
+        @Override
+        public void onScreenTurnedOff() {
+            if (DEBUG) Log.d(TAG, "onScreenTurnedOff");
+            closeSwitcherIfOpenAndNotSimple(false);
+        }
+    };
+
+    private final StatusBarStateController.StateListener mStatusBarStateListener =
+            new StatusBarStateController.StateListener() {
+                @Override
+                public void onStateChanged(int newState) {
+                    if (DEBUG) Log.d(TAG, String.format("onStateChanged: newState=%d", newState));
+
+                    boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
+                    boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
+                    int oldState = mBarState;
+                    mBarState = newState;
+
+                    if (mStatusBarStateController.goingToFullShade()
+                            || mKeyguardStateController.isKeyguardFadingAway()) {
+                        closeSwitcherIfOpenAndNotSimple(true);
+                    }
+
+                    setKeyguardUserSwitcherVisibility(
+                            newState,
+                            keyguardFadingAway,
+                            goingToFullShade,
+                            oldState);
+                }
+
+                @Override
+                public void onDozeAmountChanged(float linearAmount, float amount) {
+                    if (DEBUG) {
+                        Log.d(TAG, String.format("onDozeAmountChanged: linearAmount=%f amount=%f",
+                                linearAmount, amount));
+                    }
+                    setDarkAmount(amount);
+                }
+            };
+
+    @Inject
+    public KeyguardUserSwitcherController(
+            KeyguardUserSwitcherView keyguardUserSwitcherView,
+            Context context,
+            @Main Resources resources,
+            LayoutInflater layoutInflater,
+            ScreenLifecycle screenLifecycle,
+            UserSwitcherController userSwitcherController,
+            KeyguardStateController keyguardStateController,
+            SysuiStatusBarStateController statusBarStateController,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            DozeParameters dozeParameters) {
+        super(keyguardUserSwitcherView);
+        if (DEBUG) Log.d(TAG, "New KeyguardUserSwitcherController");
+        mContext = context;
+        mScreenLifecycle = screenLifecycle;
+        mUserSwitcherController = userSwitcherController;
+        mKeyguardStateController = keyguardStateController;
+        mStatusBarStateController = statusBarStateController;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mAdapter = new KeyguardUserAdapter(mContext, resources, layoutInflater,
+                mUserSwitcherController, this);
+        mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
+                keyguardStateController, dozeParameters);
+    }
+
+    @Override
+    protected void onInit() {
+        super.onInit();
+
+        if (DEBUG) Log.d(TAG, "onInit");
+
+        mListView = mView.findViewById(R.id.keyguard_user_switcher_list);
+        mEndGuestButton = mView.findViewById(R.id.end_guest_button);
+
+        mEndGuestButton.setOnClickListener(v -> {
+            mUserSwitcherController.showExitGuestDialog(mCurrentUserId);
+        });
+
+        mView.setOnTouchListener((v, event) -> {
+            if (!isListAnimating()) {
+                // Hide switcher if it didn't handle the touch event (and block the event from
+                // going through).
+                return closeSwitcherIfOpenAndNotSimple(true);
+            }
+            return false;
+        });
+    }
+
+    @Override
+    protected void onViewAttached() {
+        if (DEBUG) Log.d(TAG, "onViewAttached");
+        mAdapter.registerDataSetObserver(mDataSetObserver);
+        mDataSetObserver.onChanged();
+        mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
+        mStatusBarStateController.addCallback(mStatusBarStateListener);
+        mScreenLifecycle.addObserver(mScreenObserver);
+    }
+
+    @Override
+    protected void onViewDetached() {
+        if (DEBUG) Log.d(TAG, "onViewDetached");
+
+        // Detaching the view will always close the switcher
+        closeSwitcherIfOpenAndNotSimple(false);
+
+        mAdapter.unregisterDataSetObserver(mDataSetObserver);
+        mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
+        mStatusBarStateController.removeCallback(mStatusBarStateListener);
+        mScreenLifecycle.removeObserver(mScreenObserver);
+    }
+
+    /**
+     * See:
+     *
+     * <ul>
+     *   <li>{@link com.android.internal.R.bool.config_expandLockScreenUserSwitcher}</li>
+     *    <li>{@link UserSwitcherController.SIMPLE_USER_SWITCHER_GLOBAL_SETTING}</li>
+     * </ul>
+     *
+     * @return true if the user switcher should be open by default on the lock screen.
+     * @see android.os.UserManager#isUserSwitcherEnabled()
+     */
+    public boolean isSimpleUserSwitcher() {
+        return mUserSwitcherController.isSimpleUserSwitcher();
+    }
+
+    /**
+     * @param animate if the transition should be animated
+     * @return true if the switcher state changed
+     */
+    public boolean closeSwitcherIfOpenAndNotSimple(boolean animate) {
+        if (isUserSwitcherOpen() && !isSimpleUserSwitcher()) {
+            setUserSwitcherOpened(false /* open */, animate);
+            return true;
+        }
+        return false;
+    }
+
+    public final DataSetObserver mDataSetObserver = new DataSetObserver() {
+        @Override
+        public void onChanged() {
+            refreshUserList();
+        }
+    };
+
+    void refreshUserList() {
+        final int childCount = mListView.getChildCount();
+        final int adapterCount = mAdapter.getCount();
+        final int count = Math.max(childCount, adapterCount);
+
+        if (DEBUG) {
+            Log.d(TAG, String.format("refreshUserList childCount=%d adapterCount=%d", childCount,
+                    adapterCount));
+        }
+
+        boolean foundCurrentUser = false;
+        for (int i = 0; i < count; i++) {
+            if (i < adapterCount) {
+                View oldView = null;
+                if (i < childCount) {
+                    oldView = mListView.getChildAt(i);
+                }
+                KeyguardUserDetailItemView newView = (KeyguardUserDetailItemView)
+                        mAdapter.getView(i, oldView, mListView);
+                UserSwitcherController.UserRecord userTag =
+                        (UserSwitcherController.UserRecord) newView.getTag();
+                if (userTag.isCurrent) {
+                    if (i != 0) {
+                        Log.w(TAG, "Current user is not the first view in the list");
+                    }
+                    foundCurrentUser = true;
+                    mCurrentUserId = userTag.info.id;
+                    mCurrentUserIsGuest = userTag.isGuest;
+                    // Current user is always visible
+                    newView.updateVisibilities(true /* showItem */,
+                            mUserSwitcherOpen /* showTextName */, false /* animate */);
+                } else {
+                    // Views for non-current users are always expanded (e.g. they should the name
+                    // next to the user icon). However, they could be hidden entirely if the list
+                    // is closed.
+                    newView.updateVisibilities(mUserSwitcherOpen /* showItem */,
+                            true /* showTextName */, false /* animate */);
+                }
+                newView.setDarkAmount(mDarkAmount);
+                if (oldView == null) {
+                    // We ran out of existing views. Add it at the end.
+                    mListView.addView(newView);
+                } else if (oldView != newView) {
+                    // We couldn't rebind the view. Replace it.
+                    mListView.replaceView(newView, i);
+                }
+            } else {
+                mListView.removeLastView();
+            }
+        }
+        if (!foundCurrentUser) {
+            Log.w(TAG, "Current user is not listed");
+            mCurrentUserId = UserHandle.USER_NULL;
+            mCurrentUserIsGuest = false;
+        }
+    }
+
+    /**
+     * Get the height of the keyguard user switcher view when closed.
+     */
+    public int getUserIconHeight() {
+        View firstChild = mListView.getChildAt(0);
+        return firstChild == null ? 0 : firstChild.getHeight();
+    }
+
+    /**
+     * Set the visibility of the keyguard user switcher view based on some new state.
+     */
+    public void setKeyguardUserSwitcherVisibility(
+            int statusBarState,
+            boolean keyguardFadingAway,
+            boolean goingToFullShade,
+            int oldStatusBarState) {
+        mKeyguardVisibilityHelper.setViewVisibility(
+                statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
+    }
+
+    /**
+     * Update position of the view with an optional animation
+     */
+    public void updatePosition(int x, int y, boolean animate) {
+        PropertyAnimator.setProperty(mListView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES,
+                animate);
+        PropertyAnimator.setProperty(mListView, AnimatableProperty.TRANSLATION_X, -Math.abs(x),
+                ANIMATION_PROPERTIES, animate);
+    }
+
+    /**
+     * Set keyguard user switcher view alpha.
+     */
+    public void setAlpha(float alpha) {
+        if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
+            mView.setAlpha(alpha);
+        }
+    }
+
+    /**
+     * Set the amount (ratio) that the device has transitioned to doze.
+     *
+     * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
+     */
+    private void setDarkAmount(float darkAmount) {
+        boolean isAwake = darkAmount != 0;
+        if (darkAmount == mDarkAmount) {
+            return;
+        }
+        mDarkAmount = darkAmount;
+        mListView.setDarkAmount(darkAmount);
+        mView.setVisibility(isAwake ? View.VISIBLE : View.GONE);
+        if (!isAwake) {
+            closeSwitcherIfOpenAndNotSimple(false);
+        }
+    }
+
+    private boolean isListAnimating() {
+        return mKeyguardVisibilityHelper.isVisibilityAnimating() || mListView.isAnimating();
+    }
+
+    /**
+     * Remove the callback if it exists.
+     */
+    public void removeCallback() {
+        if (DEBUG) Log.d(TAG, "removeCallback");
+        mKeyguardUserSwitcherCallback = null;
+    }
+
+    /**
+     * Register to receive notifications about keyguard user switcher state
+     * (see {@link KeyguardUserSwitcherListener}.
+     *
+     * Only one callback can be used at a time.
+     *
+     * @param callback The callback to register
+     */
+    public void setCallback(KeyguardUserSwitcherListener callback) {
+        if (DEBUG) Log.d(TAG, "setCallback");
+        mKeyguardUserSwitcherCallback = new WeakReference<>(callback);
+    }
+
+    /**
+     * If user switcher state changes, notifies all {@link KeyguardUserSwitcherListener}.
+     * Switcher state is updatd before animations finish.
+     *
+     * @param animate true to animate transition. The user switcher state (i.e.
+     *                {@link #isUserSwitcherOpen()}) is updated before animation is finished.
+     */
+    private void setUserSwitcherOpened(boolean open, boolean animate) {
+        boolean wasOpen = mUserSwitcherOpen;
+        if (DEBUG) {
+            Log.d(TAG, String.format("setUserSwitcherOpened: %b -> %b (animate=%b)", wasOpen,
+                    open, animate));
+        }
+        mUserSwitcherOpen = open;
+        if (mUserSwitcherOpen != wasOpen) {
+            notifyUserSwitcherStateChanged();
+        }
+        updateVisibilities(animate);
+    }
+
+    private void updateVisibilities(boolean animate) {
+        if (DEBUG) Log.d(TAG, String.format("updateVisibilities: animate=%b", animate));
+        mEndGuestButton.animate().cancel();
+        if (mUserSwitcherOpen && mCurrentUserIsGuest) {
+            // Show the "End guest session" button
+            mEndGuestButton.setVisibility(View.VISIBLE);
+            if (animate) {
+                mEndGuestButton.setAlpha(0f);
+                mEndGuestButton.animate()
+                        .alpha(1f)
+                        .setDuration(360)
+                        .setInterpolator(Interpolators.ALPHA_IN)
+                        .withEndAction(() -> {
+                            mEndGuestButton.setClickable(true);
+                        });
+            } else {
+                mEndGuestButton.setClickable(true);
+                mEndGuestButton.setAlpha(1f);
+            }
+        } else {
+            // Hide the "End guest session" button. If it's already GONE, don't try to
+            // animate it or it will appear again for an instant.
+            mEndGuestButton.setClickable(false);
+            if (animate && mEndGuestButton.getVisibility() != View.GONE) {
+                mEndGuestButton.setVisibility(View.VISIBLE);
+                mEndGuestButton.setAlpha(1f);
+                mEndGuestButton.animate()
+                        .alpha(0f)
+                        .setDuration(360)
+                        .setInterpolator(Interpolators.ALPHA_OUT)
+                        .withEndAction(() -> {
+                            mEndGuestButton.setVisibility(View.GONE);
+                            mEndGuestButton.setAlpha(1f);
+                        });
+            } else {
+                mEndGuestButton.setVisibility(View.GONE);
+                mEndGuestButton.setAlpha(1f);
+            }
+        }
+
+        mListView.updateVisibilities(mUserSwitcherOpen, animate);
+    }
+
+    private boolean isUserSwitcherOpen() {
+        return mUserSwitcherOpen;
+    }
+
+    private void notifyUserSwitcherStateChanged() {
+        if (DEBUG) {
+            Log.d(TAG, String.format("notifyUserSwitcherStateChanged: mUserSwitcherOpen=%b",
+                    mUserSwitcherOpen));
+        }
+        if (mKeyguardUserSwitcherCallback != null) {
+            KeyguardUserSwitcherListener cb = mKeyguardUserSwitcherCallback.get();
+            if (cb != null) {
+                cb.onKeyguardUserSwitcherChanged(mUserSwitcherOpen);
+            }
+        }
+    }
+
+    /**
+     * Callback for keyguard user switcher state information
+     */
+    public interface KeyguardUserSwitcherListener {
+
+        /**
+         * Called when the keyguard enters or leaves user switcher mode. This will be called
+         * before the animations are finished.
+         *
+         * @param open if true, keyguard is showing the user switcher or transitioning from/to user
+         *             switcher mode.
+         */
+        void onKeyguardUserSwitcherChanged(boolean open);
+    }
+
+    static class KeyguardUserAdapter extends
+            UserSwitcherController.BaseUserAdapter implements View.OnClickListener {
+
+        private final Context mContext;
+        private final Resources mResources;
+        private final LayoutInflater mLayoutInflater;
+        private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
+        private View mCurrentUserView;
+        // List of users where the first entry is always the current user
+        private ArrayList<UserSwitcherController.UserRecord> mUsersOrdered = new ArrayList<>();
+
+        KeyguardUserAdapter(Context context, Resources resources, LayoutInflater layoutInflater,
+                UserSwitcherController controller,
+                KeyguardUserSwitcherController keyguardUserSwitcherController) {
+            super(controller);
+            mContext = context;
+            mResources = resources;
+            mLayoutInflater = layoutInflater;
+            mKeyguardUserSwitcherController = keyguardUserSwitcherController;
+        }
+
+        @Override
+        public void notifyDataSetChanged() {
+            // At this point, value of isSimpleUserSwitcher() may have changed in addition to the
+            // data set
+            refreshUserOrder();
+            super.notifyDataSetChanged();
+        }
+
+        void refreshUserOrder() {
+            ArrayList<UserSwitcherController.UserRecord> users = super.getUsers();
+            mUsersOrdered = new ArrayList<>(users.size());
+            for (int i = 0; i < users.size(); i++) {
+                UserSwitcherController.UserRecord record = users.get(i);
+                if (record.isCurrent) {
+                    mUsersOrdered.add(0, record);
+                } else {
+                    mUsersOrdered.add(record);
+                }
+            }
+        }
+
+        @Override
+        protected ArrayList<UserSwitcherController.UserRecord> getUsers() {
+            return mUsersOrdered;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            UserSwitcherController.UserRecord item = getItem(position);
+            return createUserDetailItemView(convertView, parent, item);
+        }
+
+        @Override
+        public String getName(Context context, UserSwitcherController.UserRecord item) {
+            if (item.isGuest) {
+                return context.getString(com.android.settingslib.R.string.guest_nickname);
+            } else {
+                return super.getName(context, item);
+            }
+        }
+
+        KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) {
+            if (!(convertView instanceof KeyguardUserDetailItemView)
+                    || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) {
+                convertView = mLayoutInflater.inflate(
+                        R.layout.keyguard_user_switcher_item, parent, false);
+            }
+            return (KeyguardUserDetailItemView) convertView;
+        }
+
+        KeyguardUserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
+                UserSwitcherController.UserRecord item) {
+            KeyguardUserDetailItemView v = convertOrInflate(convertView, parent);
+            v.setOnClickListener(this);
+
+            String name = getName(mContext, item);
+            if (item.picture == null) {
+                v.bind(name, getDrawable(item).mutate(), item.resolveId());
+            } else {
+                int avatarSize =
+                        (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
+                Drawable drawable = new CircleFramedDrawable(item.picture, avatarSize);
+                drawable.setColorFilter(
+                        item.isSwitchToEnabled ? null : getDisabledUserAvatarColorFilter());
+                v.bind(name, drawable, item.info.id);
+            }
+            v.setActivated(item.isCurrent);
+            v.setDisabledByAdmin(item.isDisabledByAdmin);
+            v.setEnabled(item.isSwitchToEnabled);
+            v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA);
+
+            if (item.isCurrent) {
+                mCurrentUserView = v;
+            }
+            v.setTag(item);
+            return v;
+        }
+
+        private Drawable getDrawable(UserSwitcherController.UserRecord item) {
+            Drawable drawable;
+            if (item.isCurrent && item.isGuest) {
+                drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
+            } else {
+                drawable = getIconDrawable(mContext, item);
+            }
+
+            int iconColorRes;
+            if (item.isSwitchToEnabled) {
+                iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
+            } else {
+                iconColorRes = R.color.kg_user_switcher_restricted_avatar_icon_color;
+            }
+            drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));
+
+            Drawable bg = mContext.getDrawable(R.drawable.kg_bg_avatar);
+            drawable = new LayerDrawable(new Drawable[]{bg, drawable});
+            return drawable;
+        }
+
+        @Override
+        public void onClick(View v) {
+            UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag();
+
+            if (mKeyguardUserSwitcherController.isListAnimating()) {
+                return;
+            }
+
+            if (mKeyguardUserSwitcherController.isUserSwitcherOpen()) {
+                if (user.isCurrent) {
+                    // Close the switcher if tapping the current user
+                    mKeyguardUserSwitcherController.setUserSwitcherOpened(
+                            false /* open */, true /* animate */);
+                } else if (user.isSwitchToEnabled) {
+                    if (!user.isAddUser && !user.isRestricted && !user.isDisabledByAdmin) {
+                        if (mCurrentUserView != null) {
+                            mCurrentUserView.setActivated(false);
+                        }
+                        v.setActivated(true);
+                    }
+                    onUserListItemClicked(user);
+                }
+            } else {
+                // If switcher is closed, tapping anywhere in the view will open it
+                mKeyguardUserSwitcherController.setUserSwitcherOpened(
+                        true /* open */, true /* animate */);
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
new file mode 100644
index 0000000..7c82c11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import com.android.keyguard.AlphaOptimizedLinearLayout;
+import com.android.keyguard.KeyguardConstants;
+import com.android.settingslib.animation.AppearAnimationUtils;
+import com.android.settingslib.animation.DisappearAnimationUtils;
+import com.android.systemui.Interpolators;
+
+/**
+ * The container for the user switcher on Keyguard.
+ */
+public class KeyguardUserSwitcherListView extends AlphaOptimizedLinearLayout {
+
+    private static final String TAG = "KeyguardUserSwitcherListView";
+    private static final boolean DEBUG = KeyguardConstants.DEBUG;
+
+    private static final int ANIMATION_DURATION_OPENING = 360;
+    private static final int ANIMATION_DURATION_CLOSING = 240;
+
+    private boolean mAnimating;
+    private final AppearAnimationUtils mAppearAnimationUtils;
+    private final DisappearAnimationUtils mDisappearAnimationUtils;
+
+    public KeyguardUserSwitcherListView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setClipChildren(false);
+        mAppearAnimationUtils = new AppearAnimationUtils(context, ANIMATION_DURATION_OPENING,
+                -0.5f, 0.5f, Interpolators.FAST_OUT_SLOW_IN);
+        mDisappearAnimationUtils = new DisappearAnimationUtils(context, ANIMATION_DURATION_CLOSING,
+                0.5f, 0.5f, Interpolators.FAST_OUT_LINEAR_IN);
+    }
+
+    /**
+     * Set the amount (ratio) that the device has transitioned to doze.
+     *
+     * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
+     */
+    void setDarkAmount(float darkAmount) {
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View v = getChildAt(i);
+            if (v instanceof KeyguardUserDetailItemView) {
+                ((KeyguardUserDetailItemView) v).setDarkAmount(darkAmount);
+            }
+        }
+    }
+
+    boolean isAnimating() {
+        return mAnimating;
+    }
+
+    /**
+     * Update visibilities of this view and child views for when the user list is open or closed.
+     * If closed, this hides everything but the first item (which is always the current user).
+     */
+    void updateVisibilities(boolean open, boolean animate) {
+        if (DEBUG) {
+            Log.d(TAG, String.format("updateVisibilities: open=%b animate=%b childCount=%d",
+                    open, animate, getChildCount()));
+        }
+
+        mAnimating = false;
+
+        int userListCount = getChildCount();
+        if (userListCount > 0) {
+            // The first child is always the current user.
+            KeyguardUserDetailItemView currentUserView = ((KeyguardUserDetailItemView) getChildAt(
+                    0));
+            currentUserView.updateVisibilities(true /* showItem */, open /* showTextName */,
+                    animate);
+            currentUserView.setClickable(true);
+            currentUserView.clearAnimation();
+        }
+
+        if (userListCount <= 1) {
+            return;
+        }
+
+        if (animate) {
+            // Create an array of all the remaining users (that aren't the current user).
+            KeyguardUserDetailItemView[] otherUserViews =
+                    new KeyguardUserDetailItemView[userListCount - 1];
+            for (int i = 1, n = 0; i < userListCount; i++, n++) {
+                otherUserViews[n] = (KeyguardUserDetailItemView) getChildAt(i);
+
+                // Update clickable state immediately so that the menu feels more responsive
+                otherUserViews[n].setClickable(open);
+
+                // Before running the animation, ensure visibility is set correctly
+                otherUserViews[n].updateVisibilities(
+                        true /* showItem */, true /* showTextName */, false /* animate */);
+                otherUserViews[n].clearAnimation();
+            }
+
+            setClipChildren(false);
+            setClipToPadding(false);
+
+            mAnimating = true;
+
+            final int nonCurrentUserCount = otherUserViews.length;
+            if (open) {
+                mAppearAnimationUtils.startAnimation(otherUserViews, () -> {
+                    setClipChildren(true);
+                    setClipToPadding(true);
+                    mAnimating = false;
+                });
+            } else {
+                mDisappearAnimationUtils.startAnimation(otherUserViews, () -> {
+                    setClipChildren(true);
+                    setClipToPadding(true);
+                    for (int i = 0; i < nonCurrentUserCount; i++) {
+                        otherUserViews[i].updateVisibilities(
+                                false /* showItem */, true /* showTextName */, false /* animate */);
+                    }
+                    mAnimating = false;
+                });
+            }
+        } else {
+            for (int i = 1; i < userListCount; i++) {
+                KeyguardUserDetailItemView nonCurrentUserView =
+                        ((KeyguardUserDetailItemView) getChildAt(i));
+                nonCurrentUserView.clearAnimation();
+                nonCurrentUserView.updateVisibilities(
+                        open /* showItem */, true /* showTextName */, false /* animate */);
+                nonCurrentUserView.setClickable(open);
+            }
+        }
+    }
+
+    /**
+     * Replaces the view at the specified position in the group.
+     *
+     * @param index the position in the group of the view to remove
+     */
+    void replaceView(KeyguardUserDetailItemView newView, int index) {
+        removeViewAt(index);
+        addView(newView, index);
+    }
+
+    /**
+     * Removes the last view in the group.
+     */
+    void removeLastView() {
+        int lastIndex = getChildCount() - 1;
+        removeViewAt(lastIndex);
+    }
+}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java
similarity index 61%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java
index 14d57bf..3f0e23f 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java
@@ -14,6 +14,18 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package com.android.systemui.statusbar.policy;
 
-parcelable ExternalTimeSuggestion;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+/**
+ * The container for the user switcher on Keyguard.
+ */
+public class KeyguardUserSwitcherView extends FrameLayout {
+
+    public KeyguardUserSwitcherView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 0fe338e..1ab7652 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -26,6 +26,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.provider.Settings.Global;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.CellSignalStrength;
 import android.telephony.CellSignalStrengthCdma;
 import android.telephony.ServiceState;
@@ -34,12 +35,18 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
+import android.telephony.ims.RegistrationManager.RegistrationCallback;
 import android.text.Html;
 import android.text.TextUtils;
 import android.util.FeatureFlagUtils;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.AccessibilityContentDescriptions;
 import com.android.settingslib.SignalIcon.MobileIconGroup;
 import com.android.settingslib.SignalIcon.MobileState;
 import com.android.settingslib.Utils;
@@ -65,13 +72,19 @@
  */
 public class MobileSignalController extends SignalController<MobileState, MobileIconGroup> {
     private static final SimpleDateFormat SSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
-
+    private static final int STATUS_HISTORY_SIZE = 64;
+    private static final int IMS_TYPE_WWAN = 1;
+    private static final int IMS_TYPE_WLAN = 2;
+    private static final int IMS_TYPE_WLAN_CROSS_SIM = 3;
     private final TelephonyManager mPhone;
+    private final ImsMmTelManager mImsMmTelManager;
     private final SubscriptionDefaults mDefaults;
     private final String mNetworkNameDefault;
     private final String mNetworkNameSeparator;
     private final ContentObserver mObserver;
     private final boolean mProviderModel;
+    private final Handler mReceiverHandler;
+    private int mImsType = IMS_TYPE_WWAN;
     // Save entire info for logging, we only use the id.
     final SubscriptionInfo mSubscriptionInfo;
     // @VisibleForDemoMode
@@ -86,16 +99,21 @@
                     TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
     private ServiceState mServiceState;
     private SignalStrength mSignalStrength;
+    private int mLastLevel;
     private MobileIconGroup mDefaultIcons;
     private Config mConfig;
     @VisibleForTesting
     boolean mInflateSignalStrengths = false;
     private MobileStatusTracker.Callback mCallback;
+    private RegistrationCallback mRegistrationCallback;
+    private int mLastWwanLevel;
+    private int mLastWlanLevel;
+    private int mLastWlanCrossSimLevel;
     @VisibleForTesting
     MobileStatusTracker mMobileStatusTracker;
 
-    // Save the previous HISTORY_SIZE states for logging.
-    private final String[] mMobileStatusHistory = new String[HISTORY_SIZE];
+    // Save the previous STATUS_HISTORY_SIZE states for logging.
+    private final String[] mMobileStatusHistory = new String[STATUS_HISTORY_SIZE];
     // Where to copy the next state into.
     private int mMobileStatusHistoryIndex;
 
@@ -116,6 +134,7 @@
                 .toString();
         mNetworkNameDefault = getTextIfExists(
                 com.android.internal.R.string.lockscreen_carrier_default).toString();
+        mReceiverHandler = new Handler(receiverLooper);
 
         mNetworkToIconLookup = mapIconSets(mConfig);
         mDefaultIcons = getDefaultIcons(mConfig);
@@ -133,6 +152,8 @@
             }
         };
         mCallback = new MobileStatusTracker.Callback() {
+            private String mLastStatus;
+
             @Override
             public void onMobileStatusChanged(boolean updateTelephony,
                     MobileStatus mobileStatus) {
@@ -141,11 +162,15 @@
                             + " updateTelephony=" + updateTelephony
                             + " mobileStatus=" + mobileStatus.toString());
                 }
-                String status = new StringBuilder()
-                        .append(SSDF.format(System.currentTimeMillis())).append(",")
-                        .append(mobileStatus.toString())
-                        .toString();
-                recordLastMobileStatus(status);
+                String currentStatus = mobileStatus.toString();
+                if (!currentStatus.equals(mLastStatus)) {
+                    mLastStatus = currentStatus;
+                    String status = new StringBuilder()
+                            .append(SSDF.format(System.currentTimeMillis())).append(",")
+                            .append(currentStatus)
+                            .toString();
+                    recordLastMobileStatus(status);
+                }
                 updateMobileStatus(mobileStatus);
                 if (updateTelephony) {
                     updateTelephony();
@@ -154,6 +179,53 @@
                 }
             }
         };
+
+        mRegistrationCallback = new RegistrationCallback() {
+            @Override
+            public void onRegistered(ImsRegistrationAttributes attributes) {
+                Log.d(mTag, "onRegistered: " + "attributes=" + attributes);
+                int imsTransportType = attributes.getTransportType();
+                int registrationAttributes = attributes.getAttributeFlags();
+                if (imsTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
+                    mImsType = IMS_TYPE_WWAN;
+                    IconState statusIcon = new IconState(
+                            true,
+                            getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false),
+                            getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false));
+                    notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+                } else if (imsTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
+                    if (registrationAttributes == 0) {
+                        mImsType = IMS_TYPE_WLAN;
+                        IconState statusIcon = new IconState(
+                                true,
+                                getCallStrengthIcon(mLastWlanLevel, /* isWifi= */true),
+                                getCallStrengthDescription(mLastWlanLevel, /* isWifi= */true));
+                        notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+                    } else if (registrationAttributes
+                            == ImsRegistrationAttributes.ATTR_EPDG_OVER_CELL_INTERNET) {
+                        mImsType = IMS_TYPE_WLAN_CROSS_SIM;
+                        IconState statusIcon = new IconState(
+                                true,
+                                getCallStrengthIcon(mLastWlanCrossSimLevel, /* isWifi= */false),
+                                getCallStrengthDescription(
+                                        mLastWlanCrossSimLevel, /* isWifi= */false));
+                        notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+                    }
+                }
+            }
+
+            @Override
+            public void onUnregistered(ImsReasonInfo info) {
+                Log.d(mTag, "onDeregistered: " + "info=" + info);
+                mImsType = IMS_TYPE_WWAN;
+                IconState statusIcon = new IconState(
+                        true,
+                        getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false),
+                        getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false));
+                notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+            }
+        };
+        mImsMmTelManager = ImsMmTelManager.createForSubscriptionId(info.getSubscriptionId());
         mMobileStatusTracker = new MobileStatusTracker(mPhone, receiverLooper,
                 info, mDefaults, mCallback);
         mProviderModel = FeatureFlagUtils.isEnabled(
@@ -202,14 +274,41 @@
         mContext.getContentResolver().registerContentObserver(Global.getUriFor(
                 Global.MOBILE_DATA + mSubscriptionInfo.getSubscriptionId()),
                 true, mObserver);
+        if (mProviderModel) {
+            mReceiverHandler.post(mTryRegisterIms);
+        }
     }
 
+    // There is no listener to monitor whether the IMS service is ready, so we have to retry the
+    // IMS registration.
+    private final Runnable mTryRegisterIms = new Runnable() {
+        private static final int MAX_RETRY = 12;
+        private int mRetryCount;
+
+        @Override
+        public void run() {
+            try {
+                mRetryCount++;
+                mImsMmTelManager.registerImsRegistrationCallback(
+                        mReceiverHandler::post, mRegistrationCallback);
+                Log.d(mTag, "registerImsRegistrationCallback succeeded");
+            } catch (RuntimeException | ImsException e) {
+                if (mRetryCount < MAX_RETRY) {
+                    Log.e(mTag, mRetryCount + " registerImsRegistrationCallback failed", e);
+                    // Wait for 5 seconds to retry
+                    mReceiverHandler.postDelayed(mTryRegisterIms, 5000);
+                }
+            }
+        }
+    };
+
     /**
      * Stop listening for phone state changes.
      */
     public void unregisterListener() {
         mMobileStatusTracker.setListening(false);
         mContext.getContentResolver().unregisterContentObserver(mObserver);
+        mImsMmTelManager.unregisterImsRegistrationCallback(mRegistrationCallback);
     }
 
     private void updateInflateSignalStrength() {
@@ -452,9 +551,9 @@
     /**
      * Extracts the CellSignalStrengthCdma from SignalStrength then returns the level
      */
-    private final int getCdmaLevel() {
+    private int getCdmaLevel(SignalStrength signalStrength) {
         List<CellSignalStrengthCdma> signalStrengthCdma =
-            mSignalStrength.getCellSignalStrengths(CellSignalStrengthCdma.class);
+                signalStrength.getCellSignalStrengths(CellSignalStrengthCdma.class);
         if (!signalStrengthCdma.isEmpty()) {
             return signalStrengthCdma.get(0).getLevel();
         }
@@ -467,6 +566,7 @@
         mCurrentState.dataSim = mobileStatus.dataSim;
         mCurrentState.carrierNetworkChangeMode = mobileStatus.carrierNetworkChangeMode;
         mDataState = mobileStatus.dataState;
+        notifyMobileLevelChangeIfNecessary(mobileStatus.signalStrength);
         mSignalStrength = mobileStatus.signalStrength;
         mTelephonyDisplayInfo = mobileStatus.telephonyDisplayInfo;
         int lastVoiceState = mServiceState != null ? mServiceState.getState() : -1;
@@ -481,9 +581,117 @@
                 && (lastVoiceState == -1
                         || (lastVoiceState == ServiceState.STATE_IN_SERVICE
                                 || currentVoiceState == ServiceState.STATE_IN_SERVICE))) {
-            notifyNoCallingStatusChange(
-                    currentVoiceState != ServiceState.STATE_IN_SERVICE,
-                    mSubscriptionInfo.getSubscriptionId());
+            boolean isNoCalling = currentVoiceState != ServiceState.STATE_IN_SERVICE;
+            IconState statusIcon = new IconState(isNoCalling, R.drawable.ic_qs_no_calling_sms,
+                    getTextIfExists(AccessibilityContentDescriptions.NO_CALLING).toString());
+            notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+        }
+    }
+
+    private int getCallStrengthIcon(int level, boolean isWifi) {
+        return isWifi ? TelephonyIcons.WIFI_CALL_STRENGTH_ICONS[level]
+                : TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[level];
+    }
+
+    private String getCallStrengthDescription(int level, boolean isWifi) {
+        return isWifi
+                ? getTextIfExists(AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH[level])
+                        .toString()
+                : getTextIfExists(AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[level])
+                        .toString();
+    }
+
+    void refreshCallIndicator(SignalCallback callback) {
+        boolean isNoCalling = mServiceState != null
+                && mServiceState.getState() != ServiceState.STATE_IN_SERVICE;
+        IconState statusIcon = new IconState(isNoCalling, R.drawable.ic_qs_no_calling_sms,
+                getTextIfExists(AccessibilityContentDescriptions.NO_CALLING).toString());
+        callback.setCallIndicator(statusIcon, mSubscriptionInfo.getSubscriptionId());
+
+        switch (mImsType) {
+            case IMS_TYPE_WWAN:
+                statusIcon = new IconState(
+                        true,
+                        getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false),
+                        getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false));
+                break;
+            case IMS_TYPE_WLAN:
+                statusIcon = new IconState(
+                        true,
+                        getCallStrengthIcon(mLastWlanLevel, /* isWifi= */true),
+                        getCallStrengthDescription(mLastWlanLevel, /* isWifi= */true));
+                break;
+            case IMS_TYPE_WLAN_CROSS_SIM:
+                statusIcon = new IconState(
+                        true,
+                        getCallStrengthIcon(mLastWlanCrossSimLevel, /* isWifi= */false),
+                        getCallStrengthDescription(mLastWlanCrossSimLevel, /* isWifi= */false));
+        }
+        callback.setCallIndicator(statusIcon, mSubscriptionInfo.getSubscriptionId());
+    }
+
+    void notifyWifiLevelChange(int level) {
+        if (!mProviderModel) {
+            return;
+        }
+        Log.d("mTag", "notifyWifiLevelChange " + mImsType);
+        mLastWlanLevel = level;
+        if (mImsType != IMS_TYPE_WLAN) {
+            return;
+        }
+        IconState statusIcon = new IconState(
+                true,
+                getCallStrengthIcon(level, /* isWifi= */true),
+                getCallStrengthDescription(level, /* isWifi= */true));
+        notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+    }
+
+    void notifyDefaultMobileLevelChange(int level) {
+        if (!mProviderModel) {
+            return;
+        }
+        Log.d("mTag", "notifyDefaultMobileLevelChange " + mImsType);
+        mLastWlanCrossSimLevel = level;
+        if (mImsType != IMS_TYPE_WLAN_CROSS_SIM) {
+            return;
+        }
+        IconState statusIcon = new IconState(
+                true,
+                getCallStrengthIcon(level, /* isWifi= */false),
+                getCallStrengthDescription(level, /* isWifi= */false));
+        notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+    }
+
+    void notifyMobileLevelChangeIfNecessary(SignalStrength signalStrength) {
+        if (!mProviderModel) {
+            return;
+        }
+        int newLevel = getSignalLevel(signalStrength);
+        if (newLevel != mLastLevel) {
+            mLastLevel = newLevel;
+            Log.d("mTag", "notifyMobileLevelChangeIfNecessary " + mImsType);
+            mLastWwanLevel = newLevel;
+            if (mImsType == IMS_TYPE_WWAN) {
+                IconState statusIcon = new IconState(
+                        true,
+                        getCallStrengthIcon(newLevel, /* isWifi= */false),
+                        getCallStrengthDescription(newLevel, /* isWifi= */false));
+                notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+            }
+            if (mCurrentState.dataSim) {
+                mNetworkController.notifyDefaultMobileLevelChange(newLevel);
+            }
+        }
+    }
+
+    int getSignalLevel(SignalStrength signalStrength) {
+        if (signalStrength == null) {
+            return 0;
+        }
+        if (!signalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {
+            return getCdmaLevel(signalStrength);
+        } else {
+            return signalStrength.getLevel();
         }
     }
 
@@ -501,11 +709,7 @@
         checkDefaultData();
         mCurrentState.connected = Utils.isInService(mServiceState) && mSignalStrength != null;
         if (mCurrentState.connected) {
-            if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {
-                mCurrentState.level = getCdmaLevel();
-            } else {
-                mCurrentState.level = mSignalStrength.getLevel();
-            }
+            mCurrentState.level = getSignalLevel(mSignalStrength);
         }
 
         String iconKey = getIconKey(mTelephonyDisplayInfo);
@@ -577,7 +781,13 @@
     }
 
     private void recordLastMobileStatus(String mobileStatus) {
-        mMobileStatusHistory[mMobileStatusHistoryIndex++ & (HISTORY_SIZE - 1)] = mobileStatus;
+        mMobileStatusHistory[mMobileStatusHistoryIndex] = mobileStatus;
+        mMobileStatusHistoryIndex = (mMobileStatusHistoryIndex + 1) % STATUS_HISTORY_SIZE;
+    }
+
+    @VisibleForTesting
+    void setImsType(int imsType) {
+        mImsType = imsType;
     }
 
     @Override
@@ -592,15 +802,17 @@
         pw.println("  isDataDisabled=" + isDataDisabled() + ",");
         pw.println("  MobileStatusHistory");
         int size = 0;
-        for (int i = 0; i < HISTORY_SIZE; i++) {
-            if (mMobileStatusHistory[i] != null) size++;
+        for (int i = 0; i < STATUS_HISTORY_SIZE; i++) {
+            if (mMobileStatusHistory[i] != null) {
+                size++;
+            }
         }
         // Print out the previous states in ordered number.
-        for (int i = mMobileStatusHistoryIndex + HISTORY_SIZE - 1;
-                i >= mMobileStatusHistoryIndex + HISTORY_SIZE - size; i--) {
+        for (int i = mMobileStatusHistoryIndex + STATUS_HISTORY_SIZE - 1;
+                i >= mMobileStatusHistoryIndex + STATUS_HISTORY_SIZE - size; i--) {
             pw.println("  Previous MobileStatus("
-                    + (mMobileStatusHistoryIndex + HISTORY_SIZE - i) + "): "
-                    + mMobileStatusHistory[i & (HISTORY_SIZE - 1)]);
+                    + (mMobileStatusHistoryIndex + STATUS_HISTORY_SIZE - i) + "): "
+                    + mMobileStatusHistory[i & (STATUS_HISTORY_SIZE - 1)]);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index e60d5c5..0a9fead 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -53,7 +53,7 @@
 
         /**
          * Callback for listeners to be able to update the state of any UI tracking connectivity
-         *  @param statusIcon the icon that should be shown in the status bar
+         * @param statusIcon the icon that should be shown in the status bar
          * @param qsIcon the icon to show in Quick Settings
          * @param statusType the resId of the data type icon (e.g. LTE) to show in the status bar
          * @param qsType similar to above, the resId of the data type icon to show in Quick Settings
@@ -95,11 +95,11 @@
                 boolean noNetworksAvailable) {}
 
         /**
-         * Callback for listeners to be able to update the no calling & SMS status
-         * @param noCalling whether the calling and SMS is not working.
+         * Callback for listeners to be able to update the call indicator
+         * @param statusIcon the icon for the call indicator
          * @param subId subscription ID for which to update the UI
          */
-        default void setNoCallingStatus(boolean noCalling, int subId) {}
+        default void setCallIndicator(IconState statusIcon, int subId) {}
     }
 
     public interface EmergencyListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 80c7811..9f92142 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -554,6 +554,20 @@
         return controller != null ? controller.getNetworkNameForCarrierWiFi() : "";
     }
 
+    void notifyWifiLevelChange(int level) {
+        for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+            MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
+            mobileSignalController.notifyWifiLevelChange(level);
+        }
+    }
+
+    void notifyDefaultMobileLevelChange(int level) {
+        for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+            MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
+            mobileSignalController.notifyDefaultMobileLevelChange(level);
+        }
+    }
+
     private void notifyControllersMobileDataChanged() {
         for (int i = 0; i < mMobileSignalControllers.size(); i++) {
             MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
@@ -623,6 +637,9 @@
         for (int i = 0; i < mMobileSignalControllers.size(); i++) {
             MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
             mobileSignalController.notifyListeners(cb);
+            if (mProviderModel) {
+                mobileSignalController.refreshCallIndicator(cb);
+            }
         }
         mCallbackHandler.setListening(cb, true);
     }
@@ -1272,7 +1289,8 @@
     }
 
     private void recordLastNetworkCallback(String callback) {
-        mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)] = callback;
+        mHistory[mHistoryIndex] = callback;
+        mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
     }
 
     private SubscriptionInfo addSignalController(int id, int simSlotIndex) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
index 554145e..4b6722c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
@@ -23,6 +23,7 @@
 
 import com.android.settingslib.SignalIcon.IconGroup;
 import com.android.settingslib.SignalIcon.State;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
 
 import java.io.PrintWriter;
@@ -167,8 +168,8 @@
         }
     }
 
-    protected final void notifyNoCallingStatusChange(boolean noCalling, int subId) {
-        mCallbackHandler.setNoCallingStatus(noCalling, subId);
+    protected final void notifyCallStateChange(IconState statusIcon, int subId) {
+        mCallbackHandler.setCallIndicator(statusIcon, subId);
     }
 
     /**
@@ -187,7 +188,8 @@
      * and last value of any state data.
      */
     protected void recordLastState() {
-        mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)].copyFrom(mLastState);
+        mHistory[mHistoryIndex].copyFrom(mLastState);
+        mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
     }
 
     public void dump(PrintWriter pw) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 68d74ef..019ab47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -423,7 +423,7 @@
         }
     }
 
-    private void showExitGuestDialog(int id) {
+    protected void showExitGuestDialog(int id) {
         int newId = UserHandle.USER_SYSTEM;
         if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) {
             UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
@@ -680,11 +680,7 @@
             if (item.isAddUser) {
                 iconRes = R.drawable.ic_add_circle;
             } else if (item.isGuest) {
-                if (item.isCurrent) {
-                    iconRes = R.drawable.ic_exit_to_app;
-                } else {
-                    iconRes = R.drawable.ic_avatar_guest_user;
-                }
+                iconRes = R.drawable.ic_avatar_guest_user;
             } else {
                 iconRes = R.drawable.ic_avatar_user;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index 1fd2ccb..47207d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -202,6 +202,7 @@
         mCurrentState.connected = mWifiTracker.connected;
         mCurrentState.ssid = mWifiTracker.ssid;
         mCurrentState.rssi = mWifiTracker.rssi;
+        notifyWifiLevelChangeIfNecessary(mWifiTracker.level);
         mCurrentState.level = mWifiTracker.level;
         mCurrentState.statusLabel = mWifiTracker.statusLabel;
         mCurrentState.isCarrierMerged = mWifiTracker.isCarrierMerged;
@@ -211,6 +212,12 @@
                         : mUnmergedWifiIconGroup;
     }
 
+    void notifyWifiLevelChangeIfNecessary(int level) {
+        if (level != mCurrentState.level) {
+            mNetworkController.notifyWifiLevelChange(level);
+        }
+    }
+
     boolean isCarrierMergedWifi(int subId) {
         return mCurrentState.isDefault
                 && mCurrentState.isCarrierMerged && (mCurrentState.subId == subId);
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index 7863914..3f01414 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -15,11 +15,11 @@
  */
 package com.android.systemui.theme;
 
+import android.content.om.FabricatedOverlay;
 import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayManager;
 import android.content.om.OverlayManagerTransaction;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -53,13 +53,6 @@
 public class ThemeOverlayApplier implements Dumpable {
     private static final String TAG = "ThemeOverlayApplier";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    private static final boolean MONET_ENABLED = SystemProperties
-            .getBoolean("persist.sysui.monet", false);
-
-    @VisibleForTesting
-    static final String MONET_ACCENT_COLOR_PACKAGE = "com.android.theme.accentcolor.color";
-    @VisibleForTesting
-    static final String MONET_SYSTEM_PALETTE_PACKAGE = "com.android.theme.systemcolors.color";
 
     @VisibleForTesting
     static final String ANDROID_PACKAGE = "android";
@@ -68,10 +61,8 @@
     @VisibleForTesting
     static final String SYSUI_PACKAGE = "com.android.systemui";
 
-    @VisibleForTesting
     static final String OVERLAY_CATEGORY_ACCENT_COLOR =
             "android.theme.customization.accent_color";
-    @VisibleForTesting
     static final String OVERLAY_CATEGORY_SYSTEM_PALETTE =
             "android.theme.customization.system_palette";
     @VisibleForTesting
@@ -120,16 +111,6 @@
             OVERLAY_CATEGORY_ICON_ANDROID,
             OVERLAY_CATEGORY_ICON_SYSUI);
 
-    /**
-     * List of main colors of Monet themes. These are extracted from overlays installed
-     * on the system.
-     */
-    private final ArrayList<Integer> mMainSystemColors = new ArrayList<>();
-    /**
-     * Same as above, but providing accent colors instead of a system palette.
-     */
-    private final ArrayList<Integer> mAccentColors = new ArrayList<>();
-
     /* Allowed overlay categories for each target package. */
     private final Map<String, Set<String>> mTargetPackageToCategories = new ArrayMap<>();
     /* Target package for each overlay category. */
@@ -165,64 +146,17 @@
         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_LAUNCHER, mLauncherPackage);
         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_THEME_PICKER, mThemePickerPackage);
 
-        collectMonetSystemOverlays();
         dumpManager.registerDumpable(TAG, this);
     }
 
     /**
-     * List of accent colors available as Monet overlays.
-     */
-    List<Integer> getAvailableAccentColors() {
-        return mAccentColors;
-    }
-
-    /**
-     * List of main system colors available as Monet overlays.
-     */
-    List<Integer> getAvailableSystemColors() {
-        return mMainSystemColors;
-    }
-
-    private void collectMonetSystemOverlays() {
-        if (!MONET_ENABLED) {
-            return;
-        }
-        List<OverlayInfo> androidOverlays = mOverlayManager
-                .getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM);
-        for (OverlayInfo overlayInfo : androidOverlays) {
-            String packageName = overlayInfo.packageName;
-            if (DEBUG) {
-                Log.d(TAG, "Processing overlay " + packageName);
-            }
-            if (OVERLAY_CATEGORY_SYSTEM_PALETTE.equals(overlayInfo.category)
-                    && packageName.startsWith(MONET_SYSTEM_PALETTE_PACKAGE)) {
-                try {
-                    String color = packageName.replace(MONET_SYSTEM_PALETTE_PACKAGE, "");
-                    mMainSystemColors.add(Integer.parseInt(color, 16));
-                } catch (NumberFormatException e) {
-                    Log.w(TAG, "Invalid package name for overlay " + packageName, e);
-                }
-            } else if (OVERLAY_CATEGORY_ACCENT_COLOR.equals(overlayInfo.category)
-                    && packageName.startsWith(MONET_ACCENT_COLOR_PACKAGE)) {
-                try {
-                    String color = packageName.replace(MONET_ACCENT_COLOR_PACKAGE, "");
-                    mAccentColors.add(Integer.parseInt(color, 16));
-                } catch (NumberFormatException e) {
-                    Log.w(TAG, "Invalid package name for overlay " + packageName, e);
-                }
-            } else if (DEBUG) {
-                Log.d(TAG, "Unknown overlay: " + packageName + " category: "
-                        + overlayInfo.category);
-            }
-        }
-    }
-
-    /**
      * Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that
      * affect sysui will also be applied to the system user.
      */
     void applyCurrentUserOverlays(
-            Map<String, String> categoryToPackage, Set<UserHandle> userHandles) {
+            Map<String, OverlayIdentifier> categoryToPackage,
+            FabricatedOverlay[] pendingCreation,
+            Set<UserHandle> userHandles) {
         // Disable all overlays that have not been specified in the user setting.
         final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES);
         overlayCategoriesToDisable.removeAll(categoryToPackage.keySet());
@@ -241,11 +175,16 @@
                 .collect(Collectors.toList());
 
         OverlayManagerTransaction.Builder transaction = getTransactionBuilder();
+        if (pendingCreation != null) {
+            for (FabricatedOverlay overlay : pendingCreation) {
+                transaction.registerFabricatedOverlay(overlay);
+            }
+        }
+
         // Toggle overlays in the order of THEME_CATEGORIES.
         for (String category : THEME_CATEGORIES) {
             if (categoryToPackage.containsKey(category)) {
-                OverlayIdentifier overlayInfo =
-                        new OverlayIdentifier(categoryToPackage.get(category));
+                OverlayIdentifier overlayInfo = categoryToPackage.get(category);
                 setEnabled(transaction, overlayInfo, category, userHandles, true);
             }
         }
@@ -284,7 +223,7 @@
      */
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
-        pw.println("mMainSystemColors=" + mMainSystemColors.size());
-        pw.println("mAccentColors=" + mAccentColors.size());
+        pw.println("mTargetPackageToCategories=" + mTargetPackageToCategories);
+        pw.println("mCategoryToTargetPackage=" + mCategoryToTargetPackage);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index d9f4744..522a42b 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -18,6 +18,7 @@
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
@@ -25,6 +26,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.graphics.Color;
@@ -40,7 +43,6 @@
 import androidx.annotation.NonNull;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.SystemUI;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -48,6 +50,7 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -59,10 +62,10 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Collection;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
@@ -77,9 +80,12 @@
  */
 @SysUISingleton
 public class ThemeOverlayController extends SystemUI implements Dumpable {
-    private static final String TAG = "ThemeOverlayController";
+    protected static final String TAG = "ThemeOverlayController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    protected static final int MAIN = 0;
+    protected static final int ACCENT = 1;
+
     // If lock screen wallpaper colors should also be considered when selecting the theme.
     // Doing this has performance impact, given that overlays would need to be swapped when
     // the device unlocks.
@@ -95,16 +101,19 @@
     private final Handler mBgHandler;
     private final WallpaperManager mWallpaperManager;
     private final KeyguardStateController mKeyguardStateController;
+    private final boolean mIsMonetEnabled;
     private WallpaperColors mLockColors;
     private WallpaperColors mSystemColors;
-    // Color extracted from wallpaper, NOT the color used on the overlay
+    // If fabricated overlays were already created for the current theme.
+    private boolean mNeedsOverlayCreation;
+    // Dominant olor extracted from wallpaper, NOT the color used on the overlay
     protected int mMainWallpaperColor = Color.TRANSPARENT;
-    // Color extracted from wallpaper, NOT the color used on the overlay
+    // Accent color extracted from wallpaper, NOT the color used on the overlay
     protected int mWallpaperAccentColor = Color.TRANSPARENT;
-    // Main system color that maps to an overlay color
-    private int mSystemOverlayColor = Color.TRANSPARENT;
-    // Accent color that maps to an overlay color
-    private int mAccentOverlayColor = Color.TRANSPARENT;
+    // System colors overlay
+    private FabricatedOverlay mSystemOverlay;
+    // Accent colors overlay
+    private FabricatedOverlay mAccentOverlay;
 
     @Inject
     public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher,
@@ -112,9 +121,10 @@
             @Background Executor bgExecutor, ThemeOverlayApplier themeOverlayApplier,
             SecureSettings secureSettings, WallpaperManager wallpaperManager,
             UserManager userManager, KeyguardStateController keyguardStateController,
-            DumpManager dumpManager) {
+            DumpManager dumpManager, FeatureFlags featureFlags) {
         super(context);
 
+        mIsMonetEnabled = featureFlags.isMonetEnabled();
         mBroadcastDispatcher = broadcastDispatcher;
         mUserManager = userManager;
         mBgExecutor = bgExecutor;
@@ -221,20 +231,16 @@
         mMainWallpaperColor = mainColor;
         mWallpaperAccentColor = accentCandidate;
 
-        // Let's compare these colors to our finite set of overlays, and then pick an overlay.
-        List<Integer> systemColors = mThemeManager.getAvailableSystemColors();
-        List<Integer> accentColors = mThemeManager.getAvailableAccentColors();
-
-        if (systemColors.size() == 0 || accentColors.size() == 0) {
+        if (mIsMonetEnabled) {
+            mSystemOverlay = getOverlay(mMainWallpaperColor, MAIN);
+            mAccentOverlay = getOverlay(mWallpaperAccentColor, ACCENT);
+            mNeedsOverlayCreation = true;
             if (DEBUG) {
-                Log.d(TAG, "Cannot apply system theme, palettes are unavailable");
+                Log.d(TAG, "fetched overlays. system: " + mSystemOverlay + " accent: "
+                        + mAccentOverlay);
             }
-            return;
         }
 
-        mSystemOverlayColor = getClosest(systemColors, mMainWallpaperColor);
-        mAccentOverlayColor = getClosest(accentColors, mWallpaperAccentColor);
-
         updateThemeOverlays();
     }
 
@@ -257,42 +263,10 @@
     }
 
     /**
-     * Given a color and a list of candidates, return the candidate that's the most similar to the
-     * given color.
+     * Given a color candidate, return an overlay definition.
      */
-    protected int getClosest(List<Integer> candidates, int color) {
-        float[] hslMain = new float[3];
-        float[] hslCandidate = new float[3];
-
-        ColorUtils.RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hslMain);
-        hslMain[0] /= 360f;
-
-        // To close to white or black, let's use the default system theme instead of
-        // applying a colorized one.
-        if (hslMain[2] < 0.05 || hslMain[2] > 0.95) {
-            return Color.TRANSPARENT;
-        }
-
-        float minDistance = Float.MAX_VALUE;
-        int closestColor = Color.TRANSPARENT;
-        for (int candidate: candidates) {
-            ColorUtils.RGBToHSL(Color.red(candidate), Color.green(candidate), Color.blue(candidate),
-                    hslCandidate);
-            hslCandidate[0] /= 360f;
-
-            float sqDistance = squared(hslCandidate[0] - hslMain[0])
-                    + squared(hslCandidate[1] - hslMain[1])
-                    + squared(hslCandidate[2] - hslMain[2]);
-            if (sqDistance < minDistance) {
-                minDistance = sqDistance;
-                closestColor = candidate;
-            }
-        }
-        return closestColor;
-    }
-
-    private static float squared(float f) {
-        return f * f;
+    protected @Nullable FabricatedOverlay getOverlay(int color, int type) {
+        return null;
     }
 
     private void updateThemeOverlays() {
@@ -301,20 +275,15 @@
                 Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
                 currentUser);
         if (DEBUG) Log.d(TAG, "updateThemeOverlays. Setting: " + overlayPackageJson);
-        boolean hasSystemPalette = false;
-        boolean hasAccentColor = false;
-        final Map<String, String> categoryToPackage = new ArrayMap<>();
+        final Map<String, OverlayIdentifier> categoryToPackage = new ArrayMap<>();
         if (!TextUtils.isEmpty(overlayPackageJson)) {
             try {
                 JSONObject object = new JSONObject(overlayPackageJson);
                 for (String category : ThemeOverlayApplier.THEME_CATEGORIES) {
                     if (object.has(category)) {
-                        if (category.equals(OVERLAY_CATEGORY_ACCENT_COLOR)) {
-                            hasAccentColor = true;
-                        } else if (category.equals(OVERLAY_CATEGORY_SYSTEM_PALETTE)) {
-                            hasSystemPalette = true;
-                        }
-                        categoryToPackage.put(category, object.getString(category));
+                        OverlayIdentifier identifier =
+                                new OverlayIdentifier(object.getString(category));
+                        categoryToPackage.put(category, identifier);
                     }
                 }
             } catch (JSONException e) {
@@ -322,17 +291,41 @@
             }
         }
 
-        // Let's apply the system palette, but only if it was not overridden by the style picker.
-        if (!hasSystemPalette && mSystemOverlayColor != Color.TRANSPARENT) {
-            categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE,
-                    ThemeOverlayApplier.MONET_SYSTEM_PALETTE_PACKAGE
-                            + getColorString(mSystemOverlayColor));
+        // Let's generate system overlay if the style picker decided to override it.
+        OverlayIdentifier systemPalette = categoryToPackage.get(OVERLAY_CATEGORY_SYSTEM_PALETTE);
+        if (mIsMonetEnabled && systemPalette != null && systemPalette.getPackageName() != null) {
+            try {
+                int color = Integer.parseInt(systemPalette.getPackageName().toLowerCase(), 16);
+                mSystemOverlay = getOverlay(color, MAIN);
+                mNeedsOverlayCreation = true;
+                categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
+            } catch (NumberFormatException e) {
+                Log.w(TAG, "Invalid color definition: " + systemPalette.getPackageName());
+            }
         }
-        // Same for the accent color
-        if (!hasAccentColor && mAccentOverlayColor != Color.TRANSPARENT) {
-            categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR,
-                    ThemeOverlayApplier.MONET_ACCENT_COLOR_PACKAGE
-                            + getColorString(mAccentOverlayColor));
+
+        // Same for accent color.
+        OverlayIdentifier accentPalette = categoryToPackage.get(OVERLAY_CATEGORY_ACCENT_COLOR);
+        if (mIsMonetEnabled && accentPalette != null && accentPalette.getPackageName() != null) {
+            try {
+                int color = Integer.parseInt(accentPalette.getPackageName().toLowerCase(), 16);
+                mAccentOverlay = getOverlay(color, ACCENT);
+                mNeedsOverlayCreation = true;
+                categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
+            } catch (NumberFormatException e) {
+                Log.w(TAG, "Invalid color definition: " + accentPalette.getPackageName());
+            }
+        }
+
+        // Compatibility with legacy themes, where full packages were defined, instead of just
+        // colors.
+        if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)
+                && mSystemOverlay != null) {
+            categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, mSystemOverlay.getIdentifier());
+        }
+        if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_ACCENT_COLOR)
+                && mAccentOverlay != null) {
+            categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, mAccentOverlay.getIdentifier());
         }
 
         Set<UserHandle> userHandles = Sets.newHashSet(UserHandle.of(currentUser));
@@ -341,28 +334,31 @@
                 userHandles.add(userInfo.getUserHandle());
             }
         }
-        mThemeManager.applyCurrentUserOverlays(categoryToPackage, userHandles);
-    }
-
-    private String getColorString(int color) {
-        String colorString = Integer.toHexString(color).toUpperCase();
-        while (colorString.length() < 6) {
-            colorString = "0" + colorString;
+        if (DEBUG) {
+            Log.d(TAG, "Applying overlays: " + categoryToPackage.keySet().stream()
+                    .map(key -> key + " -> " + categoryToPackage.get(key)).collect(
+                            Collectors.joining(", ")));
         }
-        // Remove alpha component
-        if (colorString.length() > 6) {
-            colorString = colorString.substring(colorString.length() - 6);
+        if (mNeedsOverlayCreation) {
+            mNeedsOverlayCreation = false;
+            mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] {
+                    mSystemOverlay, mAccentOverlay
+            }, userHandles);
+        } else {
+            mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, userHandles);
         }
-        return colorString;
     }
 
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("USE_LOCK_SCREEN_WALLPAPER=" + USE_LOCK_SCREEN_WALLPAPER);
         pw.println("mLockColors=" + mLockColors);
         pw.println("mSystemColors=" + mSystemColors);
         pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor));
         pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor));
-        pw.println("mSystemOverlayColor=" + Integer.toHexString(mSystemOverlayColor));
-        pw.println("mAccentOverlayColor=" + Integer.toHexString(mAccentOverlayColor));
+        pw.println("mSystemOverlayColor=" + mSystemOverlay);
+        pw.println("mAccentOverlayColor=" + mAccentOverlay);
+        pw.println("mIsMonetEnabled=" + mIsMonetEnabled);
+        pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 09335af..b67574d 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -109,7 +109,7 @@
         dialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.cancel),
                 (OnClickListener) null);
         dialog.setButton(DialogInterface.BUTTON_POSITIVE,
-                context.getString(R.string.qs_customize_remove), new OnClickListener() {
+                context.getString(R.string.guest_exit_guest_dialog_remove), new OnClickListener() {
                     @Override
                     public void onClick(DialogInterface dialog, int which) {
                         // Tell the tuner (in main SysUI process) to clear all its settings.
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index 72f1f22..fd3641c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -20,12 +20,15 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.view.View;
 
+import com.android.systemui.R;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
 
 import java.util.HashSet;
 import java.util.List;
@@ -163,4 +166,15 @@
         }
         return apps;
     }
+
+    /**
+     * Returns true if the device should use the split notification shade, based on feature flags,
+     * orientation and screen width.
+     */
+    public static boolean shouldUseSplitNotificationShade(FeatureFlags featureFlags,
+            Resources resources) {
+        return featureFlags.isTwoColumnNotificationShadeEnabled()
+                && resources.getBoolean(R.bool.config_use_split_notification_shade);
+    }
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 6e7aed0..afeda967 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -45,6 +45,7 @@
 import android.service.notification.ZenModeConfig;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Pair;
 import android.view.View;
 
 import androidx.annotation.NonNull;
@@ -86,10 +87,12 @@
 import java.io.PrintWriter;
 import java.lang.reflect.Array;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Optional;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 import java.util.function.Supplier;
 
@@ -248,38 +251,19 @@
                 });
 
         mSysuiProxy = new Bubbles.SysuiProxy() {
-            private <T> T executeBlockingForResult(Supplier<T> runnable, Executor executor,
-                    Class clazz) {
-                if (Looper.myLooper() == Looper.getMainLooper()) {
-                    return runnable.get();
-                }
-                final T[] result = (T[]) Array.newInstance(clazz, 1);
-                final CountDownLatch latch = new CountDownLatch(1);
-                executor.execute(() -> {
-                    result[0] = runnable.get();
-                    latch.countDown();
-                });
-                try {
-                    latch.await();
-                    return result[0];
-                } catch (InterruptedException e) {
-                    return null;
-                }
-            }
-
             @Override
-            @Nullable
-            public BubbleEntry getPendingOrActiveEntry(String key) {
-                return executeBlockingForResult(() -> {
+            public void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback) {
+                sysuiMainExecutor.execute(() -> {
                     NotificationEntry entry =
                             mNotificationEntryManager.getPendingOrActiveNotif(key);
-                    return entry == null ? null : notifToBubbleEntry(entry);
-                }, sysuiMainExecutor, BubbleEntry.class);
+                    callback.accept(entry == null ? null : notifToBubbleEntry(entry));
+                });
             }
 
             @Override
-            public List<BubbleEntry> getShouldRestoredEntries(ArraySet<String> savedBubbleKeys) {
-                return executeBlockingForResult(() -> {
+            public void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys,
+                    Consumer<List<BubbleEntry>> callback) {
+                sysuiMainExecutor.execute(() -> {
                     List<BubbleEntry> result = new ArrayList<>();
                     List<NotificationEntry> activeEntries =
                             mNotificationEntryManager.getActiveNotificationsForCurrentUser();
@@ -291,27 +275,8 @@
                             result.add(notifToBubbleEntry(entry));
                         }
                     }
-                    return result;
-                }, sysuiMainExecutor, List.class);
-            }
-
-            @Override
-            public boolean isNotificationShadeExpand() {
-                return executeBlockingForResult(() -> {
-                    return mNotificationShadeWindowController.getPanelExpanded();
-                }, sysuiMainExecutor, Boolean.class);
-            }
-
-            @Override
-            public boolean shouldBubbleUp(String key) {
-                return executeBlockingForResult(() -> {
-                    final NotificationEntry entry =
-                            mNotificationEntryManager.getPendingOrActiveNotif(key);
-                    if (entry != null) {
-                        return mNotificationInterruptStateProvider.shouldBubbleUp(entry);
-                    }
-                    return false;
-                }, sysuiMainExecutor, Boolean.class);
+                    callback.accept(result);
+                });
             }
 
             @Override
@@ -587,7 +552,20 @@
     }
 
     void onRankingUpdate(RankingMap rankingMap) {
-        mBubbles.onRankingUpdated(rankingMap);
+        String[] orderedKeys = rankingMap.getOrderedKeys();
+        HashMap<String, Pair<BubbleEntry, Boolean>> pendingOrActiveNotif = new HashMap<>();
+        for (int i = 0; i < orderedKeys.length; i++) {
+            String key = orderedKeys[i];
+            NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(key);
+            BubbleEntry bubbleEntry = entry != null
+                    ? notifToBubbleEntry(entry)
+                    : null;
+            boolean shouldBubbleUp = entry != null
+                    ? mNotificationInterruptStateProvider.shouldBubbleUp(entry)
+                    : false;
+            pendingOrActiveNotif.put(key, new Pair<>(bubbleEntry, shouldBubbleUp));
+        }
+        mBubbles.onRankingUpdated(rankingMap, pendingOrActiveNotif);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
index 0795d89..ff28819 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.os.Handler;
 
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.WMSingleton;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
@@ -28,6 +29,7 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
@@ -49,7 +51,7 @@
 import dagger.Provides;
 
 /**
- * Dagger module for TV Pip.
+ * Provides TV specific dependencies for Pip.
  */
 @Module(includes = {WMShellBaseModule.class})
 public abstract class TvPipModule {
@@ -143,7 +145,8 @@
             PipAnimationController pipAnimationController,
             PipTransitionController pipTransitionController,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
-            Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
+            Optional<LegacySplitScreenController> splitScreenOptional,
+            DisplayController displayController,
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
index f23367b..141b9f7 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.view.IWindowManager;
 
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.WMSingleton;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
@@ -39,11 +40,20 @@
 import dagger.Provides;
 
 /**
- * Provides dependencies from {@link com.android.wm.shell} which could be customized among different
- * branches of SystemUI.
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
+ * accessible from components within the WM subcomponent (can be explicitly exposed to the
+ * SysUIComponent, see {@link WMComponent}).
+ *
+ * This module only defines Shell dependencies for the TV SystemUI implementation.  Common
+ * dependencies should go into {@link WMShellBaseModule}.
  */
 @Module(includes = {TvPipModule.class})
 public class TvWMShellModule {
+
+    //
+    // Internal common - Components used internally by multiple shell features
+    //
+
     @WMSingleton
     @Provides
     static DisplayImeController provideDisplayImeController(IWindowManager wmService,
@@ -53,16 +63,20 @@
                 transactionPool);
     }
 
+    //
+    // Split/multiwindow
+    //
+
     @WMSingleton
     @Provides
-    static LegacySplitScreen provideSplitScreen(Context context,
+    static LegacySplitScreenController provideSplitScreen(Context context,
             DisplayController displayController, SystemWindows systemWindows,
             DisplayImeController displayImeController, TransactionPool transactionPool,
             ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
             TaskStackListenerImpl taskStackListener, Transitions transitions,
             @ShellMainThread ShellExecutor mainExecutor,
             @ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) {
-        return LegacySplitScreenController.create(context, displayController, systemWindows,
+        return new LegacySplitScreenController(context, displayController, systemWindows,
                 displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
                 taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 81ac21c..ec61db5 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -43,6 +43,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.SystemUI;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.model.SysUiState;
@@ -73,7 +74,20 @@
 import javax.inject.Inject;
 
 /**
- * Proxy in SysUiScope to delegate events to controllers in WM Shell library.
+ * A SystemUI service that starts with the SystemUI application and sets up any bindings between
+ * Shell and SysUI components.  This service starts happens after the {@link WMComponent} has
+ * already been initialized and may only reference Shell components that are explicitly exported to
+ * SystemUI (see {@link WMComponent}.
+ *
+ * eg. SysUI application starts
+ *     -> SystemUIFactory is initialized
+ *       -> WMComponent is created
+ *         -> WMShellBaseModule dependencies are injected
+ *         -> WMShellModule (form-factory specific) dependencies are injected
+ *       -> SysUIComponent is created
+ *         -> WMComponents are explicitly provided to SysUIComponent for injection into SysUI code
+ *     -> SysUI services are started
+ *       -> WMShell starts and binds SysUI with Shell components via exported Shell interfaces
  */
 @SysUISingleton
 public final class WMShell extends SystemUI
@@ -142,6 +156,8 @@
 
     @Override
     public void start() {
+        // TODO: Consider piping config change and other common calls to a shell component to
+        //  delegate internally
         mProtoTracer.add(this);
         mCommandQueue.addCallback(this);
         mPipOptional.ifPresent(this::initPip);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index b42dde6..449db61 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -32,6 +32,7 @@
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.WMSingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.wm.shell.FullscreenTaskListener;
@@ -45,6 +46,7 @@
 import com.android.wm.shell.TaskViewFactoryController;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.apppairs.AppPairsController;
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.common.DisplayController;
@@ -63,6 +65,7 @@
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.Pip;
@@ -70,8 +73,8 @@
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip.PipUiEventLogger;
 import com.android.wm.shell.pip.phone.PipAppOpsListener;
+import com.android.wm.shell.pip.phone.PipController;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
 import com.android.wm.shell.sizecompatui.SizeCompatUIController;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -85,8 +88,13 @@
 import dagger.Provides;
 
 /**
- * Provides basic dependencies from {@link com.android.wm.shell}, the dependencies declared here
- * should be shared among different branches of SystemUI.
+ * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
+ * accessible from components within the WM subcomponent (can be explicitly exposed to the
+ * SysUIComponent, see {@link WMComponent}).
+ *
+ * This module only defines *common* dependencies across various SystemUI implementations,
+ * dependencies that are device/form factor SystemUI implementation specific should go into their
+ * respective modules (ie. {@link WMShellModule} for handheld, {@link TvWMShellModule} for tv, etc.)
  */
 @Module
 public abstract class WMShellBaseModule {
@@ -174,53 +182,9 @@
         }
     }
 
-    @WMSingleton
-    @Provides
-    static ShellInit provideShellInit(DisplayImeController displayImeController,
-            DragAndDropController dragAndDropController,
-            ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
-            Optional<SplitScreenController> splitScreenOptional,
-            Optional<AppPairs> appPairsOptional,
-            FullscreenTaskListener fullscreenTaskListener,
-            Transitions transitions,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return ShellInitImpl.create(displayImeController,
-                dragAndDropController,
-                shellTaskOrganizer,
-                legacySplitScreenOptional,
-                splitScreenOptional,
-                appPairsOptional,
-                fullscreenTaskListener,
-                transitions,
-                mainExecutor);
-    }
-
-    /**
-     * Note, this is only optional because we currently pass this to the SysUI component scope and
-     * for non-primary users, we may inject a null-optional for that dependency.
-     */
-    @WMSingleton
-    @Provides
-    static Optional<ShellCommandHandler> provideShellCommandHandler(
-            ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
-            Optional<SplitScreenController> splitScreenOptional,
-            Optional<Pip> pipOptional,
-            Optional<OneHanded> oneHandedOptional,
-            Optional<HideDisplayCutout> hideDisplayCutout,
-            Optional<AppPairs> appPairsOptional,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer,
-                legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
-                hideDisplayCutout, appPairsOptional, mainExecutor));
-    }
-
-    @WMSingleton
-    @Provides
-    static TransactionPool provideTransactionPool() {
-        return new TransactionPool();
-    }
+    //
+    // Internal common - Components used internally by multiple shell features
+    //
 
     @WMSingleton
     @Provides
@@ -238,8 +202,45 @@
 
     @WMSingleton
     @Provides
-    static FloatingContentCoordinator provideFloatingContentCoordinator() {
-        return new FloatingContentCoordinator();
+    static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
+            Context context, SizeCompatUIController sizeCompatUI) {
+        return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI);
+    }
+
+    @WMSingleton
+    @Provides
+    static SizeCompatUIController provideSizeCompatUIController(Context context,
+            DisplayController displayController, DisplayImeController imeController,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new SizeCompatUIController(context, displayController, imeController, mainExecutor);
+    }
+
+    @WMSingleton
+    @Provides
+    static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new SyncTransactionQueue(pool, mainExecutor);
+    }
+
+    @WMSingleton
+    @Provides
+    static SystemWindows provideSystemWindows(DisplayController displayController,
+            IWindowManager wmService) {
+        return new SystemWindows(displayController, wmService);
+    }
+
+    // We currently dedupe multiple messages, so we use the shell main handler directly
+    @WMSingleton
+    @Provides
+    static TaskStackListenerImpl providerTaskStackListenerImpl(
+            @ShellMainThread Handler mainHandler) {
+        return new TaskStackListenerImpl(mainHandler);
+    }
+
+    @WMSingleton
+    @Provides
+    static TransactionPool provideTransactionPool() {
+        return new TransactionPool();
     }
 
     @WMSingleton
@@ -249,10 +250,99 @@
         return new WindowManagerShellWrapper(mainExecutor);
     }
 
+    //
+    // Bubbles
+    //
+
+    @WMSingleton
+    @Provides
+    static Optional<Bubbles> provideBubbles(Optional<BubbleController> bubbleController) {
+        return bubbleController.map((controller) -> controller.asBubbles());
+    }
+
+    // Note: Handler needed for LauncherApps.register
+    @WMSingleton
+    @Provides
+    static Optional<BubbleController> provideBubbleController(Context context,
+            FloatingContentCoordinator floatingContentCoordinator,
+            IStatusBarService statusBarService,
+            WindowManager windowManager,
+            WindowManagerShellWrapper windowManagerShellWrapper,
+            LauncherApps launcherApps,
+            UiEventLogger uiEventLogger,
+            ShellTaskOrganizer organizer,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler) {
+        return Optional.of(BubbleController.create(context, null /* synchronizer */,
+                floatingContentCoordinator, statusBarService, windowManager,
+                windowManagerShellWrapper, launcherApps, uiEventLogger, organizer,
+                mainExecutor, mainHandler));
+    }
+
+    //
+    // Fullscreen
+    //
+
+    @WMSingleton
+    @Provides
+    static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) {
+        return new FullscreenTaskListener(syncQueue);
+    }
+
+    //
+    // Hide display cutout
+    //
+
+    @WMSingleton
+    @Provides
+    static Optional<HideDisplayCutout> provideHideDisplayCutout(
+            Optional<HideDisplayCutoutController> hideDisplayCutoutController) {
+        return hideDisplayCutoutController.map((controller) -> controller.asHideDisplayCutout());
+    }
+
+    @WMSingleton
+    @Provides
+    static Optional<HideDisplayCutoutController> provideHideDisplayCutoutController(Context context,
+            DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor) {
+        return Optional.ofNullable(
+                HideDisplayCutoutController.create(context, displayController, mainExecutor));
+    }
+
+    //
+    // One handed mode (optional feature)
+    //
+
+    @WMSingleton
+    @Provides
+    static Optional<OneHanded> provideOneHanded(Optional<OneHandedController> oneHandedController) {
+        return oneHandedController.map((controller) -> controller.asOneHanded());
+    }
+
+    // Needs the shell main handler for ContentObserver callbacks
+    @WMSingleton
+    @Provides
+    static Optional<OneHandedController> provideOneHandedController(Context context,
+            DisplayController displayController, TaskStackListenerImpl taskStackListener,
+            UiEventLogger uiEventLogger,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler) {
+        return Optional.ofNullable(OneHandedController.create(context, displayController,
+                taskStackListener, uiEventLogger, mainExecutor, mainHandler));
+    }
+
+    //
+    // Pip (optional feature)
+    //
+
+    @WMSingleton
+    @Provides
+    static FloatingContentCoordinator provideFloatingContentCoordinator() {
+        return new FloatingContentCoordinator();
+    }
+
     @WMSingleton
     @Provides
     static PipAppOpsListener providePipAppOpsListener(Context context,
-            IActivityManager activityManager,
             PipTouchHandler pipTouchHandler,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
@@ -268,37 +358,38 @@
 
     @WMSingleton
     @Provides
-    static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
-            PackageManager packageManager) {
-        return new PipUiEventLogger(uiEventLogger, packageManager);
-    }
-
-    @WMSingleton
-    @Provides
     static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
         return new PipSurfaceTransactionHelper(context);
     }
 
     @WMSingleton
     @Provides
-    static SystemWindows provideSystemWindows(DisplayController displayController,
-            IWindowManager wmService) {
-        return new SystemWindows(displayController, wmService);
+    static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
+            PackageManager packageManager) {
+        return new PipUiEventLogger(uiEventLogger, packageManager);
+    }
+
+    //
+    // Shell transitions
+    //
+
+    @WMSingleton
+    @Provides
+    static RemoteTransitions provideRemoteTransitions(Transitions transitions) {
+        return Transitions.asRemoteTransitions(transitions);
     }
 
     @WMSingleton
     @Provides
-    static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new SyncTransactionQueue(pool, mainExecutor);
+    static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellAnimationThread ShellExecutor animExecutor) {
+        return new Transitions(organizer, pool, mainExecutor, animExecutor);
     }
 
-    @WMSingleton
-    @Provides
-    static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
-            Context context, SizeCompatUI sizeCompatUI) {
-        return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI);
-    }
+    //
+    // Split/multiwindow
+    //
 
     @WMSingleton
     @Provides
@@ -307,17 +398,6 @@
         return new RootTaskDisplayAreaOrganizer(mainExecutor, context);
     }
 
-    // We currently dedupe multiple messages, so we use the shell main handler directly
-    @WMSingleton
-    @Provides
-    static TaskStackListenerImpl providerTaskStackListenerImpl(
-            @ShellMainThread Handler mainHandler) {
-        return new TaskStackListenerImpl(mainHandler);
-    }
-
-    @BindsOptionalOf
-    abstract LegacySplitScreen optionalLegacySplitScreen();
-
     @WMSingleton
     @Provides
     static Optional<SplitScreen> provideSplitScreen(
@@ -340,81 +420,91 @@
         }
     }
 
+    // Legacy split (optional feature)
+
+    @WMSingleton
+    @Provides
+    static Optional<LegacySplitScreen> provideLegacySplitScreen(
+            Optional<LegacySplitScreenController> splitScreenController) {
+        return splitScreenController.map((controller) -> controller.asLegacySplitScreen());
+    }
+
     @BindsOptionalOf
-    abstract AppPairs optionalAppPairs();
+    abstract LegacySplitScreenController optionalLegacySplitScreenController();
 
-    // Note: Handler needed for LauncherApps.register
+    // App Pairs (optional feature)
+
     @WMSingleton
     @Provides
-    static Optional<Bubbles> provideBubbles(Context context,
-            FloatingContentCoordinator floatingContentCoordinator,
-            IStatusBarService statusBarService,
-            WindowManager windowManager,
-            WindowManagerShellWrapper windowManagerShellWrapper,
-            LauncherApps launcherApps,
-            UiEventLogger uiEventLogger,
-            ShellTaskOrganizer organizer,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellMainThread Handler mainHandler) {
-        return Optional.of(BubbleController.create(context, null /* synchronizer */,
-                floatingContentCoordinator, statusBarService, windowManager,
-                windowManagerShellWrapper, launcherApps, uiEventLogger, organizer,
-                mainExecutor, mainHandler));
+    static Optional<AppPairs> provideAppPairs(Optional<AppPairsController> appPairsController) {
+        return appPairsController.map((controller) -> controller.asAppPairs());
     }
 
-    // Needs the shell main handler for ContentObserver callbacks
+    @BindsOptionalOf
+    abstract AppPairsController optionalAppPairs();
+
+    //
+    // Task view factory
+    //
+
     @WMSingleton
     @Provides
-    static Optional<OneHanded> provideOneHandedController(Context context,
-            DisplayController displayController, TaskStackListenerImpl taskStackListener,
-            UiEventLogger uiEventLogger,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellMainThread Handler mainHandler) {
-        return Optional.ofNullable(OneHandedController.create(context, displayController,
-                taskStackListener, uiEventLogger, mainExecutor, mainHandler));
+    static Optional<TaskViewFactory> provideTaskViewFactory(
+            TaskViewFactoryController taskViewFactoryController) {
+        return Optional.of(taskViewFactoryController.asTaskViewFactory());
     }
 
     @WMSingleton
     @Provides
-    static Optional<HideDisplayCutout> provideHideDisplayCutoutController(Context context,
-            DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor) {
-        return Optional.ofNullable(
-                HideDisplayCutoutController.create(context, displayController, mainExecutor));
-    }
-
-    @WMSingleton
-    @Provides
-    static Optional<TaskViewFactory> provideTaskViewFactory(ShellTaskOrganizer shellTaskOrganizer,
+    static TaskViewFactoryController provideTaskViewFactoryController(
+            ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return Optional.of(new TaskViewFactoryController(shellTaskOrganizer, mainExecutor)
-                .getTaskViewFactory());
+        return new TaskViewFactoryController(shellTaskOrganizer, mainExecutor);
     }
 
+    //
+    // Misc
+    //
+
     @WMSingleton
     @Provides
-    static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) {
-        return new FullscreenTaskListener(syncQueue);
-    }
-
-    @WMSingleton
-    @Provides
-    static RemoteTransitions provideRemoteTransitions(Transitions transitions) {
-        return Transitions.asRemoteTransitions(transitions);
-    }
-
-    @WMSingleton
-    @Provides
-    static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellAnimationThread ShellExecutor animExecutor) {
-        return new Transitions(organizer, pool, mainExecutor, animExecutor);
-    }
-
-    @WMSingleton
-    @Provides
-    static SizeCompatUI provideSizeCompatUI(Context context, DisplayController displayController,
-            DisplayImeController imeController, @ShellMainThread ShellExecutor mainExecutor) {
-        return SizeCompatUIController.create(context, displayController, imeController,
+    static ShellInit provideShellInit(DisplayImeController displayImeController,
+            DragAndDropController dragAndDropController,
+            ShellTaskOrganizer shellTaskOrganizer,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
+            Optional<SplitScreenController> splitScreenOptional,
+            Optional<AppPairsController> appPairsOptional,
+            FullscreenTaskListener fullscreenTaskListener,
+            Transitions transitions,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return ShellInitImpl.create(displayImeController,
+                dragAndDropController,
+                shellTaskOrganizer,
+                legacySplitScreenOptional,
+                splitScreenOptional,
+                appPairsOptional,
+                fullscreenTaskListener,
+                transitions,
                 mainExecutor);
     }
+
+    /**
+     * Note, this is only optional because we currently pass this to the SysUI component scope and
+     * for non-primary users, we may inject a null-optional for that dependency.
+     */
+    @WMSingleton
+    @Provides
+    static Optional<ShellCommandHandler> provideShellCommandHandler(
+            ShellTaskOrganizer shellTaskOrganizer,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
+            Optional<SplitScreenController> splitScreenOptional,
+            Optional<Pip> pipOptional,
+            Optional<OneHandedController> oneHandedOptional,
+            Optional<HideDisplayCutoutController> hideDisplayCutout,
+            Optional<AppPairsController> appPairsOptional,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer,
+                legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
+                hideDisplayCutout, appPairsOptional, mainExecutor));
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 2aaa095..997b488 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -21,10 +21,10 @@
 import android.os.Handler;
 import android.view.IWindowManager;
 
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.WMSingleton;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.apppairs.AppPairs;
 import com.android.wm.shell.apppairs.AppPairsController;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
@@ -36,7 +36,6 @@
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
 import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
@@ -60,11 +59,20 @@
 import dagger.Provides;
 
 /**
- * Provides dependencies from {@link com.android.wm.shell} which could be customized among different
- * branches of SystemUI.
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
+ * accessible from components within the WM subcomponent (can be explicitly exposed to the
+ * SysUIComponent, see {@link WMComponent}).
+ *
+ * This module only defines Shell dependencies for handheld SystemUI implementation.  Common
+ * dependencies should go into {@link WMShellBaseModule}.
  */
 @Module(includes = WMShellBaseModule.class)
 public class WMShellModule {
+
+    //
+    // Internal common - Components used internally by multiple shell features
+    //
+
     @WMSingleton
     @Provides
     static DisplayImeController provideDisplayImeController(IWindowManager wmService,
@@ -74,29 +82,37 @@
                 transactionPool);
     }
 
+    //
+    // Split/multiwindow
+    //
+
     @WMSingleton
     @Provides
-    static LegacySplitScreen provideLegacySplitScreen(Context context,
+    static LegacySplitScreenController provideLegacySplitScreen(Context context,
             DisplayController displayController, SystemWindows systemWindows,
             DisplayImeController displayImeController, TransactionPool transactionPool,
             ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
             TaskStackListenerImpl taskStackListener, Transitions transitions,
             @ShellMainThread ShellExecutor mainExecutor,
             @ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) {
-        return LegacySplitScreenController.create(context, displayController, systemWindows,
+        return new LegacySplitScreenController(context, displayController, systemWindows,
                 displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
                 taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler);
     }
 
     @WMSingleton
     @Provides
-    static AppPairs provideAppPairs(ShellTaskOrganizer shellTaskOrganizer,
+    static AppPairsController provideAppPairs(ShellTaskOrganizer shellTaskOrganizer,
             SyncTransactionQueue syncQueue, DisplayController displayController,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return AppPairsController.create(shellTaskOrganizer, syncQueue, displayController,
+        return new AppPairsController(shellTaskOrganizer, syncQueue, displayController,
                 mainExecutor);
     }
 
+    //
+    // Pip
+    //
+
     @WMSingleton
     @Provides
     static Optional<Pip> providePip(Context context, DisplayController displayController,
@@ -161,7 +177,8 @@
             PipAnimationController pipAnimationController,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
             PipTransitionController pipTransitionController,
-            Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
+            Optional<LegacySplitScreenController> splitScreenOptional,
+            DisplayController displayController,
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
new file mode 100644
index 0000000..9278570
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.ui
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.globalactions.GlobalActionsComponent
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.wm.shell.TaskViewFactory
+import dagger.Lazy
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlActionCoordinatorImplTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var uiController: ControlsUiController
+    @Mock
+    private lateinit var lazyUiController: Lazy<ControlsUiController>
+    @Mock
+    private lateinit var keyguardStateController: KeyguardStateController
+    @Mock
+    private lateinit var bgExecutor: DelayableExecutor
+    @Mock
+    private lateinit var uiExecutor: DelayableExecutor
+    @Mock
+    private lateinit var activityStarter: ActivityStarter
+    @Mock
+    private lateinit var globalActionsComponent: GlobalActionsComponent
+    @Mock
+    private lateinit var taskViewFactory: Optional<TaskViewFactory>
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private lateinit var cvh: ControlViewHolder
+
+    companion object {
+        fun <T> any(): T = Mockito.any<T>()
+
+        private val ID = "id"
+    }
+
+    private lateinit var coordinator: ControlActionCoordinatorImpl
+    private lateinit var action: ControlActionCoordinatorImpl.Action
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        coordinator = spy(ControlActionCoordinatorImpl(
+            mContext,
+            bgExecutor,
+            uiExecutor,
+            activityStarter,
+            keyguardStateController,
+            globalActionsComponent,
+            taskViewFactory,
+            getFakeBroadcastDispatcher(),
+            lazyUiController
+        ))
+
+        `when`(cvh.cws.ci.controlId).thenReturn(ID)
+        action = spy(coordinator.Action(ID, {}, false))
+        doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean())
+    }
+
+    @Test
+    fun testToggleRunsWhenUnlocked() {
+        `when`(keyguardStateController.isShowing()).thenReturn(false)
+
+        coordinator.toggle(cvh, "", true)
+        verify(coordinator).bouncerOrRun(action)
+        verify(action).invoke()
+    }
+
+    @Test
+    fun testToggleDoesNotRunWhenLockedThenRunsWhenUnlocked() {
+        `when`(keyguardStateController.isShowing()).thenReturn(true)
+        `when`(keyguardStateController.isUnlocked()).thenReturn(false)
+
+        coordinator.toggle(cvh, "", true)
+        verify(coordinator).bouncerOrRun(action)
+        verify(activityStarter).dismissKeyguardThenExecute(any(), any(), anyBoolean())
+        verify(action, never()).invoke()
+
+        // Simulate a refresh call from a Publisher, which will trigger a call to runPendingAction
+        reset(action)
+        coordinator.runPendingAction(ID)
+        verify(action, never()).invoke()
+
+        `when`(keyguardStateController.isUnlocked()).thenReturn(true)
+        reset(action)
+        coordinator.runPendingAction(ID)
+        verify(action).invoke()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java
index a52a598..0457100 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java
@@ -19,7 +19,7 @@
 import android.app.Activity;
 import android.os.Bundle;
 
-import com.android.systemui.R;
+import com.android.systemui.tests.R;
 
 /**
  * Test activity for resolving {@link EmergencyGesture#ACTION_LAUNCH_EMERGENCY} action.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 0dc268a..fb817ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -28,6 +30,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 
@@ -37,6 +41,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 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.MediaHost;
@@ -44,6 +49,7 @@
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.util.animation.DisappearParameters;
 
 import org.junit.Before;
@@ -86,17 +92,25 @@
     QSTileView mQSTileView;
     @Mock
     PagedTileLayout mPagedTileLayout;
+    @Mock
+    FeatureFlags mFeatureFlags;
+    @Mock
+    Resources mResources;
+    @Mock
+    Configuration mConfiguration;
 
     private QSPanelControllerBase<QSPanel> mController;
 
+
+
     /** Implementation needed to ensure we have a reflectively-available class name. */
     private class TestableQSPanelControllerBase extends QSPanelControllerBase<QSPanel> {
         protected TestableQSPanelControllerBase(QSPanel view, QSTileHost host,
                 QSCustomizerController qsCustomizerController, MediaHost mediaHost,
                 MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
-                DumpManager dumpManager) {
+                DumpManager dumpManager, FeatureFlags featureFlags) {
             super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger,
-                    qsLogger, dumpManager);
+                    qsLogger, dumpManager, featureFlags);
         }
 
         @Override
@@ -121,10 +135,12 @@
         when(mQSTileRevealControllerFactory.create(any(), any()))
                 .thenReturn(mQSTileRevealController);
         when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters());
+        when(mQSPanel.getResources()).thenReturn(mResources);
+        when(mResources.getConfiguration()).thenReturn(mConfiguration);
 
         mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
                 mQSCustomizerController, mMediaHost,
-                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
+                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags);
 
         mController.init();
         reset(mQSTileRevealController);
@@ -136,7 +152,7 @@
 
         QSPanelControllerBase<QSPanel> controller = new TestableQSPanelControllerBase(mQSPanel,
                 mQSTileHost, mQSCustomizerController, mMediaHost,
-                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager) {
+                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags) {
             @Override
             protected QSTileRevealController createTileRevealController() {
                 return mQSTileRevealController;
@@ -218,4 +234,18 @@
         verify(mQSLogger).logAllTilesChangeListening(false, "QSPanel", "dnd");
         verify(mPagedTileLayout).setListening(false, mUiEventLogger);
     }
+
+
+    @Test
+    public void testShouldUzeHorizontalLayout_falseForSplitShade() {
+        mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+        when(mMediaHost.getVisible()).thenReturn(true);
+
+        when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false);
+        assertThat(mController.shouldUseHorizontalLayout()).isTrue();
+
+        when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
+        assertThat(mController.shouldUseHorizontalLayout()).isFalse();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
index 4381158..0dfebab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
@@ -41,6 +41,7 @@
 import com.android.systemui.settings.brightness.BrightnessController;
 import com.android.systemui.settings.brightness.BrightnessSlider;
 import com.android.systemui.settings.brightness.ToggleSlider;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.animation.DisappearParameters;
 
@@ -93,6 +94,8 @@
     QSTileView mQSTileView;
     @Mock
     PagedTileLayout mPagedTileLayout;
+    @Mock
+    FeatureFlags mFeatureFlags;
 
     private QSPanelController mController;
 
@@ -118,7 +121,9 @@
                 mQSTileHost, mQSCustomizerController, true, mMediaHost,
                 mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger,
                 mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory,
-                /* labelsFlag */ false);
+                /* labelsFlag */ false,
+                mFeatureFlags
+        );
 
         mController.init();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 107160f..5870200 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.plugins.qs.QSTileView
 import com.android.systemui.qs.customize.QSCustomizerController
 import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.statusbar.FeatureFlags
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -63,6 +64,8 @@
     private lateinit var tileLayout: TileLayout
     @Mock
     private lateinit var tileView: QSTileView
+    @Mock
+    private lateinit var featureFlags: FeatureFlags
 
     private lateinit var controller: QuickQSPanelController
 
@@ -84,7 +87,8 @@
                 uiEventLogger,
                 qsLogger,
                 dumpManager,
-                false
+                false,
+                featureFlags
         )
 
         controller.init()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
index b452d3a..1ec1da4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
@@ -98,7 +98,7 @@
 
         mQSCarrierGroupController = new QSCarrierGroupController.Builder(
                 mActivityStarter, handler, TestableLooper.get(this).getLooper(),
-                mNetworkController, mCarrierTextControllerBuilder)
+                mNetworkController, mCarrierTextControllerBuilder, mContext)
                 .setQSCarrierGroup(mQSCarrierGroup)
                 .build();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
new file mode 100644
index 0000000..b7b9678
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.os.Handler
+import android.provider.Settings
+import android.service.quicksettings.Tile
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.lifecycle.LifecycleOwner
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.ControlsDialog
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.GlobalSettings
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class DeviceControlsTileTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var qsHost: QSHost
+    @Mock
+    private lateinit var metricsLogger: MetricsLogger
+    @Mock
+    private lateinit var statusBarStateController: StatusBarStateController
+    @Mock
+    private lateinit var activityStarter: ActivityStarter
+    @Mock
+    private lateinit var qsLogger: QSLogger
+    private lateinit var controlsComponent: ControlsComponent
+    @Mock
+    private lateinit var controlsUiController: ControlsUiController
+    @Mock
+    private lateinit var controlsListingController: ControlsListingController
+    @Mock
+    private lateinit var controlsController: ControlsController
+    @Mock
+    private lateinit var featureFlags: FeatureFlags
+    @Mock
+    private lateinit var controlsDialog: ControlsDialog
+    private lateinit var globalSettings: GlobalSettings
+    @Mock
+    private lateinit var serviceInfo: ControlsServiceInfo
+    @Mock
+    private lateinit var uiEventLogger: UiEventLogger
+    @Captor
+    private lateinit var listingCallbackCaptor:
+            ArgumentCaptor<ControlsListingController.ControlsListingCallback>
+
+    private lateinit var testableLooper: TestableLooper
+    private lateinit var tile: DeviceControlsTile
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testableLooper = TestableLooper.get(this)
+
+        `when`(qsHost.context).thenReturn(mContext)
+        `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
+
+        controlsComponent = ControlsComponent(
+                true,
+                { controlsController },
+                { controlsUiController },
+                { controlsListingController }
+        )
+
+        globalSettings = FakeSettings()
+
+        globalSettings.putInt(DeviceControlsTile.SETTINGS_FLAG, 1)
+        `when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(true)
+
+        tile = createTile()
+    }
+
+    @Test
+    fun testAvailable() {
+        assertThat(tile.isAvailable).isTrue()
+    }
+
+    @Test
+    fun testNotAvailableFeature() {
+        `when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(false)
+
+        assertThat(tile.isAvailable).isFalse()
+    }
+
+    @Test
+    fun testNotAvailableControls() {
+        controlsComponent = ControlsComponent(
+                false,
+                { controlsController },
+                { controlsUiController },
+                { controlsListingController }
+        )
+        tile = createTile()
+
+        assertThat(tile.isAvailable).isFalse()
+    }
+
+    @Test
+    fun testNotAvailableFlag() {
+        globalSettings.putInt(DeviceControlsTile.SETTINGS_FLAG, 0)
+        tile = createTile()
+
+        assertThat(tile.isAvailable).isFalse()
+    }
+
+    @Test
+    fun testObservingCallback() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                any(ControlsListingController.ControlsListingCallback::class.java)
+        )
+    }
+
+    @Test
+    fun testLongClickIntent() {
+        assertThat(tile.longClickIntent.action).isEqualTo(Settings.ACTION_DEVICE_CONTROLS_SETTINGS)
+    }
+
+    @Test
+    fun testUnavailableByDefault() {
+        assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+    }
+
+    @Test
+    fun testStateUnavailableIfNoListings() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                capture(listingCallbackCaptor)
+        )
+
+        listingCallbackCaptor.value.onServicesUpdated(emptyList())
+        testableLooper.processAllMessages()
+
+        assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+    }
+
+    @Test
+    fun testStateAvailableIfListings() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                capture(listingCallbackCaptor)
+        )
+
+        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+        testableLooper.processAllMessages()
+
+        assertThat(tile.state.state).isEqualTo(Tile.STATE_ACTIVE)
+    }
+
+    @Test
+    fun testMoveBetweenStates() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                capture(listingCallbackCaptor)
+        )
+
+        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+        testableLooper.processAllMessages()
+
+        listingCallbackCaptor.value.onServicesUpdated(emptyList())
+        testableLooper.processAllMessages()
+
+        assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+    }
+
+    @Test
+    fun testNoDialogWhenUnavailable() {
+        tile.click()
+        testableLooper.processAllMessages()
+
+        verify(controlsDialog, never()).show(any(ControlsUiController::class.java))
+    }
+
+    @Test
+    fun testDialogShowWhenAvailable() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                capture(listingCallbackCaptor)
+        )
+
+        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+        testableLooper.processAllMessages()
+
+        tile.click()
+        testableLooper.processAllMessages()
+
+        verify(controlsDialog).show(controlsUiController)
+    }
+
+    @Test
+    fun testDialogDismissedOnDestroy() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                capture(listingCallbackCaptor)
+        )
+
+        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+        testableLooper.processAllMessages()
+
+        tile.click()
+        testableLooper.processAllMessages()
+
+        tile.destroy()
+        testableLooper.processAllMessages()
+        verify(controlsDialog).dismiss()
+    }
+
+    private fun createTile(): DeviceControlsTile {
+        return DeviceControlsTile(
+                qsHost,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                metricsLogger,
+                statusBarStateController,
+                activityStarter,
+                qsLogger,
+                controlsComponent,
+                featureFlags,
+                { controlsDialog },
+                globalSettings
+        )
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
index c1c6371..580f800 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
@@ -57,6 +57,8 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class ScrollCaptureClientTest extends SysuiTestCase {
+    private static final float MAX_PAGES = 3f;
+
     private Context mContext;
     private IWindowManager mWm;
 
@@ -96,7 +98,7 @@
 
         Connection conn = mConnectionConsumer.getValue();
 
-        conn.start(mSessionConsumer);
+        conn.start(mSessionConsumer, MAX_PAGES);
         verify(mSessionConsumer, timeout(100)).accept(any(Session.class));
 
         Session session = mSessionConsumer.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java
index bd37259..4c84df2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java
@@ -34,7 +34,7 @@
         linearLayout.setOrientation(LinearLayout.VERTICAL);
         TextView text = new TextView(this);
         text.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 40);
-        text.setText(com.android.systemui.R.string.test_content);
+        text.setText(com.android.systemui.tests.R.string.test_content);
         linearLayout.addView(text);
         scrollView.addView(linearLayout);
         setContentView(scrollView);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
index d131dce..e16d4d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
@@ -36,16 +36,20 @@
 
 import android.app.Notification;
 import android.app.NotificationChannel;
+import android.os.Handler;
 import android.os.UserHandle;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.util.Pair;
 
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.util.DeviceConfigProxyFake;
 
 import junit.framework.Assert;
 
@@ -55,38 +59,44 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class AssistantFeedbackControllerTest extends SysuiTestCase {
-    private static final int ON = 1;
-    private static final int OFF = 0;
     private static final String TEST_PACKAGE_NAME = "test_package";
     private static final int TEST_UID = 1;
 
     private AssistantFeedbackController mAssistantFeedbackController;
+    private DeviceConfigProxyFake mProxyFake;
+    private TestableLooper mTestableLooper;
+
     private StatusBarNotification mSbn;
 
     @Before
     public void setUp() {
-        mAssistantFeedbackController = new AssistantFeedbackController(mContext);
-        switchSetting(ON);
+        mProxyFake = new DeviceConfigProxyFake();
+        mTestableLooper = TestableLooper.get(this);
+        mAssistantFeedbackController = new AssistantFeedbackController(
+                new Handler(mTestableLooper.getLooper()),
+                mContext, mProxyFake);
         mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME,
                 0, null, TEST_UID, 0, new Notification(),
                 UserHandle.CURRENT, null, 0);
     }
 
     @Test
-    public void testUserControls_settingDisabled() {
-        switchSetting(OFF);
+    public void testFlagDisabled() {
+        switchFlag("false");
         assertFalse(mAssistantFeedbackController.isFeedbackEnabled());
     }
 
     @Test
-    public void testUserControls_settingEnabled() {
+    public void testFlagEnabled() {
+        switchFlag("true");
         assertTrue(mAssistantFeedbackController.isFeedbackEnabled());
     }
 
     @Test
-    public void testFeedback_settingDisabled() {
-        switchSetting(OFF);
+    public void testFeedback_flagDisabled() {
+        switchFlag("false");
         assertEquals(STATUS_UNCHANGED, mAssistantFeedbackController.getFeedbackStatus(
                 getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_UNCHANGED)));
         assertFalse(mAssistantFeedbackController.showFeedbackIndicator(
@@ -95,6 +105,7 @@
 
     @Test
     public void testFeedback_changedImportance() {
+        switchFlag("true");
         NotificationEntry entry = getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_HIGH, RANKING_UNCHANGED);
         assertEquals(STATUS_PROMOTED, mAssistantFeedbackController.getFeedbackStatus(entry));
         assertTrue(mAssistantFeedbackController.showFeedbackIndicator(entry));
@@ -110,6 +121,7 @@
 
     @Test
     public void testFeedback_changedRanking() {
+        switchFlag("true");
         NotificationEntry entry =
                 getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_PROMOTED);
         assertEquals(STATUS_PROMOTED, mAssistantFeedbackController.getFeedbackStatus(entry));
@@ -121,8 +133,8 @@
     }
 
     @Test
-    public void testGetFeedbackResources_settingDisabled() {
-        switchSetting(OFF);
+    public void testGetFeedbackResources_flagDisabled() {
+        switchFlag("false");
         Assert.assertEquals(new Pair(0, 0), mAssistantFeedbackController.getFeedbackResources(
                 getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_UNCHANGED)));
     }
@@ -138,9 +150,10 @@
                 .build();
     }
 
-    private void switchSetting(int setting) {
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.NOTIFICATION_FEEDBACK_ENABLED, setting);
-        mAssistantFeedbackController.update(null);
+    private void switchFlag(String enabled) {
+        mProxyFake.setProperty(
+                DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.ENABLE_NAS_FEEDBACK,
+                enabled, false);
+        mTestableLooper.processAllMessages();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
index 7eeae67..e6287e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
@@ -38,8 +38,8 @@
 import androidx.palette.graphics.Palette;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.tests.R;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 6b0a23f..2e2945e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -50,7 +50,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.filters.Suppress;
 
-import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.media.MediaFeatureFlag;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -61,6 +60,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.policy.InflatedSmartReplies;
 import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflater;
+import com.android.systemui.tests.R;
 
 import org.junit.Assert;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 6fcc7fa..64a7bee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -43,7 +43,6 @@
 import android.view.LayoutInflater;
 import android.widget.RemoteViews;
 
-import com.android.systemui.R;
 import com.android.systemui.TestableDependency;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.media.MediaFeatureFlag;
@@ -68,6 +67,7 @@
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.InflatedSmartReplies;
+import com.android.systemui.tests.R;
 import com.android.systemui.wmshell.BubblesManager;
 import com.android.systemui.wmshell.BubblesTestActivity;
 import com.android.wm.shell.bubbles.Bubbles;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
index a147c8d..45f7c5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
@@ -24,10 +24,10 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.tests.R;
 
 import org.junit.Assert;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index b9fd75e..fa253e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -35,7 +35,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.doze.DozeScreenState;
-import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.tuner.TunerService;
 
@@ -58,7 +57,6 @@
     @Mock private PowerManager mPowerManager;
     @Mock private TunerService mTunerService;
     @Mock private BatteryController mBatteryController;
-    @Mock private FeatureFlags mFeatureFlags;
 
     @Before
     public void setup() {
@@ -69,8 +67,7 @@
             mAlwaysOnDisplayPolicy,
             mPowerManager,
             mBatteryController,
-            mTunerService,
-            mFeatureFlags
+            mTunerService
         );
     }
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index ee1d758..4162884 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -393,10 +393,11 @@
 
     private void positionClock() {
         mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT, mNotificationStackHeight,
-                mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight, mPreferredClockY,
-                mHasCustomClock, mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
-                0 /* unlockedStackScrollerPadding */, false /* udfpsEnrolled */,
-                mQsExpansion, mCutoutTopInset);
+                mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight,
+                0 /* keyguardUserSwitcherHeight */, mPreferredClockY, mHasCustomClock,
+                mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
+                0 /* unlockedStackScrollerPadding */, false /* udfpsEnrolled */, mQsExpansion,
+                mCutoutTopInset);
         mClockPositionAlgorithm.run(mClockPosition);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index c07ba72..d0e7031 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -37,6 +37,7 @@
 import android.content.res.Resources;
 import android.hardware.biometrics.BiometricSourceType;
 import android.os.PowerManager;
+import android.os.UserManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.DisplayMetrics;
@@ -58,6 +59,7 @@
 import com.android.keyguard.KeyguardStatusViewController;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
+import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
@@ -68,7 +70,6 @@
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.media.MediaHierarchyManager;
-import com.android.systemui.qs.QSDetailDisplayer;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
@@ -193,6 +194,8 @@
     @Mock
     private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
     @Mock
+    private KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponent;
+    @Mock
     private KeyguardStatusViewComponent mKeyguardStatusViewComponent;
     @Mock
     private KeyguardClockSwitchController mKeyguardClockSwitchController;
@@ -216,6 +219,9 @@
     private NotificationsQuickSettingsContainer mNotificationContainerParent;
     @Mock
     private AmbientState mAmbientState;
+    @Mock
+    private UserManager mUserManager;
+
     private NotificationPanelViewController mNotificationPanelViewController;
     private View.AccessibilityDelegate mAccessibiltyDelegate;
 
@@ -299,11 +305,12 @@
                 mBiometricUnlockController, mStatusBarKeyguardViewManager,
                 mNotificationStackScrollLayoutController,
                 mKeyguardStatusViewComponentFactory,
+                mKeyguardUserSwitcherComponent,
                 mGroupManager,
                 mNotificationAreaController,
                 mAuthController,
-                new QSDetailDisplayer(),
                 mScrimController,
+                mUserManager,
                 mMediaDataManager,
                 mAmbientState,
                 mFeatureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 253460d..cae488a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -97,7 +97,6 @@
 import com.android.systemui.settings.brightness.BrightnessSlider;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -259,7 +258,6 @@
     @Mock private DemoModeController mDemoModeController;
     @Mock private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
     @Mock private BrightnessSlider.Factory mBrightnessSliderFactory;
-    @Mock private FeatureFlags mFeatureFlags;
     private ShadeController mShadeController;
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
     private InitController mInitController = new InitController();
@@ -420,8 +418,7 @@
                 mNotificationShadeDepthControllerLazy,
                 mStatusBarTouchableRegionManager,
                 mNotificationIconAreaController,
-                mBrightnessSliderFactory,
-                mFeatureFlags);
+                mBrightnessSliderFactory);
 
         when(mNotificationShadeWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
                 mLockIconContainer);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
index c212cf3..67c1a08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
@@ -26,12 +26,12 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.settingslib.R;
 import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.tests.R;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
index fc1a791..e479882 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
@@ -60,9 +60,9 @@
     @Mock
     private lateinit var layoutInflater: LayoutInflater
     @Mock
-    private lateinit var keyguardUserSwitcher: KeyguardUserSwitcher
+    private lateinit var keyguardUserSwitcherController: KeyguardUserSwitcherController
 
-    private lateinit var adapter: KeyguardUserSwitcher.KeyguardUserAdapter
+    private lateinit var adapter: KeyguardUserSwitcherController.KeyguardUserAdapter
     private lateinit var picture: Bitmap
 
     @Before
@@ -72,8 +72,11 @@
         mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, layoutInflater)
         `when`(layoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
                 .thenReturn(inflatedUserDetailItemView)
-        adapter = KeyguardUserSwitcher.KeyguardUserAdapter(mContext, userSwitcherController,
-                keyguardUserSwitcher)
+        adapter = KeyguardUserSwitcherController.KeyguardUserAdapter(
+                mContext,
+                mContext.resources,
+                LayoutInflater.from(mContext),
+                userSwitcherController, keyguardUserSwitcherController)
         picture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user))
     }
 
@@ -118,11 +121,11 @@
     }
 
     @Test
-    fun shouldRemoveOnClickListener_currentUser_notGuestUser_oldViewIsSameType() {
+    fun shouldSetOnOnClickListener_currentUser_notGuestUser_oldViewIsSameType() {
         val v: UserDetailItemView? = createViewFromSameType(
                 isCurrentUser = true, isGuestUser = false)
         assertNotNull(v)
-        verify(v)!!.setOnClickListener(null)
+        verify(v)!!.setOnClickListener(adapter)
     }
 
     @Test
@@ -150,11 +153,11 @@
     }
 
     @Test
-    fun shouldRemoveOnClickListener_currentUser_notGuestUser_oldViewIsDifferentType() {
+    fun shouldSetOnOnClickListener_currentUser_notGuestUser_oldViewIsDifferentType() {
         val v: UserDetailItemView? = createViewFromDifferentType(
                 isCurrentUser = true, isGuestUser = false)
         assertNotNull(v)
-        verify(v)!!.setOnClickListener(null)
+        verify(v)!!.setOnClickListener(adapter)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index f8b6383..89cc2b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -430,6 +430,10 @@
         updateSignalStrength();
     }
 
+    public void setImsType(int imsType) {
+        mMobileSignalController.setImsType(imsType);
+    }
+
     public void setIsGsm(boolean gsm) {
         when(mSignalStrength.isGsm()).thenReturn(gsm);
         updateSignalStrength();
@@ -632,6 +636,14 @@
         }
     }
 
+    protected void verifyLastCallStrength(int icon) {
+        ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
+        verify(mCallbackHandler, Mockito.atLeastOnce()).setCallIndicator(
+                iconArg.capture(),
+                anyInt());
+        assertEquals("Call strength, in status bar", icon, (int) iconArg.getValue().icon);
+    }
+
     protected void assertNetworkNameEquals(String expected) {
        assertEquals("Network name", expected, mMobileSignalController.getState().networkName);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 10166cb..fc1a08ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -15,6 +15,7 @@
 import android.net.vcn.VcnTransportInfo;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
+import android.telephony.CellSignalStrength;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
@@ -243,6 +244,28 @@
         }
     }
 
+    @Test
+    public void testCallStrengh() {
+        String testSsid = "Test SSID";
+        setWifiEnabled(true);
+        setWifiState(true, testSsid);
+        // Set the ImsType to be IMS_TYPE_WLAN
+        setImsType(2);
+        setWifiLevel(1);
+        for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
+            setWifiLevel(testLevel);
+            verifyLastCallStrength(TelephonyIcons.WIFI_CALL_STRENGTH_ICONS[testLevel]);
+        }
+        // Set the ImsType to be IMS_TYPE_WWAN
+        setImsType(1);
+        for (int testStrength = 0;
+                testStrength < CellSignalStrength.getNumSignalStrengthLevels(); testStrength++) {
+            setupDefaultSignal();
+            setLevel(testStrength);
+            verifyLastCallStrength(TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[testStrength]);
+        }
+    }
+
     protected void setWifiActivity(int activity) {
         // TODO: Not this, because this variable probably isn't sticking around.
         mNetworkController.mWifiSignalController.setActivity(activity);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
index 6c99efc..45828c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
@@ -34,10 +34,12 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.om.FabricatedOverlay;
 import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayManager;
@@ -73,11 +75,12 @@
     private static final String TEST_DISABLED_PREFIX = "com.example.";
     private static final String TEST_ENABLED_PREFIX = "com.example.enabled.";
 
-    private static final Map<String, String> ALL_CATEGORIES_MAP = Maps.newArrayMap();
+    private static final Map<String, OverlayIdentifier> ALL_CATEGORIES_MAP = Maps.newArrayMap();
 
     static {
         for (String category : THEME_CATEGORIES) {
-            ALL_CATEGORIES_MAP.put(category, TEST_DISABLED_PREFIX + category);
+            ALL_CATEGORIES_MAP.put(category,
+                    new OverlayIdentifier(TEST_DISABLED_PREFIX + category));
         }
     }
 
@@ -157,27 +160,26 @@
 
     @Test
     public void allCategoriesSpecified_allEnabledExclusively() {
-        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES);
         verify(mOverlayManager).commit(any());
 
-        for (String overlayPackage : ALL_CATEGORIES_MAP.values()) {
-            verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(overlayPackage)),
-                    eq(true), eq(TEST_USER.getIdentifier()));
+        for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
+            verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+                    eq(TEST_USER.getIdentifier()));
         }
     }
 
     @Test
     public void allCategoriesSpecified_sysuiCategoriesAlsoAppliedToSysuiUser() {
-        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES);
 
-        for (Map.Entry<String, String> entry : ALL_CATEGORIES_MAP.entrySet()) {
+        for (Map.Entry<String, OverlayIdentifier> entry : ALL_CATEGORIES_MAP.entrySet()) {
             if (SYSTEM_USER_CATEGORIES.contains(entry.getKey())) {
-                verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(entry.getValue())),
-                        eq(true), eq(UserHandle.SYSTEM.getIdentifier()));
+                verify(mTransactionBuilder).setEnabled(eq(entry.getValue()), eq(true),
+                        eq(UserHandle.SYSTEM.getIdentifier()));
             } else {
                 verify(mTransactionBuilder, never()).setEnabled(
-                        eq(new OverlayIdentifier(entry.getValue())),
-                        eq(true), eq(UserHandle.SYSTEM.getIdentifier()));
+                        eq(entry.getValue()), eq(true), eq(UserHandle.SYSTEM.getIdentifier()));
             }
         }
     }
@@ -187,19 +189,34 @@
         Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES);
         UserHandle newUserHandle = UserHandle.of(10);
         userHandles.add(newUserHandle);
-        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, userHandles);
+        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, userHandles);
 
-        for (String overlayPackage : ALL_CATEGORIES_MAP.values()) {
-            verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(overlayPackage)),
-                    eq(true), eq(TEST_USER.getIdentifier()));
-            verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(overlayPackage)),
-                    eq(true), eq(newUserHandle.getIdentifier()));
+        for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
+            verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+                    eq(TEST_USER.getIdentifier()));
+            verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+                    eq(newUserHandle.getIdentifier()));
+        }
+    }
+
+    @Test
+    public void applyCurrentUserOverlays_createsPendingOverlays() {
+        Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES);
+        UserHandle newUserHandle = UserHandle.of(10);
+        userHandles.add(newUserHandle);
+        FabricatedOverlay[] pendingCreation = new FabricatedOverlay[] {
+                mock(FabricatedOverlay.class)
+        };
+        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, pendingCreation, userHandles);
+
+        for (FabricatedOverlay overlay : pendingCreation) {
+            verify(mTransactionBuilder).registerFabricatedOverlay(eq(overlay));
         }
     }
 
     @Test
     public void allCategoriesSpecified_overlayManagerNotQueried() {
-        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES);
 
         verify(mOverlayManager, never())
                 .getOverlayInfosForTarget(anyString(), any(UserHandle.class));
@@ -207,15 +224,15 @@
 
     @Test
     public void someCategoriesSpecified_specifiedEnabled_unspecifiedDisabled() {
-        Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
+        Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
         categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS);
         categoryToPackage.remove(OVERLAY_CATEGORY_ICON_ANDROID);
 
-        mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES);
 
-        for (String overlayPackage : categoryToPackage.values()) {
-            verify(mTransactionBuilder).setEnabled(eq(new OverlayIdentifier(overlayPackage)),
-                    eq(true), eq(TEST_USER.getIdentifier()));
+        for (OverlayIdentifier overlayPackage : categoryToPackage.values()) {
+            verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+                    eq(TEST_USER.getIdentifier()));
         }
         verify(mTransactionBuilder).setEnabled(
                 eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS)),
@@ -227,7 +244,7 @@
 
     @Test
     public void zeroCategoriesSpecified_allDisabled() {
-        mManager.applyCurrentUserOverlays(Maps.newArrayMap(), TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(Maps.newArrayMap(), null, TEST_USER_HANDLES);
 
         for (String category : THEME_CATEGORIES) {
             verify(mTransactionBuilder).setEnabled(
@@ -238,10 +255,10 @@
 
     @Test
     public void nonThemeCategorySpecified_ignored() {
-        Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
-        categoryToPackage.put("blah.category", "com.example.blah.category");
+        Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
+        categoryToPackage.put("blah.category", new OverlayIdentifier("com.example.blah.category"));
 
-        mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES);
 
         verify(mTransactionBuilder, never()).setEnabled(
                 eq(new OverlayIdentifier("com.example.blah.category")), eq(false),
@@ -253,10 +270,10 @@
 
     @Test
     public void overlayManagerOnlyQueriedForUnspecifiedPackages() {
-        Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
+        Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
         categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS);
 
-        mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES);
 
         verify(mOverlayManager).getOverlayInfosForTarget(SETTINGS_PACKAGE, UserHandle.SYSTEM);
         verify(mOverlayManager, never()).getOverlayInfosForTarget(ANDROID_PACKAGE,
@@ -270,7 +287,8 @@
 
     private static OverlayInfo createOverlayInfo(String packageName, String targetPackageName,
             String category, boolean enabled) {
-        return new OverlayInfo(packageName, targetPackageName, null, category, "",
-                enabled ? OverlayInfo.STATE_ENABLED : OverlayInfo.STATE_DISABLED, 0, 0, false);
+        return new OverlayInfo(packageName, null, targetPackageName, null, category, "",
+                enabled ? OverlayInfo.STATE_ENABLED : OverlayInfo.STATE_DISABLED, 0, 0, false,
+                false);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index d33fac0..f7f8d03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.theme;
 
-import static com.android.systemui.theme.ThemeOverlayApplier.MONET_ACCENT_COLOR_PACKAGE;
-import static com.android.systemui.theme.ThemeOverlayApplier.MONET_SYSTEM_PALETTE_PACKAGE;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
 import static com.android.systemui.theme.ThemeOverlayController.USE_LOCK_SCREEN_WALLPAPER;
@@ -27,12 +25,15 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
 import android.graphics.Color;
 import android.os.Handler;
 import android.os.UserHandle;
@@ -40,11 +41,13 @@
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 
+import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -56,7 +59,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Executor;
 
@@ -85,6 +87,8 @@
     private KeyguardStateController mKeyguardStateController;
     @Mock
     private DumpManager mDumpManager;
+    @Mock
+    private FeatureFlags mFeatureFlags;
     @Captor
     private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallback;
     @Captor
@@ -93,10 +97,20 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        when(mFeatureFlags.isMonetEnabled()).thenReturn(true);
         mThemeOverlayController = new ThemeOverlayController(null /* context */,
                 mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier,
                 mSecureSettings, mWallpaperManager, mUserManager, mKeyguardStateController,
-                mDumpManager);
+                mDumpManager, mFeatureFlags) {
+            @Nullable
+            @Override
+            protected FabricatedOverlay getOverlay(int color, int type) {
+                FabricatedOverlay overlay = mock(FabricatedOverlay.class);
+                when(overlay.getIdentifier())
+                        .thenReturn(new OverlayIdentifier(Integer.toHexString(color | 0xff000000)));
+                return overlay;
+            }
+        };
 
         mThemeOverlayController.start();
         if (USE_LOCK_SCREEN_WALLPAPER) {
@@ -106,10 +120,6 @@
         verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null),
                 eq(UserHandle.USER_ALL));
         verify(mDumpManager).registerDumpable(any(), any());
-
-        List<Integer> colorList = List.of(Color.RED, Color.BLUE, 0x0CCCCC, 0x000CCC);
-        when(mThemeOverlayApplier.getAvailableAccentColors()).thenReturn(colorList);
-        when(mThemeOverlayApplier.getAvailableSystemColors()).thenReturn(colorList);
     }
 
     @Test
@@ -128,17 +138,17 @@
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
                 Color.valueOf(Color.BLUE), null);
         mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
-        ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
+        ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
+                ArgumentCaptor.forClass(Map.class);
 
-        verify(mThemeOverlayApplier).getAvailableSystemColors();
-        verify(mThemeOverlayApplier).getAvailableAccentColors();
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
+        verify(mThemeOverlayApplier)
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), any());
 
         // Assert that we received the colors that we were expecting
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
-                .isEqualTo(MONET_SYSTEM_PALETTE_PACKAGE + "FF0000");
+                .isEqualTo(new OverlayIdentifier("ffff0000"));
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
-                .isEqualTo(MONET_ACCENT_COLOR_PACKAGE + "0000FF");
+                .isEqualTo(new OverlayIdentifier("ff0000ff"));
 
         // Should not ask again if changed to same value
         mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
@@ -146,49 +156,6 @@
     }
 
     @Test
-    public void onWallpaperColorsChanged_whiteTheme() {
-        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.WHITE),
-                Color.valueOf(Color.BLUE), null);
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
-        ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
-
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
-
-        // Assert that we received the colors that we were expecting
-        assertThat(themeOverlays.getValue().containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)).isFalse();
-    }
-
-    @Test
-    public void onWallpaperColorsChanged_blackTheme() {
-        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.BLACK),
-                Color.valueOf(Color.BLUE), null);
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
-        ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
-
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
-
-        // Assert that we received the colors that we were expecting
-        assertThat(themeOverlays.getValue().containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)).isFalse();
-    }
-
-    @Test
-    public void onWallpaperColorsChanged_addsLeadingZerosToColors() {
-        // Should ask for a new theme when wallpaper colors change
-        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(0x0CCCCC),
-                Color.valueOf(0x000CCC), null);
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
-        ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
-
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
-
-        // Assert that we received the colors that we were expecting
-        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
-                .isEqualTo(MONET_SYSTEM_PALETTE_PACKAGE + "0CCCCC");
-        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
-                .isEqualTo(MONET_ACCENT_COLOR_PACKAGE + "000CCC");
-    }
-
-    @Test
     public void onWallpaperColorsChanged_preservesWallpaperPickerTheme() {
         // Should ask for a new theme when wallpaper colors change
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -201,14 +168,37 @@
                 .thenReturn(jsonString);
 
         mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
-        ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
+        ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
+                ArgumentCaptor.forClass(Map.class);
 
-        verify(mThemeOverlayApplier).getAvailableSystemColors();
-        verify(mThemeOverlayApplier).getAvailableAccentColors();
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
+        verify(mThemeOverlayApplier)
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), any());
 
         // Assert that we received the colors that we were expecting
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
-                .isEqualTo("override.package.name");
+                .isEqualTo(new OverlayIdentifier("override.package.name"));
+    }
+
+    @Test
+    public void onWallpaperColorsChanged_parsesColorsFromWallpaperPicker() {
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+
+        String jsonString =
+                "{\"android.theme.customization.system_palette\":\"00FF00\"}";
+        when(mSecureSettings.getStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+                .thenReturn(jsonString);
+
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
+                ArgumentCaptor.forClass(Map.class);
+
+        verify(mThemeOverlayApplier)
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), any());
+
+        // Assert that we received the colors that we were expecting
+        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
+                .isEqualTo(new OverlayIdentifier("ff00ff00"));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index c0af15b..203ece9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -19,8 +19,8 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 
 import java.util.List;
@@ -66,7 +66,7 @@
     }
 
     @Override
-    public void setNoCallingIcons(String slot, List<NoCallingIconState> states) {
+    public void setCallIndicatorIcons(String slot, List<CallIndicatorIconState> states) {
     }
 
     @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 76269dd..f1fc0b77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -89,8 +89,6 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.bubbles.Bubble;
@@ -297,7 +295,7 @@
 
         mBubblesManager = new BubblesManager(
                 mContext,
-                mBubbleController.getImpl(),
+                mBubbleController.asBubbles(),
                 mNotificationShadeWindowController,
                 mStatusBarStateController,
                 mShadeController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java
index f4d96a12..ab329c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java
@@ -20,7 +20,7 @@
 import android.content.Intent;
 import android.os.Bundle;
 
-import com.android.systemui.R;
+import com.android.systemui.tests.R;
 
 /**
  * Referenced by NotificationTestHelper#makeBubbleMetadata
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index 5340ff7..9e10b21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -261,7 +261,7 @@
 
         mBubblesManager = new BubblesManager(
                 mContext,
-                mBubbleController.getImpl(),
+                mBubbleController.asBubbles(),
                 mNotificationShadeWindowController,
                 mStatusBarStateController,
                 mShadeController,
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index a6cfae4..c6a8660 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -2,7 +2,7 @@
 per-file ConnectivityService.java,ConnectivityServiceInitializer.java,NetworkManagementService.java,NsdService.java = file:/services/core/java/com/android/server/net/OWNERS
 
 # Vibrator / Threads
-per-file VibratorManagerService.java, VibratorService.java, DisplayThread.java = michaelwr@google.com, ogunwale@google.com
+per-file VibratorManagerService.java, DisplayThread.java = michaelwr@google.com, ogunwale@google.com
 
 # Zram writeback
 per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 00d8b0f..d10cf4d 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -30,6 +30,7 @@
 import android.os.UserManager;
 import android.service.persistentdata.IPersistentDataBlockService;
 import android.service.persistentdata.PersistentDataBlockManager;
+import android.text.TextUtils;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -147,14 +148,15 @@
     private int getAllowedUid(int userHandle) {
         String allowedPackage = mContext.getResources()
                 .getString(R.string.config_persistentDataPackageName);
-        PackageManager pm = mContext.getPackageManager();
         int allowedUid = -1;
-        try {
-            allowedUid = pm.getPackageUidAsUser(allowedPackage,
-                    PackageManager.MATCH_SYSTEM_ONLY, userHandle);
-        } catch (PackageManager.NameNotFoundException e) {
-            // not expected
-            Slog.e(TAG, "not able to find package " + allowedPackage, e);
+        if (!TextUtils.isEmpty(allowedPackage)) {
+            try {
+                allowedUid = mContext.getPackageManager().getPackageUidAsUser(
+                        allowedPackage, PackageManager.MATCH_SYSTEM_ONLY, userHandle);
+            } catch (PackageManager.NameNotFoundException e) {
+                // not expected
+                Slog.e(TAG, "not able to find package " + allowedPackage, e);
+            }
         }
         return allowedUid;
     }
diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/VibratorManagerService.java
index e7e5d67..d264f85 100644
--- a/services/core/java/com/android/server/VibratorManagerService.java
+++ b/services/core/java/com/android/server/VibratorManagerService.java
@@ -18,9 +18,14 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.hardware.vibrator.IVibrator;
 import android.os.BatteryStats;
 import android.os.Binder;
@@ -74,6 +79,7 @@
 /** System implementation of {@link IVibratorManagerService}. */
 public class VibratorManagerService extends IVibratorManagerService.Stub {
     private static final String TAG = "VibratorManagerService";
+    private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
     private static final boolean DEBUG = false;
     private static final VibrationAttributes DEFAULT_ATTRIBUTES =
             new VibrationAttributes.Builder().build();
@@ -89,7 +95,7 @@
         @Override
         public void onStart() {
             mService = new VibratorManagerService(getContext(), new Injector());
-            publishBinderService("vibrator_manager", mService);
+            publishBinderService(Context.VIBRATOR_MANAGER_SERVICE, mService);
         }
 
         @Override
@@ -105,6 +111,7 @@
 
     private final Object mLock = new Object();
     private final Context mContext;
+    private final String mSystemUiPackage;
     private final PowerManager.WakeLock mWakeLock;
     private final IBatteryStats mBatteryStatsService;
     private final Handler mHandler;
@@ -128,6 +135,26 @@
     private VibrationScaler mVibrationScaler;
     private InputDeviceDelegate mInputDeviceDelegate;
 
+    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+                synchronized (mLock) {
+                    // When the system is entering a non-interactive state, we want
+                    // to cancel vibrations in case a misbehaving app has abandoned
+                    // them.  However it may happen that the system is currently playing
+                    // haptic feedback as part of the transition.  So we don't cancel
+                    // system vibrations.
+                    if (mCurrentVibration != null
+                            && !isSystemHapticFeedback(mCurrentVibration.getVibration())) {
+                        mNextVibration = null;
+                        mCurrentVibration.cancel();
+                    }
+                }
+            }
+        }
+    };
+
     static native long nativeInit(OnSyncedVibrationCompleteListener listener);
 
     static native long nativeGetFinalizer();
@@ -155,6 +182,9 @@
                 com.android.internal.R.integer.config_previousVibrationsDumpLimit);
         mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit);
 
+        mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
+                .getSystemUiServiceComponent().getPackageName();
+
         mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
                 BatteryStats.SERVICE_NAME));
 
@@ -184,6 +214,12 @@
         for (int i = 0; i < mVibrators.size(); i++) {
             mVibrators.valueAt(i).off();
         }
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        context.registerReceiver(mIntentReceiver, filter);
+
+        injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
     }
 
     /** Finish initialization at boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. */
@@ -371,7 +407,7 @@
                         mVibratorManagerRecords.record(mCurrentExternalVibration);
                         mCurrentExternalVibration.externalVibration.mute();
                         mCurrentExternalVibration = null;
-                        // TODO(b/167946816): set external control to false
+                        setExternalControl(false);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -432,6 +468,12 @@
         }
     }
 
+    private void setExternalControl(boolean externalControl) {
+        for (int i = 0; i < mVibrators.size(); i++) {
+            mVibrators.valueAt(i).setExternalControl(externalControl);
+        }
+    }
+
     @GuardedBy("mLock")
     private void updateAlwaysOnLocked(AlwaysOnVibration vib) {
         for (int i = 0; i < vib.effects.size(); i++) {
@@ -507,6 +549,12 @@
     }
 
     @GuardedBy("mLock")
+    private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) {
+        vib.end(status);
+        mVibratorManagerRecords.record(vib);
+    }
+
+    @GuardedBy("mLock")
     private void reportFinishedVibrationLocked(Vibration.Status status) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
         Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
@@ -827,6 +875,13 @@
                 == PackageManager.PERMISSION_GRANTED;
     }
 
+    private boolean isSystemHapticFeedback(Vibration vib) {
+        if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) {
+            return false;
+        }
+        return vib.uid == Process.SYSTEM_UID || vib.uid == 0 || mSystemUiPackage.equals(vib.opPkg);
+    }
+
     @GuardedBy("mLock")
     private void onAllVibratorsLocked(Consumer<VibratorController> consumer) {
         for (int i = 0; i < mVibrators.size(); i++) {
@@ -859,6 +914,10 @@
                 VibratorController.OnVibrationCompleteListener listener) {
             return new VibratorController(vibratorId, listener);
         }
+
+        void addService(String name, IBinder service) {
+            ServiceManager.addService(name, service);
+        }
     }
 
     /**
@@ -1088,14 +1147,14 @@
                 }
                 pw.println();
                 pw.println("  mCurrentVibration:");
-                pw.println("    " + mCurrentVibration == null
-                        ? null : mCurrentVibration.getVibration().getDebugInfo());
+                pw.println("    " + (mCurrentVibration == null
+                        ? null : mCurrentVibration.getVibration().getDebugInfo()));
                 pw.println("  mNextVibration:");
-                pw.println("    " + mNextVibration == null
-                        ? null : mNextVibration.getVibration().getDebugInfo());
+                pw.println("    " + (mNextVibration == null
+                        ? null : mNextVibration.getVibration().getDebugInfo()));
                 pw.println("  mCurrentExternalVibration:");
-                pw.println("    " + mCurrentExternalVibration == null
-                        ? null : mCurrentExternalVibration.getDebugInfo());
+                pw.println("    " + (mCurrentExternalVibration == null
+                        ? null : mCurrentExternalVibration.getDebugInfo()));
                 pw.println();
                 pw.println("  mVibrationSettings=" + mVibrationSettings);
                 for (int i = 0; i < mPreviousVibrations.size(); i++) {
@@ -1168,6 +1227,145 @@
         }
     }
 
+    /** Implementation of {@link IExternalVibratorService} to be triggered on external control. */
+    private final class ExternalVibratorService extends IExternalVibratorService.Stub {
+        ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient;
+
+        @Override
+        public int onExternalVibrationStart(ExternalVibration vib) {
+            if (!hasExternalControlCapability()) {
+                return IExternalVibratorService.SCALE_MUTE;
+            }
+            if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
+                    vib.getUid(), -1 /*owningUid*/, true /*exported*/)
+                    != PackageManager.PERMISSION_GRANTED) {
+                Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
+                        + " tried to play externally controlled vibration"
+                        + " without VIBRATE permission, ignoring.");
+                return IExternalVibratorService.SCALE_MUTE;
+            }
+
+            int mode = checkAppOpModeLocked(vib.getUid(), vib.getPackage(),
+                    vib.getVibrationAttributes());
+            if (mode != AppOpsManager.MODE_ALLOWED) {
+                ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
+                vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
+                if (mode == AppOpsManager.MODE_ERRORED) {
+                    Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid());
+                    endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS);
+                } else {
+                    endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS);
+                }
+                return vibHolder.scale;
+            }
+
+            VibrationThread cancelingVibration = null;
+            int scale;
+            synchronized (mLock) {
+                if (mCurrentExternalVibration != null
+                        && mCurrentExternalVibration.externalVibration.equals(vib)) {
+                    // We are already playing this external vibration, so we can return the same
+                    // scale calculated in the previous call to this method.
+                    return mCurrentExternalVibration.scale;
+                }
+                if (mCurrentExternalVibration == null) {
+                    // If we're not under external control right now, then cancel any normal
+                    // vibration that may be playing and ready the vibrator for external control.
+                    if (mCurrentVibration != null) {
+                        mNextVibration = null;
+                        mCurrentVibration.cancel();
+                        cancelingVibration = mCurrentVibration;
+                    }
+                } else {
+                    endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED);
+                }
+                // At this point we either have an externally controlled vibration playing, or
+                // no vibration playing. Since the interface defines that only one externally
+                // controlled vibration can play at a time, by returning something other than
+                // SCALE_MUTE from this function we can be assured that if we are currently
+                // playing vibration, it will be muted in favor of the new vibration.
+                //
+                // Note that this doesn't support multiple concurrent external controls, as we
+                // would need to mute the old one still if it came from a different controller.
+                mCurrentExternalVibration = new ExternalVibrationHolder(vib);
+                mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
+                vib.linkToDeath(mCurrentExternalDeathRecipient);
+                mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale(
+                        vib.getVibrationAttributes().getUsage());
+                scale = mCurrentExternalVibration.scale;
+            }
+
+            if (cancelingVibration != null) {
+                try {
+                    cancelingVibration.join();
+                } catch (InterruptedException e) {
+                    Slog.w("Interrupted while waiting for vibration to finish before starting "
+                                    + "external control", e);
+                }
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "Vibrator going under external control.");
+            }
+            setExternalControl(true);
+            if (DEBUG) {
+                Slog.e(TAG, "Playing external vibration: " + vib);
+            }
+            return scale;
+        }
+
+        @Override
+        public void onExternalVibrationStop(ExternalVibration vib) {
+            synchronized (mLock) {
+                if (mCurrentExternalVibration != null
+                        && mCurrentExternalVibration.externalVibration.equals(vib)) {
+                    if (DEBUG) {
+                        Slog.e(TAG, "Stopping external vibration" + vib);
+                    }
+                    stopExternalVibrateLocked(Vibration.Status.FINISHED);
+                }
+            }
+        }
+
+        private void stopExternalVibrateLocked(Vibration.Status status) {
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "stopExternalVibrateLocked");
+            try {
+                if (mCurrentExternalVibration == null) {
+                    return;
+                }
+                endVibrationLocked(mCurrentExternalVibration, status);
+                mCurrentExternalVibration.externalVibration.unlinkToDeath(
+                        mCurrentExternalDeathRecipient);
+                mCurrentExternalDeathRecipient = null;
+                mCurrentExternalVibration = null;
+                setExternalControl(false);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+            }
+        }
+
+        private boolean hasExternalControlCapability() {
+            for (int i = 0; i < mVibrators.size(); i++) {
+                if (mVibrators.valueAt(i).hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private class ExternalVibrationDeathRecipient implements IBinder.DeathRecipient {
+            public void binderDied() {
+                synchronized (mLock) {
+                    if (mCurrentExternalVibration != null) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "External vibration finished because binder died");
+                        }
+                        stopExternalVibrateLocked(Vibration.Status.CANCELLED);
+                    }
+                }
+            }
+        }
+    }
+
     /** Provide limited functionality from {@link VibratorManagerService} as shell commands. */
     private final class VibratorManagerShellCommand extends ShellCommand {
         public static final String SHELL_PACKAGE_NAME = "com.android.shell";
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
deleted file mode 100644
index 2ac365d..0000000
--- a/services/core/java/com/android/server/VibratorService.java
+++ /dev/null
@@ -1,1243 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.hardware.vibrator.IVibrator;
-import android.os.BatteryStats;
-import android.os.Binder;
-import android.os.CombinedVibrationEffect;
-import android.os.ExternalVibration;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IExternalVibratorService;
-import android.os.IVibratorService;
-import android.os.IVibratorStateListener;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.ResultReceiver;
-import android.os.ServiceManager;
-import android.os.ShellCallback;
-import android.os.ShellCommand;
-import android.os.Trace;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.os.VibratorInfo;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.util.DumpUtils;
-import com.android.server.vibrator.InputDeviceDelegate;
-import com.android.server.vibrator.Vibration;
-import com.android.server.vibrator.VibrationScaler;
-import com.android.server.vibrator.VibrationSettings;
-import com.android.server.vibrator.VibrationThread;
-import com.android.server.vibrator.VibratorController;
-import com.android.server.vibrator.VibratorController.OnVibrationCompleteListener;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/** System implementation of {@link IVibratorService}. */
-public class VibratorService extends IVibratorService.Stub {
-    private static final String TAG = "VibratorService";
-    private static final boolean DEBUG = false;
-    private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
-
-    // Default vibration attributes. Used when vibration is requested without attributes
-    private static final VibrationAttributes DEFAULT_ATTRIBUTES =
-            new VibrationAttributes.Builder().build();
-
-    // Used to generate globally unique vibration ids.
-    private final AtomicInteger mNextVibrationId = new AtomicInteger(1); // 0 = no callback
-
-    private final LinkedList<Vibration.DebugInfo> mPreviousRingVibrations;
-    private final LinkedList<Vibration.DebugInfo> mPreviousNotificationVibrations;
-    private final LinkedList<Vibration.DebugInfo> mPreviousAlarmVibrations;
-    private final LinkedList<Vibration.DebugInfo> mPreviousExternalVibrations;
-    private final LinkedList<Vibration.DebugInfo> mPreviousVibrations;
-    private final int mPreviousVibrationsLimit;
-    private final Handler mH;
-    private final Object mLock = new Object();
-    private final VibratorController mVibratorController;
-    private final VibrationCallbacks mVibrationCallbacks = new VibrationCallbacks();
-
-    private final Context mContext;
-    private final PowerManager.WakeLock mWakeLock;
-    private final AppOpsManager mAppOps;
-    private final IBatteryStats mBatteryStatsService;
-    private final String mSystemUiPackage;
-    private VibrationSettings mVibrationSettings;
-    private VibrationScaler mVibrationScaler;
-    private InputDeviceDelegate mInputDeviceDelegate;
-
-    @GuardedBy("mLock")
-    private VibrationThread mThread;
-    @GuardedBy("mLock")
-    private VibrationThread mNextVibrationThread;
-
-    @GuardedBy("mLock")
-    private Vibration mCurrentVibration;
-    private int mCurVibUid = -1;
-    private ExternalVibrationHolder mCurrentExternalVibration;
-
-    /**
-     * Implementation of {@link VibrationThread.VibrationCallbacks} that reports finished
-     * vibrations.
-     */
-    private final class VibrationCallbacks implements VibrationThread.VibrationCallbacks {
-
-        @Override
-        public boolean prepareSyncedVibration(long requiredCapabilities, int[] vibratorIds) {
-            return false;
-        }
-
-        @Override
-        public boolean triggerSyncedVibration(long vibrationId) {
-            return false;
-        }
-
-        @Override
-        public void cancelSyncedVibration() {
-        }
-
-        @Override
-        public void onVibrationEnded(long vibrationId, Vibration.Status status) {
-            if (DEBUG) {
-                Slog.d(TAG, "Vibration thread finished with status " + status);
-            }
-            synchronized (mLock) {
-                if (mCurrentVibration != null && mCurrentVibration.id == vibrationId) {
-                    mThread = null;
-                    reportFinishVibrationLocked(status);
-                    if (mNextVibrationThread != null) {
-                        startVibrationThreadLocked(mNextVibrationThread);
-                        mNextVibrationThread = null;
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Implementation of {@link OnVibrationCompleteListener} with a weak reference to this service.
-     */
-    private static final class VibrationCompleteListener implements OnVibrationCompleteListener {
-        private WeakReference<VibratorService> mServiceRef;
-
-        VibrationCompleteListener(VibratorService service) {
-            mServiceRef = new WeakReference<>(service);
-        }
-
-        @Override
-        public void onComplete(int vibratorId, long vibrationId) {
-            VibratorService service = mServiceRef.get();
-            if (service != null) {
-                service.onVibrationComplete(vibratorId, vibrationId);
-            }
-        }
-    }
-
-    /** Holder for a {@link ExternalVibration}. */
-    private final class ExternalVibrationHolder {
-
-        public final ExternalVibration externalVibration;
-        public int scale;
-
-        private final long mStartTimeDebug;
-        private long mEndTimeDebug;
-        private Vibration.Status mStatus;
-
-        private ExternalVibrationHolder(ExternalVibration externalVibration) {
-            this.externalVibration = externalVibration;
-            this.scale = IExternalVibratorService.SCALE_NONE;
-            mStartTimeDebug = System.currentTimeMillis();
-            mStatus = Vibration.Status.RUNNING;
-        }
-
-        public void end(Vibration.Status status) {
-            if (mStatus != Vibration.Status.RUNNING) {
-                // Vibration already ended, keep first ending status set and ignore this one.
-                return;
-            }
-            mStatus = status;
-            mEndTimeDebug = System.currentTimeMillis();
-        }
-
-        public Vibration.DebugInfo getDebugInfo() {
-            return new Vibration.DebugInfo(
-                    mStartTimeDebug, mEndTimeDebug, /* effect= */ null, /* originalEffect= */ null,
-                    scale, externalVibration.getVibrationAttributes(),
-                    externalVibration.getUid(), externalVibration.getPackage(),
-                    /* reason= */ null, mStatus);
-        }
-    }
-
-    VibratorService(Context context) {
-        this(context, new Injector());
-    }
-
-    @VisibleForTesting
-    VibratorService(Context context, Injector injector) {
-        mH = injector.createHandler(Looper.myLooper());
-        mVibratorController = injector.createVibratorController(
-                new VibrationCompleteListener(this));
-
-        // Reset the hardware to a default state, in case this is a runtime
-        // restart instead of a fresh boot.
-        mVibratorController.off();
-
-        mContext = context;
-        PowerManager pm = context.getSystemService(PowerManager.class);
-        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
-        mWakeLock.setReferenceCounted(true);
-
-        mAppOps = mContext.getSystemService(AppOpsManager.class);
-        mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
-                BatteryStats.SERVICE_NAME));
-        mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
-                .getSystemUiServiceComponent().getPackageName();
-
-        mPreviousVibrationsLimit = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_previousVibrationsDumpLimit);
-
-        mPreviousRingVibrations = new LinkedList<>();
-        mPreviousNotificationVibrations = new LinkedList<>();
-        mPreviousAlarmVibrations = new LinkedList<>();
-        mPreviousVibrations = new LinkedList<>();
-        mPreviousExternalVibrations = new LinkedList<>();
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_SCREEN_OFF);
-        context.registerReceiver(mIntentReceiver, filter);
-
-        injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
-    }
-
-    public void systemReady() {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorService#systemReady");
-        try {
-            mVibrationSettings = new VibrationSettings(mContext, mH);
-            mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
-            mInputDeviceDelegate = new InputDeviceDelegate(mContext, mH);
-
-            mVibrationSettings.addListener(this::updateVibrators);
-
-            mContext.registerReceiver(new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    mVibrationSettings.updateSettings();
-                }
-            }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
-
-            updateVibrators();
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-        }
-    }
-
-    /** Callback for when vibration is complete, to be called by native. */
-    @VisibleForTesting
-    public void onVibrationComplete(int vibratorId, long vibrationId) {
-        synchronized (mLock) {
-            if (mCurrentVibration != null && mCurrentVibration.id == vibrationId
-                    && mThread != null) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Vibration onComplete callback, notifying VibrationThread");
-                }
-                // Let the thread playing the vibration handle the callback, since it might be
-                // expecting the vibrator to turn off multiple times during a single vibration.
-                mThread.vibratorComplete(vibratorId);
-            }
-        }
-    }
-
-    @Override // Binder call
-    public boolean hasVibrator() {
-        // For now, we choose to ignore the presence of input devices that have vibrators
-        // when reporting whether the device has a vibrator.  Applications often use this
-        // information to decide whether to enable certain features so they expect the
-        // result of hasVibrator() to be constant.  For now, just report whether
-        // the device has a built-in vibrator.
-        return mVibratorController.isAvailable();
-    }
-
-    @Override // Binder call
-    public boolean isVibrating() {
-        if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
-            throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
-        }
-        return mVibratorController.isVibrating();
-    }
-
-    @Override // Binder call
-    public VibratorInfo getVibratorInfo() {
-        return mVibratorController.getVibratorInfo();
-    }
-
-    @Override // Binder call
-    public boolean registerVibratorStateListener(IVibratorStateListener listener) {
-        if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
-            throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
-        }
-        return mVibratorController.registerVibratorStateListener(listener);
-    }
-
-    @Override // Binder call
-    @GuardedBy("mLock")
-    public boolean unregisterVibratorStateListener(IVibratorStateListener listener) {
-        if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
-            throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
-        }
-        return mVibratorController.unregisterVibratorStateListener(listener);
-    }
-
-    @Override // Binder call
-    public boolean hasAmplitudeControl() {
-        // Input device vibrators always support amplitude controls.
-        return mInputDeviceDelegate.isAvailable()
-                || mVibratorController.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL);
-    }
-
-    private void verifyIncomingUid(int uid) {
-        if (uid == Binder.getCallingUid()) {
-            return;
-        }
-        if (Binder.getCallingPid() == Process.myPid()) {
-            return;
-        }
-        mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
-                Binder.getCallingPid(), Binder.getCallingUid(), null);
-    }
-
-    /**
-     * Validate the incoming VibrationEffect.
-     *
-     * We can't throw exceptions here since we might be called from some system_server component,
-     * which would bring the whole system down.
-     *
-     * @return whether the VibrationEffect is valid
-     */
-    private static boolean verifyVibrationEffect(VibrationEffect effect) {
-        if (effect == null) {
-            // Effect must not be null.
-            Slog.wtf(TAG, "effect must not be null");
-            return false;
-        }
-        try {
-            effect.validate();
-        } catch (Exception e) {
-            Slog.wtf(TAG, "Encountered issue when verifying VibrationEffect.", e);
-            return false;
-        }
-        return true;
-    }
-
-    private VibrationEffect fixupVibrationEffect(VibrationEffect effect) {
-        if (effect instanceof VibrationEffect.Prebaked
-                && ((VibrationEffect.Prebaked) effect).shouldFallback()) {
-            VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
-            VibrationEffect fallback = mVibrationSettings.getFallbackEffect(prebaked.getId());
-            return new VibrationEffect.Prebaked(prebaked.getId(), prebaked.getEffectStrength(),
-                    fallback);
-        }
-        return effect;
-    }
-
-    private VibrationAttributes fixupVibrationAttributes(VibrationAttributes attrs) {
-        if (attrs == null) {
-            attrs = DEFAULT_ATTRIBUTES;
-        }
-        if (shouldBypassDnd(attrs)) {
-            if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-                    || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-                    || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
-                final int flags = attrs.getFlags()
-                        & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
-                attrs = new VibrationAttributes.Builder(attrs)
-                                .setFlags(flags, attrs.getFlags()).build();
-            }
-        }
-
-        return attrs;
-    }
-
-    @Override // Binder call
-    public void vibrate(int uid, String opPkg, VibrationEffect effect,
-            @Nullable VibrationAttributes attrs, String reason, IBinder token) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
-        try {
-            if (!hasPermission(android.Manifest.permission.VIBRATE)) {
-                throw new SecurityException("Requires VIBRATE permission");
-            }
-            if (token == null) {
-                Slog.e(TAG, "token must not be null");
-                return;
-            }
-            verifyIncomingUid(uid);
-            if (!verifyVibrationEffect(effect)) {
-                return;
-            }
-            effect = fixupVibrationEffect(effect);
-            attrs = fixupVibrationAttributes(attrs);
-            Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(),
-                    CombinedVibrationEffect.createSynced(effect), attrs, uid, opPkg, reason);
-
-            // If our current vibration is longer than the new vibration and is the same amplitude,
-            // then just let the current one finish.
-            synchronized (mLock) {
-                VibrationEffect currentEffect =
-                        mCurrentVibration == null ? null : getEffect(mCurrentVibration);
-                if (effect instanceof VibrationEffect.OneShot
-                        && currentEffect instanceof VibrationEffect.OneShot) {
-                    VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
-                    VibrationEffect.OneShot currentOneShot =
-                            (VibrationEffect.OneShot) currentEffect;
-                    if (currentOneShot.getDuration() > newOneShot.getDuration()
-                            && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
-                        if (DEBUG) {
-                            Slog.d(TAG,
-                                    "Ignoring incoming vibration in favor of current vibration");
-                        }
-                        endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_ONGOING);
-                        return;
-                    }
-                }
-
-
-                // If something has external control of the vibrator, assume that it's more
-                // important for now.
-                if (mCurrentExternalVibration != null) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
-                    }
-                    endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_EXTERNAL);
-                    return;
-                }
-
-                // If the current vibration is repeating and the incoming one is non-repeating,
-                // then ignore the non-repeating vibration. This is so that we don't cancel
-                // vibrations that are meant to grab the attention of the user, like ringtones and
-                // alarms, in favor of one-shot vibrations that are likely quite short.
-                if (!isRepeatingVibration(effect)
-                        && mCurrentVibration != null
-                        && isRepeatingVibration(currentEffect)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
-                    }
-                    endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_ALARM);
-                    return;
-                }
-
-                if (!mVibrationSettings.shouldVibrateForUid(uid, vib.attrs.getUsage())) {
-                    Slog.e(TAG, "Ignoring incoming vibration as process with"
-                            + " uid= " + uid + " is background,"
-                            + " attrs= " + vib.attrs);
-                    endVibrationLocked(vib, Vibration.Status.IGNORED_BACKGROUND);
-                    return;
-                }
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    doCancelVibrateLocked(Vibration.Status.CANCELLED);
-                    startVibrationLocked(vib);
-                    boolean isNextVibration = mNextVibrationThread != null
-                            && vib.equals(mNextVibrationThread.getVibration());
-
-                    if (!vib.hasEnded() && !vib.equals(mCurrentVibration) && !isNextVibration) {
-                        // Vibration was unexpectedly ignored: add to list for debugging
-                        endVibrationLocked(vib, Vibration.Status.IGNORED);
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
-                }
-            }
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-        }
-    }
-
-    private boolean hasPermission(String permission) {
-        return mContext.checkCallingOrSelfPermission(permission)
-                == PackageManager.PERMISSION_GRANTED;
-    }
-
-    private static boolean isRepeatingVibration(VibrationEffect effect) {
-        return effect.getDuration() == Long.MAX_VALUE;
-    }
-
-    private static <T extends VibrationEffect> T getEffect(Vibration vib) {
-        return (T) ((CombinedVibrationEffect.Mono) vib.getEffect()).getEffect();
-    }
-
-    private void endVibrationLocked(Vibration vib, Vibration.Status status) {
-        final LinkedList<Vibration.DebugInfo> previousVibrations;
-        switch (vib.attrs.getUsage()) {
-            case VibrationAttributes.USAGE_NOTIFICATION:
-                previousVibrations = mPreviousNotificationVibrations;
-                break;
-            case VibrationAttributes.USAGE_RINGTONE:
-                previousVibrations = mPreviousRingVibrations;
-                break;
-            case VibrationAttributes.USAGE_ALARM:
-                previousVibrations = mPreviousAlarmVibrations;
-                break;
-            default:
-                previousVibrations = mPreviousVibrations;
-        }
-        if (previousVibrations.size() > mPreviousVibrationsLimit) {
-            previousVibrations.removeFirst();
-        }
-        vib.end(status);
-        previousVibrations.addLast(vib.getDebugInfo());
-    }
-
-    private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) {
-        if (mPreviousExternalVibrations.size() > mPreviousVibrationsLimit) {
-            mPreviousExternalVibrations.removeFirst();
-        }
-        vib.end(status);
-        mPreviousExternalVibrations.addLast(vib.getDebugInfo());
-    }
-
-    @Override // Binder call
-    public void cancelVibrate(IBinder token) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.VIBRATE,
-                "cancelVibrate");
-
-        synchronized (mLock) {
-            if (mCurrentVibration != null && mCurrentVibration.token == token) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Canceling vibration.");
-                }
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    mNextVibrationThread = null;
-                    doCancelVibrateLocked(Vibration.Status.CANCELLED);
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
-                }
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void doCancelVibrateLocked(Vibration.Status status) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelVibrateLocked");
-        try {
-            if (mThread != null) {
-                mThread.cancel();
-            }
-            mInputDeviceDelegate.cancelVibrateIfAvailable();
-            if (mCurrentExternalVibration != null) {
-                endVibrationLocked(mCurrentExternalVibration, status);
-                mCurrentExternalVibration.externalVibration.mute();
-                mCurrentExternalVibration = null;
-                mVibratorController.setExternalControl(false);
-            }
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void startVibrationLocked(final Vibration vib) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
-        try {
-            if (!shouldVibrate(vib)) {
-                return;
-            }
-            applyVibrationIntensityScalingLocked(vib);
-            startVibrationInnerLocked(vib);
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void startVibrationInnerLocked(Vibration vib) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked");
-        try {
-            boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
-                    vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs);
-            if (inputDevicesAvailable) {
-                endVibrationLocked(vib, Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
-            } else if (mThread == null) {
-                startVibrationThreadLocked(new VibrationThread(vib, mVibratorController, mWakeLock,
-                        mBatteryStatsService, mVibrationCallbacks));
-            } else {
-                mNextVibrationThread = new VibrationThread(vib, mVibratorController, mWakeLock,
-                        mBatteryStatsService, mVibrationCallbacks);
-            }
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void startVibrationThreadLocked(VibrationThread thread) {
-        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
-        mCurrentVibration = thread.getVibration();
-        mThread = thread;
-        mThread.start();
-    }
-
-    /** Scale the vibration effect by the intensity as appropriate based its intent. */
-    private void applyVibrationIntensityScalingLocked(Vibration vib) {
-        vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage()));
-    }
-
-    private static boolean shouldBypassDnd(VibrationAttributes attrs) {
-        return attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY);
-    }
-
-    private int getAppOpMode(int uid, String packageName, VibrationAttributes attrs) {
-        int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
-                attrs.getAudioUsage(), uid, packageName);
-        if (mode == AppOpsManager.MODE_ALLOWED) {
-            mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, packageName);
-        }
-
-        if (mode == AppOpsManager.MODE_IGNORED && shouldBypassDnd(attrs)) {
-            // If we're just ignoring the vibration op then this is set by DND and we should ignore
-            // if we're asked to bypass. AppOps won't be able to record this operation, so make
-            // sure we at least note it in the logs for debugging.
-            Slog.d(TAG, "Bypassing DND for vibrate from uid " + uid);
-            mode = AppOpsManager.MODE_ALLOWED;
-        }
-        return mode;
-    }
-
-    private boolean shouldVibrate(Vibration vib) {
-        if (!mVibrationSettings.shouldVibrateForPowerMode(vib.attrs.getUsage())) {
-            endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_POWER);
-            return false;
-        }
-
-        int intensity = mVibrationSettings.getCurrentIntensity(vib.attrs.getUsage());
-        if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
-            endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_SETTINGS);
-            return false;
-        }
-
-        if (!mVibrationSettings.shouldVibrateForRingerMode(vib.attrs.getUsage())) {
-            if (DEBUG) {
-                Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
-            }
-            endVibrationLocked(vib, Vibration.Status.IGNORED_RINGTONE);
-            return false;
-        }
-
-        final int mode = getAppOpMode(vib.uid, vib.opPkg, vib.attrs);
-        if (mode != AppOpsManager.MODE_ALLOWED) {
-            if (mode == AppOpsManager.MODE_ERRORED) {
-                // We might be getting calls from within system_server, so we don't actually
-                // want to throw a SecurityException here.
-                Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
-                endVibrationLocked(vib, Vibration.Status.IGNORED_ERROR_APP_OPS);
-            } else {
-                endVibrationLocked(vib, Vibration.Status.IGNORED_APP_OPS);
-            }
-            return false;
-        }
-
-        return true;
-    }
-
-    @GuardedBy("mLock")
-    private void reportFinishVibrationLocked(Vibration.Status status) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
-        try {
-            if (mCurrentVibration != null) {
-                endVibrationLocked(mCurrentVibration, status);
-                mAppOps.finishOp(AppOpsManager.OP_VIBRATE, mCurrentVibration.uid,
-                        mCurrentVibration.opPkg);
-
-                Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
-                mCurrentVibration = null;
-            }
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-        }
-    }
-
-    @VisibleForTesting
-    void updateVibrators() {
-        synchronized (mLock) {
-            boolean inputDevicesChanged = mInputDeviceDelegate.updateInputDeviceVibrators(
-                    mVibrationSettings.shouldVibrateInputDevices());
-
-            if (mCurrentVibration == null) {
-                return;
-            }
-
-            if (inputDevicesChanged || !mVibrationSettings.shouldVibrateForPowerMode(
-                    mCurrentVibration.attrs.getUsage())) {
-                // If the state changes out from under us then just reset.
-                doCancelVibrateLocked(Vibration.Status.CANCELLED);
-            }
-        }
-    }
-
-    private boolean isSystemHapticFeedback(Vibration vib) {
-        if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) {
-            return false;
-        }
-        return vib.uid == Process.SYSTEM_UID || vib.uid == 0 || mSystemUiPackage.equals(vib.opPkg);
-    }
-
-    private void dumpInternal(PrintWriter pw) {
-        pw.println("Vibrator Service:");
-        synchronized (mLock) {
-            pw.print("  mCurrentVibration=");
-            if (mCurrentVibration != null) {
-                pw.println(mCurrentVibration.getDebugInfo().toString());
-            } else {
-                pw.println("null");
-            }
-            pw.print("  mCurrentExternalVibration=");
-            if (mCurrentExternalVibration != null) {
-                pw.println(mCurrentExternalVibration.getDebugInfo().toString());
-            } else {
-                pw.println("null");
-            }
-            pw.println("  mVibratorController=" + mVibratorController);
-            pw.println("  mVibrationSettings=" + mVibrationSettings);
-            pw.println();
-            pw.println("  Previous ring vibrations:");
-            for (Vibration.DebugInfo info : mPreviousRingVibrations) {
-                pw.print("    ");
-                pw.println(info.toString());
-            }
-
-            pw.println("  Previous notification vibrations:");
-            for (Vibration.DebugInfo info : mPreviousNotificationVibrations) {
-                pw.println("    " + info);
-            }
-
-            pw.println("  Previous alarm vibrations:");
-            for (Vibration.DebugInfo info : mPreviousAlarmVibrations) {
-                pw.println("    " + info);
-            }
-
-            pw.println("  Previous vibrations:");
-            for (Vibration.DebugInfo info : mPreviousVibrations) {
-                pw.println("    " + info);
-            }
-
-            pw.println("  Previous external vibrations:");
-            for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
-                pw.println("    " + info);
-            }
-        }
-    }
-
-    private void dumpProto(FileDescriptor fd) {
-        final ProtoOutputStream proto = new ProtoOutputStream(fd);
-
-        synchronized (mLock) {
-            if (mCurrentVibration != null) {
-                mCurrentVibration.getDebugInfo().dumpProto(proto,
-                        VibratorServiceDumpProto.CURRENT_VIBRATION);
-            }
-            if (mCurrentExternalVibration != null) {
-                mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
-                        VibratorServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
-            }
-            proto.write(VibratorServiceDumpProto.IS_VIBRATING, mVibratorController.isVibrating());
-            proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
-                    mVibratorController.isUnderExternalControl());
-            mVibrationSettings.dumpProto(proto);
-
-            for (Vibration.DebugInfo info : mPreviousRingVibrations) {
-                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
-            }
-
-            for (Vibration.DebugInfo info : mPreviousNotificationVibrations) {
-                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS);
-            }
-
-            for (Vibration.DebugInfo info : mPreviousAlarmVibrations) {
-                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS);
-            }
-
-            for (Vibration.DebugInfo info : mPreviousVibrations) {
-                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_VIBRATIONS);
-            }
-
-            for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
-                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
-            }
-        }
-        proto.flush();
-    }
-
-    /** Point of injection for test dependencies */
-    @VisibleForTesting
-    static class Injector {
-
-        VibratorController createVibratorController(OnVibrationCompleteListener listener) {
-            return new VibratorController(/* vibratorId= */ -1, listener);
-        }
-
-        Handler createHandler(Looper looper) {
-            return new Handler(looper);
-        }
-
-        void addService(String name, IBinder service) {
-            ServiceManager.addService(name, service);
-        }
-    }
-
-    BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
-                synchronized (mLock) {
-                    // When the system is entering a non-interactive state, we want
-                    // to cancel vibrations in case a misbehaving app has abandoned
-                    // them.  However it may happen that the system is currently playing
-                    // haptic feedback as part of the transition.  So we don't cancel
-                    // system vibrations.
-                    if (mCurrentVibration != null && !isSystemHapticFeedback(mCurrentVibration)) {
-                        mNextVibrationThread = null;
-                        doCancelVibrateLocked(Vibration.Status.CANCELLED);
-                    }
-                }
-            }
-        }
-    };
-
-    @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
-
-        final long ident = Binder.clearCallingIdentity();
-
-        boolean isDumpProto = false;
-        for (String arg : args) {
-            if (arg.equals("--proto")) {
-                isDumpProto = true;
-            }
-        }
-        try {
-            if (isDumpProto) {
-                dumpProto(fd);
-            } else {
-                dumpInternal(pw);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    @Override
-    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
-            String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
-        new VibratorShellCommand(this).exec(this, in, out, err, args, callback, resultReceiver);
-    }
-
-    final class ExternalVibratorService extends IExternalVibratorService.Stub {
-        ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient;
-
-        @Override
-        public int onExternalVibrationStart(ExternalVibration vib) {
-            if (!mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
-                return IExternalVibratorService.SCALE_MUTE;
-            }
-            if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
-                    vib.getUid(), -1 /*owningUid*/, true /*exported*/)
-                    != PackageManager.PERMISSION_GRANTED) {
-                Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
-                        + " tried to play externally controlled vibration"
-                        + " without VIBRATE permission, ignoring.");
-                return IExternalVibratorService.SCALE_MUTE;
-            }
-
-            int mode = getAppOpMode(vib.getUid(), vib.getPackage(), vib.getVibrationAttributes());
-            if (mode != AppOpsManager.MODE_ALLOWED) {
-                ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
-                vibHolder.scale = SCALE_MUTE;
-                if (mode == AppOpsManager.MODE_ERRORED) {
-                    Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid());
-                    endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS);
-                } else {
-                    endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS);
-                }
-                return IExternalVibratorService.SCALE_MUTE;
-            }
-
-            VibrationThread cancelingVibration = null;
-            int scale;
-            synchronized (mLock) {
-                if (mCurrentExternalVibration != null
-                        && mCurrentExternalVibration.externalVibration.equals(vib)) {
-                    // We are already playing this external vibration, so we can return the same
-                    // scale calculated in the previous call to this method.
-                    return mCurrentExternalVibration.scale;
-                }
-                if (mCurrentExternalVibration == null) {
-                    // If we're not under external control right now, then cancel any normal
-                    // vibration that may be playing and ready the vibrator for external control.
-                    mNextVibrationThread = null;
-                    doCancelVibrateLocked(Vibration.Status.CANCELLED);
-                    cancelingVibration = mThread;
-                } else {
-                    endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED);
-                }
-                // At this point we either have an externally controlled vibration playing, or
-                // no vibration playing. Since the interface defines that only one externally
-                // controlled vibration can play at a time, by returning something other than
-                // SCALE_MUTE from this function we can be assured that if we are currently
-                // playing vibration, it will be muted in favor of the new vibration.
-                //
-                // Note that this doesn't support multiple concurrent external controls, as we
-                // would need to mute the old one still if it came from a different controller.
-                mCurrentExternalVibration = new ExternalVibrationHolder(vib);
-                mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
-                vib.linkToDeath(mCurrentExternalDeathRecipient);
-                mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale(
-                        vib.getVibrationAttributes().getUsage());
-                scale = mCurrentExternalVibration.scale;
-            }
-            if (cancelingVibration != null) {
-                try {
-                    cancelingVibration.join();
-                } catch (InterruptedException e) {
-                    Slog.w("Interrupted while waiting current vibration to be cancelled before "
-                            + "starting external vibration", e);
-                }
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "Vibrator going under external control.");
-            }
-            mVibratorController.setExternalControl(true);
-            if (DEBUG) {
-                Slog.e(TAG, "Playing external vibration: " + vib);
-            }
-            return scale;
-        }
-
-        @Override
-        public void onExternalVibrationStop(ExternalVibration vib) {
-            synchronized (mLock) {
-                if (mCurrentExternalVibration != null
-                        && mCurrentExternalVibration.externalVibration.equals(vib)) {
-                    if (DEBUG) {
-                        Slog.e(TAG, "Stopping external vibration" + vib);
-                    }
-                    doCancelExternalVibrateLocked(Vibration.Status.FINISHED);
-                }
-            }
-        }
-
-        private void doCancelExternalVibrateLocked(Vibration.Status status) {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelExternalVibrateLocked");
-            try {
-                if (mCurrentExternalVibration == null) {
-                    return;
-                }
-                endVibrationLocked(mCurrentExternalVibration, status);
-                mCurrentExternalVibration.externalVibration.unlinkToDeath(
-                        mCurrentExternalDeathRecipient);
-                mCurrentExternalDeathRecipient = null;
-                mCurrentExternalVibration = null;
-                mVibratorController.setExternalControl(false);
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        private class ExternalVibrationDeathRecipient implements IBinder.DeathRecipient {
-            public void binderDied() {
-                synchronized (mLock) {
-                    if (mCurrentExternalVibration != null) {
-                        if (DEBUG) {
-                            Slog.d(TAG, "External vibration finished because binder died");
-                        }
-                        doCancelExternalVibrateLocked(Vibration.Status.CANCELLED);
-                    }
-                }
-            }
-        }
-    }
-
-    private final class VibratorShellCommand extends ShellCommand {
-
-        private final IBinder mToken;
-
-        private final class CommonOptions {
-            public boolean force = false;
-            public void check(String opt) {
-                switch (opt) {
-                    case "-f":
-                        force = true;
-                        break;
-                }
-            }
-        }
-
-        private VibratorShellCommand(IBinder token) {
-            mToken = token;
-        }
-
-        @Override
-        public int onCommand(String cmd) {
-            if ("vibrate".equals(cmd)) {
-                return runVibrate();
-            } else if ("waveform".equals(cmd)) {
-                return runWaveform();
-            } else if ("prebaked".equals(cmd)) {
-                return runPrebaked();
-            } else if ("capabilities".equals(cmd)) {
-                return runCapabilities();
-            } else if ("cancel".equals(cmd)) {
-                cancelVibrate(mToken);
-                return 0;
-            }
-            return handleDefaultCommands(cmd);
-        }
-
-        private boolean checkDoNotDisturb(CommonOptions opts) {
-            if (mVibrationSettings.isInZenMode() && !opts.force) {
-                try (PrintWriter pw = getOutPrintWriter();) {
-                    pw.print("Ignoring because device is on DND mode ");
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        private int runVibrate() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runVibrate");
-            try {
-                CommonOptions commonOptions = new CommonOptions();
-
-                String opt;
-                while ((opt = getNextOption()) != null) {
-                    commonOptions.check(opt);
-                }
-
-                if (checkDoNotDisturb(commonOptions)) {
-                    return 0;
-                }
-
-                final long duration = Long.parseLong(getNextArgRequired());
-                String description = getNextArg();
-                if (description == null) {
-                    description = "Shell command";
-                }
-
-                VibrationEffect effect =
-                        VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE);
-                VibrationAttributes attrs = createVibrationAttributes(commonOptions);
-                vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
-                        mToken);
-                return 0;
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        private int runWaveform() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runWaveform");
-            try {
-                String description = "Shell command";
-                int repeat = -1;
-                ArrayList<Integer> amplitudesList = null;
-                CommonOptions commonOptions = new CommonOptions();
-
-                String opt;
-                while ((opt = getNextOption()) != null) {
-                    switch (opt) {
-                        case "-d":
-                            description = getNextArgRequired();
-                            break;
-                        case "-r":
-                            repeat = Integer.parseInt(getNextArgRequired());
-                            break;
-                        case "-a":
-                            if (amplitudesList == null) {
-                                amplitudesList = new ArrayList<Integer>();
-                            }
-                            break;
-                        default:
-                            commonOptions.check(opt);
-                            break;
-                    }
-                }
-
-                if (checkDoNotDisturb(commonOptions)) {
-                    return 0;
-                }
-
-                ArrayList<Long> timingsList = new ArrayList<Long>();
-
-                String arg;
-                while ((arg = getNextArg()) != null) {
-                    if (amplitudesList != null && amplitudesList.size() < timingsList.size()) {
-                        amplitudesList.add(Integer.parseInt(arg));
-                    } else {
-                        timingsList.add(Long.parseLong(arg));
-                    }
-                }
-
-                VibrationEffect effect;
-                long[] timings = timingsList.stream().mapToLong(Long::longValue).toArray();
-                if (amplitudesList == null) {
-                    effect = VibrationEffect.createWaveform(timings, repeat);
-                } else {
-                    int[] amplitudes =
-                            amplitudesList.stream().mapToInt(Integer::intValue).toArray();
-                    effect = VibrationEffect.createWaveform(timings, amplitudes, repeat);
-                }
-                VibrationAttributes attrs = createVibrationAttributes(commonOptions);
-                vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
-                        mToken);
-                return 0;
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        private int runPrebaked() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runPrebaked");
-            try {
-                CommonOptions commonOptions = new CommonOptions();
-                boolean shouldFallback = false;
-
-                String opt;
-                while ((opt = getNextOption()) != null) {
-                    if ("-b".equals(opt)) {
-                        shouldFallback = true;
-                    } else {
-                        commonOptions.check(opt);
-                    }
-                }
-
-                if (checkDoNotDisturb(commonOptions)) {
-                    return 0;
-                }
-
-                final int id = Integer.parseInt(getNextArgRequired());
-
-                String description = getNextArg();
-                if (description == null) {
-                    description = "Shell command";
-                }
-
-                VibrationEffect effect = VibrationEffect.get(id, shouldFallback);
-                VibrationAttributes attrs = createVibrationAttributes(commonOptions);
-                vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
-                        mToken);
-                return 0;
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        private int runCapabilities() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runCapabilities");
-            try (PrintWriter pw = getOutPrintWriter();) {
-                pw.println("Vibrator capabilities:");
-                if (mVibratorController.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
-                    pw.println("  Always on effects");
-                }
-                if (mVibratorController.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
-                    pw.println("  Compose effects");
-                }
-                if (mVibratorController.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
-                    pw.println("  Amplitude control");
-                }
-                if (mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
-                    pw.println("  External control");
-                }
-                if (mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_AMPLITUDE_CONTROL)) {
-                    pw.println("  External amplitude control");
-                }
-                pw.println("");
-                return 0;
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        private VibrationAttributes createVibrationAttributes(CommonOptions commonOptions) {
-            final int flags = commonOptions.force
-                    ? VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
-                    : 0;
-            return new VibrationAttributes.Builder()
-                    .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED)
-                    // Used to apply Settings.System.HAPTIC_FEEDBACK_INTENSITY to scale effects.
-                    .setUsage(VibrationAttributes.USAGE_TOUCH)
-                    .build();
-        }
-
-        @Override
-        public void onHelp() {
-            try (PrintWriter pw = getOutPrintWriter();) {
-                pw.println("Vibrator commands:");
-                pw.println("  help");
-                pw.println("    Prints this help text.");
-                pw.println("");
-                pw.println("  vibrate duration [description]");
-                pw.println("    Vibrates for duration milliseconds; ignored when device is on ");
-                pw.println("    DND (Do Not Disturb) mode; touch feedback strength user setting ");
-                pw.println("    will be used to scale amplitude.");
-                pw.println("  waveform [-d description] [-r index] [-a] duration [amplitude] ...");
-                pw.println("    Vibrates for durations and amplitudes in list; ignored when ");
-                pw.println("    device is on DND (Do Not Disturb) mode; touch feedback strength ");
-                pw.println("    user setting will be used to scale amplitude.");
-                pw.println("    If -r is provided, the waveform loops back to the specified");
-                pw.println("    index (e.g. 0 loops from the beginning)");
-                pw.println("    If -a is provided, the command accepts duration-amplitude pairs;");
-                pw.println("    otherwise, it accepts durations only and alternates off/on");
-                pw.println("    Duration is in milliseconds; amplitude is a scale of 1-255.");
-                pw.println("  prebaked [-b] effect-id [description]");
-                pw.println("    Vibrates with prebaked effect; ignored when device is on DND ");
-                pw.println("    (Do Not Disturb) mode; touch feedback strength user setting ");
-                pw.println("    will be used to scale amplitude.");
-                pw.println("    If -b is provided, the prebaked fallback effect will be played if");
-                pw.println("    the device doesn't support the given effect-id.");
-                pw.println("  capabilities");
-                pw.println("    Prints capabilities of this device.");
-                pw.println("  cancel");
-                pw.println("    Cancels any active vibration");
-                pw.println("Common Options:");
-                pw.println("  -f - Force. Ignore Do Not Disturb setting.");
-                pw.println("");
-            }
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/accounts/OWNERS b/services/core/java/com/android/server/accounts/OWNERS
index ea5fd36..8dcc04a 100644
--- a/services/core/java/com/android/server/accounts/OWNERS
+++ b/services/core/java/com/android/server/accounts/OWNERS
@@ -3,7 +3,6 @@
 sandrakwan@google.com
 hackbod@google.com
 svetoslavganov@google.com
-moltmann@google.com
 fkupolov@google.com
 yamasani@google.com
 omakoto@google.com
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f0f29a9..bedd19b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1490,7 +1490,9 @@
     private static final int INDEX_TOTAL_SWAP_PSS = 10;
     private static final int INDEX_TOTAL_RSS = 11;
     private static final int INDEX_TOTAL_NATIVE_PSS = 12;
-    private static final int INDEX_LAST = 13;
+    private static final int INDEX_TOTAL_MEMTRACK_GRAPHICS = 13;
+    private static final int INDEX_TOTAL_MEMTRACK_GL = 14;
+    private static final int INDEX_LAST = 15;
 
     final class UiHandler extends Handler {
         public UiHandler() {
@@ -10316,6 +10318,7 @@
         long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
         long[] miscSwapPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
         long[] miscRss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
+        long[] memtrackTmp = new long[4];
 
         long oomPss[] = new long[DUMP_MEM_OOM_LABEL.length];
         long oomSwapPss[] = new long[DUMP_MEM_OOM_LABEL.length];
@@ -10349,6 +10352,8 @@
                 final int reportType;
                 final long startTime;
                 final long endTime;
+                long memtrackGraphics = 0;
+                long memtrackGl = 0;
                 if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
                     reportType = ProcessStats.ADD_PSS_EXTERNAL_SLOW;
                     startTime = SystemClock.currentThreadTimeMillis();
@@ -10360,7 +10365,7 @@
                 } else {
                     reportType = ProcessStats.ADD_PSS_EXTERNAL;
                     startTime = SystemClock.currentThreadTimeMillis();
-                    long pss = Debug.getPss(pid, tmpLong, null);
+                    long pss = Debug.getPss(pid, tmpLong, memtrackTmp);
                     if (pss == 0) {
                         continue;
                     }
@@ -10368,6 +10373,8 @@
                     endTime = SystemClock.currentThreadTimeMillis();
                     mi.dalvikPrivateDirty = (int) tmpLong[0];
                     mi.dalvikRss = (int) tmpLong[2];
+                    memtrackGraphics = memtrackTmp[1];
+                    memtrackGl = memtrackTmp[2];
                 }
                 if (!opts.isCheckinRequest && opts.dumpDetails) {
                     pw.println("\n** MEMINFO in pid " + pid + " [" + r.processName + "] **");
@@ -10431,6 +10438,8 @@
                     ss[INDEX_TOTAL_PSS] += myTotalPss;
                     ss[INDEX_TOTAL_SWAP_PSS] += myTotalSwapPss;
                     ss[INDEX_TOTAL_RSS] += myTotalRss;
+                    ss[INDEX_TOTAL_MEMTRACK_GRAPHICS] += memtrackGraphics;
+                    ss[INDEX_TOTAL_MEMTRACK_GL] += memtrackGl;
                     MemItem pssItem = new MemItem(r.processName + " (pid " + pid +
                             (hasActivities ? " / activities)" : ")"), r.processName, myTotalPss,
                             myTotalSwapPss, myTotalRss, pid, hasActivities);
@@ -10494,6 +10503,8 @@
             final Debug.MemoryInfo[] memInfos = new Debug.MemoryInfo[1];
             mAppProfiler.forAllCpuStats((st) -> {
                 if (st.vsize > 0 && procMemsMap.indexOfKey(st.pid) < 0) {
+                    long memtrackGraphics = 0;
+                    long memtrackGl = 0;
                     if (memInfos[0] == null) {
                         memInfos[0] = new Debug.MemoryInfo();
                     }
@@ -10503,13 +10514,15 @@
                             return;
                         }
                     } else {
-                        long pss = Debug.getPss(st.pid, tmpLong, null);
+                        long pss = Debug.getPss(st.pid, tmpLong, memtrackTmp);
                         if (pss == 0) {
                             return;
                         }
                         info.nativePss = (int) pss;
                         info.nativePrivateDirty = (int) tmpLong[0];
                         info.nativeRss = (int) tmpLong[2];
+                        memtrackGraphics = memtrackTmp[1];
+                        memtrackGl = memtrackTmp[2];
                     }
 
                     final long myTotalPss = info.getTotalPss();
@@ -10519,6 +10532,8 @@
                     ss[INDEX_TOTAL_SWAP_PSS] += myTotalSwapPss;
                     ss[INDEX_TOTAL_RSS] += myTotalRss;
                     ss[INDEX_TOTAL_NATIVE_PSS] += myTotalPss;
+                    ss[INDEX_TOTAL_MEMTRACK_GRAPHICS] += memtrackGraphics;
+                    ss[INDEX_TOTAL_MEMTRACK_GL] += memtrackGl;
 
                     MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")",
                             st.name, myTotalPss, info.getSummaryTotalSwapPss(), myTotalRss,
@@ -10726,7 +10741,11 @@
                     pw.print(" mapped + ");
                     pw.print(stringifyKBSize(dmabufUnmapped));
                     pw.println(" unmapped)");
-                    kernelUsed += totalExportedDmabuf;
+                    // Account unmapped dmabufs as part of kernel memory allocations
+                    kernelUsed += dmabufUnmapped;
+                    // Replace memtrack HAL reported Graphics category with mapped dmabufs
+                    ss[INDEX_TOTAL_PSS] -= ss[INDEX_TOTAL_MEMTRACK_GRAPHICS];
+                    ss[INDEX_TOTAL_PSS] += dmabufMapped;
                 }
                 final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb();
                 if (totalDmabufHeapPool >= 0) {
@@ -10736,13 +10755,27 @@
             }
             final long gpuUsage = Debug.getGpuTotalUsageKb();
             if (gpuUsage >= 0) {
-                pw.print("      GPU: "); pw.println(stringifyKBSize(gpuUsage));
+                final long gpuDmaBufUsage = Debug.getGpuDmaBufUsageKb();
+                if (gpuDmaBufUsage >= 0) {
+                    final long gpuPrivateUsage = gpuUsage - gpuDmaBufUsage;
+                    pw.print("      GPU: ");
+                    pw.print(stringifyKBSize(gpuUsage));
+                    pw.print(" (");
+                    pw.print(stringifyKBSize(gpuDmaBufUsage));
+                    pw.print(" dmabuf + ");
+                    pw.print(stringifyKBSize(gpuPrivateUsage));
+                    pw.println(" private)");
+                    // Replace memtrack HAL reported GL category with private GPU allocations and
+                    // account it as part of kernel memory allocations
+                    ss[INDEX_TOTAL_PSS] -= ss[INDEX_TOTAL_MEMTRACK_GL];
+                    kernelUsed += gpuPrivateUsage;
+                } else {
+                    pw.print("      GPU: "); pw.println(stringifyKBSize(gpuUsage));
+                }
             }
 
-            /*
-             * Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of
-             * memInfo.getCachedSizeKb().
-             */
+             // Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of
+             // memInfo.getCachedSizeKb().
             final long lostRAM = memInfo.getTotalSizeKb()
                     - (ss[INDEX_TOTAL_PSS] - ss[INDEX_TOTAL_SWAP_PSS])
                     - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 3ff5872..fc7a476 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1322,7 +1322,7 @@
             infoMap.put(mi.pid, mi);
         }
         updateCpuStatsNow();
-        long[] memtrackTmp = new long[1];
+        long[] memtrackTmp = new long[4];
         long[] swaptrackTmp = new long[2];
         // Get a list of Stats that have vsize > 0
         final List<ProcessCpuTracker.Stats> stats = getCpuStats(st -> st.vsize > 0);
@@ -1345,6 +1345,8 @@
         long totalPss = 0;
         long totalSwapPss = 0;
         long totalMemtrack = 0;
+        long totalMemtrackGraphics = 0;
+        long totalMemtrackGl = 0;
         for (int i = 0, size = memInfos.size(); i < size; i++) {
             ProcessMemInfo mi = memInfos.get(i);
             if (mi.pss == 0) {
@@ -1355,6 +1357,8 @@
             totalPss += mi.pss;
             totalSwapPss += mi.swapPss;
             totalMemtrack += mi.memtrack;
+            totalMemtrackGraphics += memtrackTmp[1];
+            totalMemtrackGl += memtrackTmp[2];
         }
         Collections.sort(memInfos, new Comparator<ProcessMemInfo>() {
             @Override public int compare(ProcessMemInfo lhs, ProcessMemInfo rhs) {
@@ -1521,10 +1525,16 @@
         } else {
             final long totalExportedDmabuf = Debug.getDmabufTotalExportedKb();
             if (totalExportedDmabuf >= 0) {
+                final long dmabufMapped = Debug.getDmabufMappedSizeKb();
+                final long dmabufUnmapped = totalExportedDmabuf - dmabufMapped;
                 memInfoBuilder.append("DMA-BUF: ");
                 memInfoBuilder.append(stringifyKBSize(totalExportedDmabuf));
                 memInfoBuilder.append("\n");
-                kernelUsed += totalExportedDmabuf;
+                // Account unmapped dmabufs as part of kernel memory allocations
+                kernelUsed += dmabufUnmapped;
+                // Replace memtrack HAL reported Graphics category with mapped dmabufs
+                totalPss -= totalMemtrackGraphics;
+                totalPss += dmabufMapped;
             }
 
             final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb();
@@ -1537,19 +1547,34 @@
 
         final long gpuUsage = Debug.getGpuTotalUsageKb();
         if (gpuUsage >= 0) {
-            memInfoBuilder.append("       GPU: ");
-            memInfoBuilder.append(stringifyKBSize(gpuUsage));
-            memInfoBuilder.append("\n");
+            final long gpuDmaBufUsage = Debug.getGpuDmaBufUsageKb();
+            if (gpuDmaBufUsage >= 0) {
+                final long gpuPrivateUsage = gpuUsage - gpuDmaBufUsage;
+                memInfoBuilder.append("      GPU: ");
+                memInfoBuilder.append(stringifyKBSize(gpuUsage));
+                memInfoBuilder.append(" (");
+                memInfoBuilder.append(stringifyKBSize(gpuDmaBufUsage));
+                memInfoBuilder.append(" dmabuf + ");
+                memInfoBuilder.append(stringifyKBSize(gpuPrivateUsage));
+                memInfoBuilder.append(" private)\n");
+                // Replace memtrack HAL reported GL category with private GPU allocations and
+                // account it as part of kernel memory allocations
+                totalPss -= totalMemtrackGl;
+                kernelUsed += gpuPrivateUsage;
+            } else {
+                memInfoBuilder.append("       GPU: ");
+                memInfoBuilder.append(stringifyKBSize(gpuUsage));
+                memInfoBuilder.append("\n");
+            }
+
         }
         memInfoBuilder.append("  Used RAM: ");
         memInfoBuilder.append(stringifyKBSize(
                                   totalPss - cachedPss + kernelUsed));
         memInfoBuilder.append("\n");
 
-        /*
-         * Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of
-         * memInfo.getCachedSizeKb().
-         */
+        // Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of
+        // memInfo.getCachedSizeKb().
         memInfoBuilder.append("  Lost RAM: ");
         memInfoBuilder.append(stringifyKBSize(memInfo.getTotalSizeKb()
                 - (totalPss - totalSwapPss) - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index c6947c2d..b994389 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -15,8 +15,6 @@
  */
 package com.android.server.am;
 
-import static com.android.internal.power.MeasuredEnergyArray.SUBSYSTEM_DISPLAY;
-
 import android.annotation.Nullable;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.BluetoothAdapter;
@@ -39,13 +37,12 @@
 import android.telephony.TelephonyManager;
 import android.util.IntArray;
 import android.util.Slog;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
 import android.util.SparseLongArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BatteryStatsImpl;
-import com.android.internal.power.MeasuredEnergyArray;
 import com.android.internal.power.MeasuredEnergyStats;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -148,13 +145,13 @@
     private WifiActivityEnergyInfo mLastWifiInfo =
             new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0);
 
-    /** Maps the EnergyConsumer id to it's corresponding {@link MeasuredEnergySubsystem} */
+    /**
+     * Maps an {@link EnergyConsumerType} to it's corresponding {@link EnergyConsumer#id}s,
+     * unless it is of {@link EnergyConsumer#type}=={@link EnergyConsumerType#OTHER}
+     */
+    // TODO: Hook this up (it isn't used yet)
     @GuardedBy("mWorkerLock")
-    private @Nullable SparseIntArray mEnergyConsumerToSubsystemMap = null;
-
-    /** Maps a {@link MeasuredEnergySubsystem} to it's corresponding EnergyConsumer id */
-    @GuardedBy("mWorkerLock")
-    private @Nullable SparseIntArray mSubsystemToEnergyConsumerMap = null;
+    private @Nullable SparseArray<int[]> mEnergyConsumerTypeToIdMap = null;
 
     /** Snapshot of measured energies, or null if no measured energies are supported. */
     @GuardedBy("mWorkerLock")
@@ -204,18 +201,26 @@
             mWifiManager = wm;
             mTelephony = tm;
             mPowerStatsInternal = psi;
+
+            boolean[] supportedStdBuckets = null;
+            int numCustomBuckets = 0;
             if (mPowerStatsInternal != null) {
-                populateEnergyConsumerSubsystemMapsLocked();
-                final MeasuredEnergyArray initialMeasuredEnergies = getEnergyConsumptionData();
-                mMeasuredEnergySnapshot = initialMeasuredEnergies == null
-                        ? null : new MeasuredEnergySnapshot(initialMeasuredEnergies);
-                final boolean[] supportedStdBuckets
-                        = getSupportedEnergyBuckets(initialMeasuredEnergies);
-                final int numCustomBuckets = 0; // TODO: Get this from initialMeasuredEnergies
-                synchronized (mStats) {
-                    mStats.initMeasuredEnergyStatsLocked(supportedStdBuckets, numCustomBuckets);
+                final SparseArray<EnergyConsumer> idToConsumer
+                        = populateEnergyConsumerSubsystemMapsLocked();
+                if (idToConsumer != null) {
+                    mMeasuredEnergySnapshot = new MeasuredEnergySnapshot(idToConsumer);
+                    final EnergyConsumerResult[] initialEcrs = getEnergyConsumptionData();
+                    // 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);
+                    numCustomBuckets = mMeasuredEnergySnapshot.getNumOtherOrdinals();
+                    supportedStdBuckets = getSupportedEnergyBuckets(idToConsumer);
                 }
             }
+            synchronized (mStats) {
+                mStats.initMeasuredEnergyStatsLocked(supportedStdBuckets, numCustomBuckets);
+            }
         }
     }
 
@@ -568,7 +573,9 @@
         } catch (ExecutionException e) {
             Slog.w(TAG, "exception reading modem stats: " + e.getCause());
         }
-        final SparseLongArray energyDeltas = mMeasuredEnergySnapshot == null ? null :
+
+        final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDeltas =
+                mMeasuredEnergySnapshot == null ? null :
                 mMeasuredEnergySnapshot.updateAndGetDelta(getMeasuredEnergyLocked(updateFlags));
 
         final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -576,6 +583,7 @@
         final long elapsedRealtimeUs = elapsedRealtime * 1000;
         final long uptimeUs = uptime * 1000;
 
+        // Now that we have finally received all the data, we can tell mStats about it.
         synchronized (mStats) {
             mStats.addHistoryEventLocked(
                     elapsedRealtime,
@@ -601,10 +609,21 @@
             }
 
             // Inform mStats about each applicable measured energy.
-            if (energyDeltas != null) {
-                final long displayEnergy = energyDeltas.get(SUBSYSTEM_DISPLAY, 0L);
-                // Always pass in what BatteryExternalStatsWorker thinks screenState is.
-                mStats.updateDisplayEnergyLocked(displayEnergy, screenState, elapsedRealtime);
+            if (measuredEnergyDeltas != null) {
+                final long displayEnergy = measuredEnergyDeltas.displayEnergyUJ;
+                if (displayEnergy != MeasuredEnergySnapshot.UNAVAILABLE) {
+                    // If updating, pass in what BatteryExternalStatsWorker thinks screenState is.
+                    mStats.updateDisplayEnergyLocked(displayEnergy, screenState, elapsedRealtime);
+                }
+            }
+            // Inform mStats about each applicable custom energy bucket.
+            if (measuredEnergyDeltas != null && measuredEnergyDeltas.otherTotalEnergyUJ != null) {
+                // Iterate over the custom (EnergyConsumerType.OTHER) ordinals.
+                for (int ord = 0; ord < measuredEnergyDeltas.otherTotalEnergyUJ.length; ord++) {
+                    long totalEnergy = measuredEnergyDeltas.otherTotalEnergyUJ[ord];
+                    SparseLongArray uidEnergies = measuredEnergyDeltas.otherUidEnergiesUJ[ord];
+                    mStats.updateCustomMeasuredEnergyDataLocked(ord, totalEnergy, uidEnergies);
+                }
             }
 
             if (bluetoothInfo != null) {
@@ -621,7 +640,8 @@
 
         if (wifiInfo != null) {
             if (wifiInfo.isValid()) {
-                // TODO: wifiEnergyDelta = energyDeltas.get(MeasuredEnergyArray.SUBSYSTEM_WIFI, 0L);
+                // TODO: wifiEnergyDelta = measuredEnergyDeltas.consumerTypeEnergyUJ
+                //               .get(EnergyConsumerType.WIFI, MeasuredEnergySnapshot.UNAVAILABLE)
                 mStats.updateWifiState(extractDeltaLocked(wifiInfo)
                         /*, TODO: wifiEnergyDelta */, elapsedRealtime, uptime);
             } else {
@@ -740,21 +760,23 @@
     }
 
     /**
-     * Map the {@link MeasuredEnergyArray.MeasuredEnergySubsystem}s in the given energyArray to
+     * Map the {@link EnergyConsumerType}s in the given energyArray to
      * their corresponding {@link MeasuredEnergyStats.StandardEnergyBucket}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.
      */
-    private static @Nullable boolean[] getSupportedEnergyBuckets(MeasuredEnergyArray energyArray) {
-        if (energyArray == null) {
+    private static @Nullable boolean[] getSupportedEnergyBuckets(
+            SparseArray<EnergyConsumer> idToConsumer) {
+        if (idToConsumer == null) {
             return null;
         }
         final boolean[] buckets = new boolean[MeasuredEnergyStats.NUMBER_STANDARD_ENERGY_BUCKETS];
-        final int size = energyArray.size();
-        for (int energyIdx = 0; energyIdx < size; energyIdx++) {
-            switch (energyArray.getSubsystem(energyIdx)) {
-                case MeasuredEnergyArray.SUBSYSTEM_DISPLAY:
+        final int size = idToConsumer.size();
+        for (int idx = 0; idx < size; idx++) {
+            final EnergyConsumer consumer = idToConsumer.valueAt(idx);
+            switch (consumer.type) {
+                case EnergyConsumerType.DISPLAY:
                     buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON] = true;
                     buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE] = true;
                     buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_OTHER] = true;
@@ -764,71 +786,22 @@
         return buckets;
     }
 
-    /**
-     * Get a {@link MeasuredEnergyArray} with the latest
-     * {@link MeasuredEnergyArray.MeasuredEnergySubsystem} energy usage since boot.
-     *
-     * TODO(b/176988041): Replace {@link MeasuredEnergyArray} usage with {@link
-     * EnergyConsumerResult}[]
-     */
+    /** Get {@link EnergyConsumerResult}s with the latest energy usage since boot. */
     @GuardedBy("mWorkerLock")
-    @VisibleForTesting
-    public @Nullable MeasuredEnergyArray getEnergyConsumptionData() {
-        final EnergyConsumerResult[] results;
+    private @Nullable EnergyConsumerResult[] getEnergyConsumptionData() {
         try {
-            results = mPowerStatsInternal.getEnergyConsumedAsync(new int[0])
+            return mPowerStatsInternal.getEnergyConsumedAsync(new int[0])
                     .get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
         } catch (Exception e) {
             Slog.e(TAG, "Failed to getEnergyConsumedAsync", e);
             return null;
         }
-        if (results == null) return null;
-        final int size = results.length;
-        final int[] subsystems = new int[size];
-        final long[] energyUJ = new long[size];
-
-        int count = 0;
-        for (int i = 0; i < size; i++) {
-            final EnergyConsumerResult consumer = results[i];
-            final int subsystem = mEnergyConsumerToSubsystemMap.get(consumer.id,
-                    MeasuredEnergyArray.SUBSYSTEM_UNKNOWN);
-            if (subsystem == MeasuredEnergyArray.SUBSYSTEM_UNKNOWN) continue;
-            subsystems[count] = subsystem;
-            energyUJ[count] = consumer.energyUWs;
-            count++;
-        }
-        final int arraySize = count;
-        return new MeasuredEnergyArray() {
-            @Override
-            public int getSubsystem(int index) {
-                if (index >= size()) {
-                    throw new IllegalArgumentException(
-                            "Out of bounds subsystem index! index : " + index + ", size : "
-                                    + size());
-                }
-                return subsystems[index];
-            }
-
-            @Override
-            public long getEnergy(int index) {
-                if (index >= size()) {
-                    throw new IllegalArgumentException(
-                            "Out of bounds subsystem index! index : " + index + ", size : "
-                                    + size());
-                }
-                return energyUJ[index];
-            }
-
-            @Override
-            public int size() {
-                return arraySize;
-            }
-        };
     }
 
-    /** Fetch MeasuredEnergyArray for supported subsystems based on the given updateFlags. */
+    /** Fetch EnergyConsumerResult[] for supported subsystems based on the given updateFlags. */
     @GuardedBy("mWorkerLock")
-    private @Nullable MeasuredEnergyArray getMeasuredEnergyLocked(@ExternalUpdateFlag int flags) {
+    private @Nullable EnergyConsumerResult[] getMeasuredEnergyLocked(@ExternalUpdateFlag int flags)
+    {
         if (mMeasuredEnergySnapshot == null || mPowerStatsInternal == null) return null;
 
         if (flags == UPDATE_ALL) {
@@ -836,12 +809,13 @@
             return getEnergyConsumptionData();
         }
 
-        final List<Integer> energySubsystems = new ArrayList<>();
+        final List<Integer> energyConsumerIds = new ArrayList<>();
         if ((flags & UPDATE_DISPLAY) != 0) {
-            addEnergyConsumerIdLocked(energySubsystems, SUBSYSTEM_DISPLAY);
+            addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.DISPLAY);
         }
         // TODO: Wifi, Bluetooth, etc., go here
-        if (energySubsystems.isEmpty()) {
+
+        if (energyConsumerIds.isEmpty()) {
             return null;
         }
         // TODO: Query *specific* subsystems from HAL based on energyConsumerIds.toArray()
@@ -849,59 +823,48 @@
     }
 
     @GuardedBy("mWorkerLock")
-    private void addEnergyConsumerIdLocked(List<Integer> energyConsumerIds,
-            @MeasuredEnergyArray.MeasuredEnergySubsystem int consumerId) {
-        if (mMeasuredEnergySnapshot.hasSubsystem(consumerId)) {
-            energyConsumerIds.add(consumerId);
-        }
+    private void addEnergyConsumerIdLocked(
+            List<Integer> energyConsumerIds, @EnergyConsumerType int type) {
+        final int consumerId = 0; // TODO: Use mEnergyConsumerTypeToIdMap to get this
+        energyConsumerIds.add(consumerId);
     }
 
+    /** Populates the cached type->ids map, and returns the (inverse) id->EnergyConsumer map. */
     @GuardedBy("mWorkerLock")
-    private void populateEnergyConsumerSubsystemMapsLocked() {
+    private @Nullable SparseArray<EnergyConsumer> populateEnergyConsumerSubsystemMapsLocked() {
         if (mPowerStatsInternal == null) {
-            // PowerStatsInternal unavailable, don't bother populating maps.
-            mEnergyConsumerToSubsystemMap = null;
-            mSubsystemToEnergyConsumerMap = null;
-            return;
+            return null;
         }
         final EnergyConsumer[] energyConsumers = mPowerStatsInternal.getEnergyConsumerInfo();
-        if (energyConsumers == null) {
-            // EnergyConsumer data unavailable, don't bother populating maps.
-            mEnergyConsumerToSubsystemMap = null;
-            mSubsystemToEnergyConsumerMap = null;
-            return;
+        if (energyConsumers == null || energyConsumers.length == 0) {
+            return null;
         }
 
-        final int length = energyConsumers.length;
-        if (length == 0) {
-            // EnergyConsumer array empty, don't bother populating maps.
-            mEnergyConsumerToSubsystemMap = null;
-            mSubsystemToEnergyConsumerMap = null;
-            return;
-        } else {
-            mEnergyConsumerToSubsystemMap = new SparseIntArray(length);
-            mSubsystemToEnergyConsumerMap = new SparseIntArray(length);
-        }
+        // TODO: Initialize typeToIds
+        // Maps type -> {ids} (1:n map, since multiple ids might have the same type)
+        // final SparseArray<SparseIntArray> typeToIds = new SparseArray<>();
+
+        // Maps id -> EnergyConsumer (1:1 map)
+        final SparseArray<EnergyConsumer> idToConsumer = new SparseArray<>(energyConsumers.length);
 
         // Add all expected EnergyConsumers to the maps
-        for (int i = 0; i < length; i++) {
-            final EnergyConsumer consumer = energyConsumers[i];
-            switch (consumer.type) {
-                case EnergyConsumerType.DISPLAY:
-                    if (consumer.ordinal == 0) {
-                        mEnergyConsumerToSubsystemMap.put(consumer.id,
-                                MeasuredEnergyArray.SUBSYSTEM_DISPLAY);
-                        mSubsystemToEnergyConsumerMap.put(MeasuredEnergyArray.SUBSYSTEM_DISPLAY,
-                                consumer.id);
-                    } else {
-                        Slog.w(TAG, "Unexpected ordinal (" + consumer.ordinal
-                                + ") for EnergyConsumerType.DISPLAY");
-                    }
-                    break;
-                default:
-                    Slog.w(TAG, "Unexpected EnergyConsumerType (" + consumer.type + ")");
+        for (final EnergyConsumer consumer : energyConsumers) {
+            // Check for inappropriate ordinals
+            if (consumer.ordinal != 0) {
+                switch (consumer.type) {
+                    case EnergyConsumerType.OTHER:
+                    case EnergyConsumerType.CPU_CLUSTER:
+                        break;
+                    default:
+                        Slog.w(TAG, "EnergyConsumer '" + consumer.name + "' has unexpected ordinal "
+                                + consumer.ordinal + " for type " + consumer.type);
+                        continue; // Ignore this consumer
+                }
             }
-
+            idToConsumer.put(consumer.id, consumer);
+            // TODO: Also populate typeToIds map
         }
+        // TODO: Store typeToIds in mEnergyConsumerTypeToIdMap.
+        return idToConsumer;
     }
 }
diff --git a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
index b915c0c..9e0aa32 100644
--- a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
+++ b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
@@ -17,132 +17,260 @@
 package com.android.server.am;
 
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerAttribution;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseLongArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.power.MeasuredEnergyArray;
-import com.android.internal.power.MeasuredEnergyArray.MeasuredEnergySubsystem;
 
 import java.io.PrintWriter;
-import java.util.Arrays;
 
 /**
- * Keeps snapshots of data from previously pulled MeasuredEnergyArrays.
+ * Keeps snapshots of data from previously pulled EnergyConsumerResults.
  */
 @VisibleForTesting
 public class MeasuredEnergySnapshot {
     private static final String TAG = "MeasuredEnergySnapshot";
 
-    private static final long UNAVAILABLE = -1;
+    public static final long UNAVAILABLE = -1L;
+
+    /** Map of {@link EnergyConsumer#id} to its corresponding {@link EnergyConsumer}. */
+    private final SparseArray<EnergyConsumer> mEnergyConsumers;
+
+    /** Number of ordinals for {@link EnergyConsumerType#OTHER}. */
+    private final int mNumOtherOrdinals;
 
     /**
-     * Energy snapshots from the last time each {@link MeasuredEnergySubsystem} was updated.
+     * Energy snapshots, mapping {@link EnergyConsumer#id} to energy (UJ) from the last time
+     * each {@link EnergyConsumer} was updated.
      *
-     * Note that the snapshots for different subsystems may have been taken at different times.
+     * Note that the snapshots for different ids may have been taken at different times.
+     * Note that energies for all existing ids are stored here, including each ordinal of type
+     * {@link EnergyConsumerType#OTHER} (tracking their total energy usage).
      *
-     * A snapshot is {@link #UNAVAILABLE} if the subsystem has never been updated (ie. unsupported).
+     * If an id is not present yet, it is treated as uninitialized (energy {@link #UNAVAILABLE}).
      */
-    private final long[] mMeasuredEnergySnapshots;
+    private final SparseLongArray mMeasuredEnergySnapshots;
 
     /**
-     * Constructor that initializes to the given energyArray;
-     * all subsystems not mentioned in initialEnergyArray are set to UNAVAILABLE.
+     * Energy snapshots <b>per uid</b> from the last time each {@link EnergyConsumer} of type
+     * {@link EnergyConsumerType#OTHER} was updated.
+     * It maps each OTHER {@link EnergyConsumer#id} to a SparseLongArray, which itself maps each
+     * uid to an energy (UJ). That is,
+     * mAttributionSnapshots.get(consumerId).get(uid) = energy used by uid for this consumer.
+     *
+     * If an id is not present yet, it is treated as uninitialized (i.e. each uid is unavailable).
+     * If an id is present but a uid is not present, that uid's energy is 0.
      */
-    public MeasuredEnergySnapshot(MeasuredEnergyArray initialEnergyArray) {
-        this(MeasuredEnergyArray.NUMBER_SUBSYSTEMS, initialEnergyArray);
+    private final SparseArray<SparseLongArray> mAttributionSnapshots;
+
+    /**
+     * Constructor that initializes to the given id->EnergyConsumer map, indicating which consumers
+     * exist and what their details are.
+     */
+    MeasuredEnergySnapshot(@NonNull SparseArray<EnergyConsumer> idToConsumerMap) {
+        mEnergyConsumers = idToConsumerMap;
+        mMeasuredEnergySnapshots = new SparseLongArray(mEnergyConsumers.size());
+
+        mNumOtherOrdinals = calculateNumOtherOrdinals(idToConsumerMap);
+        mAttributionSnapshots = new SparseArray<>(mNumOtherOrdinals);
     }
 
     /**
-     * Constructor (for testing) that initializes to the given energyArray and numSubsystems;
-     * all subsystems not mentioned in initialEnergyArray are set to UNAVAILABLE.
+     * Returns the number of ordinals for {@link EnergyConsumerType#OTHER}, i.e. the number of
+     * custom energy buckets supported by the device.
      */
-    @VisibleForTesting
-    MeasuredEnergySnapshot(int numSubsystems, MeasuredEnergyArray initialEnergyArray) {
-        if (initialEnergyArray.size() > numSubsystems) {
-            throw new IllegalArgumentException("Energy array contains " + initialEnergyArray.size()
-                    + " subsystems, which exceeds the maximum allowed of " + numSubsystems);
-        }
-        mMeasuredEnergySnapshots = new long[numSubsystems];
-        Arrays.fill(mMeasuredEnergySnapshots, UNAVAILABLE);
-        fillGivenSubsystems(initialEnergyArray);
+    public int getNumOtherOrdinals() {
+        return mNumOtherOrdinals;
     }
 
-    /**
-     * For the subsystems present in energyArray, overwrites mMeasuredEnergySnapshots with their
-     * energy values from energyArray.
-     */
-    private void fillGivenSubsystems(MeasuredEnergyArray energyArray) {
-        final int size = energyArray.size();
-        for (int i = 0; i < size; i++) {
-            final int subsystem = energyArray.getSubsystem(i);
-            mMeasuredEnergySnapshots[subsystem] = energyArray.getEnergy(i);
-        }
+    /** Class for returning measured energy delta data. */
+    static class MeasuredEnergyDeltaData {
+        /** The energyUJ for {@link EnergyConsumerType#DISPLAY}. */
+        public long displayEnergyUJ = UNAVAILABLE;
+
+        /** Map of {@link EnergyConsumerType#OTHER} ordinals to their total energyUJ. */
+        public @Nullable long[] otherTotalEnergyUJ = null;
+
+        /** Map of {@link EnergyConsumerType#OTHER} ordinals to their {uid->energyUJ} maps. */
+        public @Nullable SparseLongArray[] otherUidEnergiesUJ = null;
     }
 
     /**
      * Update with the some freshly measured energies and return the difference (delta)
      * between the previously stored values and the passed-in values.
      *
-     * @param energyArray measured energy array for some (possibly not all) subsystems.
+     * @param ecrs EnergyConsumerResults for some (possibly not all) {@link EnergyConsumer}s.
+     *             Consumers that are not present are ignored (they are *not* treated as 0).
      *
-     * @return a map from the updated subsystems to their corresponding energy deltas.
-     *         Subsystems not present in energyArray will not appear.
-     *         Subsystems with no difference in energy will not appear.
-     *         Returns null, if energyArray is null.
+     * @return a MeasuredEnergyDeltaData, containing maps from the updated consumers to
+     *         their corresponding energy deltas.
+     *         Fields with no interesting data (consumers not present in ecrs or with no energy
+     *         difference) will generally be left as their default values.
+     *         otherTotalEnergyUJ and otherUidEnergiesUJ are always either both null or both of
+     *         length {@link #getNumOtherOrdinals()}.
+     *         Returns null, if ecrs is null or empty.
      */
-    public @Nullable SparseLongArray updateAndGetDelta(MeasuredEnergyArray energyArray) {
-        if (energyArray == null) {
+    public @Nullable MeasuredEnergyDeltaData updateAndGetDelta(EnergyConsumerResult[] ecrs) {
+        if (ecrs == null || ecrs.length == 0) {
             return null;
         }
-        final SparseLongArray delta = new SparseLongArray();
-        final int size = energyArray.size();
-        for (int i = 0; i < size; i++) {
-            final int updatedSubsystem = energyArray.getSubsystem(i);
-            final long newEnergyUJ = energyArray.getEnergy(i);
-            final long oldEnergyUJ = mMeasuredEnergySnapshots[updatedSubsystem];
+        final MeasuredEnergyDeltaData output = new MeasuredEnergyDeltaData();
 
-            // If this is the first valid energy, there is no delta to take.
-            if (oldEnergyUJ < 0) continue;
-            final long deltaUJ = newEnergyUJ - oldEnergyUJ;
-            if (deltaUJ == 0) continue;
-            if (deltaUJ < 0) {
-                Slog.e(TAG, "For subsystem " + updatedSubsystem + ", new energy (" + newEnergyUJ
-                        + ") is less than old energy (" + oldEnergyUJ + "). Skipping. ");
+        for (final EnergyConsumerResult ecr : ecrs) {
+            // Extract the new energy data for the current consumer.
+            final int consumerId = ecr.id;
+            final long newEnergyUJ = ecr.energyUWs;
+            final EnergyConsumerAttribution[] newAttributions = ecr.attribution;
+
+            // Look up the static information about this consumer.
+            final EnergyConsumer consumer = mEnergyConsumers.get(consumerId, null);
+            if (consumer == null) {
+                Slog.e(TAG, "updateAndGetDelta given invalid consumerId " + consumerId);
                 continue;
             }
-            delta.put(updatedSubsystem, deltaUJ);
+            final int type = consumer.type;
+            final int ordinal = consumer.ordinal;
+
+            // Look up, and update, the old energy information about this consumer.
+            final long oldEnergyUJ = mMeasuredEnergySnapshots.get(consumerId, UNAVAILABLE);
+            mMeasuredEnergySnapshots.put(consumerId, newEnergyUJ);
+            final SparseLongArray otherUidEnergies
+                    = updateAndGetDeltaForTypeOther(consumer, newAttributions);
+
+            // Everything is fully done being updated. We now calculate the delta for returning.
+
+            // NB: Since sum(attribution.energyUWs)<=energyUWs we assume that if deltaEnergy==0
+            // there's no attribution either. Technically that isn't enforced at the HAL, but we
+            // can't really trust data like that anyway.
+
+            if (oldEnergyUJ < 0) continue; // Generally happens only on initialization.
+            if (newEnergyUJ == oldEnergyUJ) continue;
+            final long deltaUJ = newEnergyUJ - oldEnergyUJ;
+            if (deltaUJ < 0) {
+                Slog.e(TAG, "EnergyConsumer " + consumer.name + ": new energy (" + newEnergyUJ
+                        + ") < old energy (" + oldEnergyUJ + "). Skipping. ");
+                continue;
+            }
+
+            switch (type) {
+                case EnergyConsumerType.DISPLAY:
+                    output.displayEnergyUJ = deltaUJ;
+                    break;
+                case EnergyConsumerType.OTHER:
+                    if (output.otherTotalEnergyUJ == null) {
+                        output.otherTotalEnergyUJ = new long[getNumOtherOrdinals()];
+                        output.otherUidEnergiesUJ = new SparseLongArray[getNumOtherOrdinals()];
+                    }
+                    output.otherTotalEnergyUJ[ordinal] = deltaUJ;
+                    output.otherUidEnergiesUJ[ordinal] = otherUidEnergies;
+                    break;
+                default:
+                    Slog.w(TAG, "Ignoring consumer " + consumer.name + " of unknown type " + type);
+
+            }
         }
-
-        fillGivenSubsystems(energyArray);
-
-        return delta;
+        return output;
     }
 
     /**
-     * Check if a subsystem's measured energy is available.
-     * @param subsystem which subsystem.
-     * @return true if subsystem is available.
+     * For a consumer of type {@link EnergyConsumerType#OTHER}, updates
+     * {@link #mAttributionSnapshots} with freshly measured energies (per uid) and returns the
+     * difference (delta) between the previously stored values and the passed-in values.
+     *
+     * @param consumerInfo a consumer of type {@link EnergyConsumerType#OTHER}.
+     * @param newAttributions Record of uids and their new energyUJ values.
+     *                        Any uid not present is treated as having energy 0.
+     *                        If null or empty, all uids are treated as having energy 0.
+     * @return A map (in the sense of {@link MeasuredEnergyDeltaData#otherUidEnergiesUJ} for this
+     *         consumer) of uid -> energyDelta, with all uids that have a non-zero energyDelta.
+     *         Returns null if no delta available to calculate.
      */
-    public boolean hasSubsystem(@MeasuredEnergySubsystem int subsystem) {
-        return mMeasuredEnergySnapshots[subsystem] != UNAVAILABLE;
+    private @Nullable SparseLongArray updateAndGetDeltaForTypeOther(
+            @NonNull EnergyConsumer consumerInfo,
+            @Nullable EnergyConsumerAttribution[] newAttributions) {
+
+        if (consumerInfo.type != EnergyConsumerType.OTHER) {
+            return null;
+        }
+        if (newAttributions == null) {
+            // Treat null as empty (i.e. all uids have 0 energy).
+            newAttributions = new EnergyConsumerAttribution[0];
+        }
+
+        // SparseLongArray mapping uid -> energyUJ (for this particular consumerId)
+        SparseLongArray uidOldEnergyMap = mAttributionSnapshots.get(consumerInfo.id, null);
+
+        // If uidOldEnergyMap wasn't present, each uid was UNAVAILABLE, so update data and return.
+        if (uidOldEnergyMap == null) {
+            uidOldEnergyMap = new SparseLongArray(newAttributions.length);
+            mAttributionSnapshots.put(consumerInfo.id, uidOldEnergyMap);
+            for (EnergyConsumerAttribution newAttribution : newAttributions) {
+                uidOldEnergyMap.put(newAttribution.uid, newAttribution.energyUWs);
+            }
+            return null;
+        }
+
+        // Map uid -> energyDelta. No initial capacity since many deltas might be 0.
+        final SparseLongArray uidEnergyDeltas = new SparseLongArray();
+
+        for (EnergyConsumerAttribution newAttribution : newAttributions) {
+            final int uid = newAttribution.uid;
+            final long newEnergyUJ = newAttribution.energyUWs;
+            // uidOldEnergyMap was present. So any particular uid that wasn't present, had 0 energy.
+            final long oldEnergyUJ = uidOldEnergyMap.get(uid, 0L);
+            uidOldEnergyMap.put(uid, newEnergyUJ);
+
+            // Everything is fully done being updated. We now calculate the delta for returning.
+            if (oldEnergyUJ < 0) continue;
+            if (newEnergyUJ == oldEnergyUJ) continue;
+            final long deltaUJ = newEnergyUJ - oldEnergyUJ;
+            if (deltaUJ < 0) {
+                Slog.e(TAG, "EnergyConsumer " + consumerInfo.name + ": new energy (" + newEnergyUJ
+                        + ") but old energy (" + oldEnergyUJ + "). Skipping. ");
+                continue;
+            }
+            uidEnergyDeltas.put(uid, deltaUJ);
+        }
+        return uidEnergyDeltas;
     }
 
     /** Dump debug data. */
     public void dump(PrintWriter pw) {
-        pw.println("Measured energy snapshot (microjoules):");
-        pw.print("   ");
-        for (int i = 0; i < MeasuredEnergyArray.NUMBER_SUBSYSTEMS; i++) {
-            final long energyUJ = mMeasuredEnergySnapshots[i];
-            if (energyUJ == UNAVAILABLE) continue;
-            pw.print(MeasuredEnergyArray.SUBSYSTEM_NAMES[i]);
-            pw.print(" : ");
-            pw.print(energyUJ);
-            if (i != MeasuredEnergyArray.NUMBER_SUBSYSTEMS - 1) {
-                pw.print(", ");
-            }
+        pw.println("Measured energy snapshot");
+        pw.println("List of EnergyConsumers:");
+        for (int i = 0; i < mEnergyConsumers.size(); i++) {
+            final int id = mEnergyConsumers.keyAt(i);
+            final EnergyConsumer consumer = mEnergyConsumers.valueAt(i);
+            pw.println(String.format("    Consumer %d is {id=%d, ordinal=%d, type=%d, name=%s}", id,
+                    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);
+            pw.println(String.format("    Consumer %d has energy %d uJ}", id, energyUJ));
+        }
+        pw.println("List of the " + mNumOtherOrdinals + " OTHER EnergyConsumers:");
+        pw.println("    " + mAttributionSnapshots);
         pw.println();
     }
+
+    /** Determines the number of ordinals for {@link EnergyConsumerType#OTHER}. */
+    private static int calculateNumOtherOrdinals(SparseArray<EnergyConsumer> idToConsumer) {
+        if (idToConsumer == null) return 0;
+        int numOtherOrdinals = 0;
+        final int size = idToConsumer.size();
+        for (int idx = 0; idx < size; idx++) {
+            final EnergyConsumer consumer = idToConsumer.valueAt(idx);
+            if (consumer.type == EnergyConsumerType.OTHER) numOtherOrdinals++;
+        }
+        return numOtherOrdinals;
+    }
 }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 10fe1e1..c92abd4 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -3131,11 +3131,6 @@
                 return AppOpsManager.MODE_ERRORED;
             }
             final Op op = getOpLocked(ops, code, uid, true);
-            if (isOpRestrictedLocked(uid, code, packageName, bypass)) {
-                scheduleOpNotedIfNeededLocked(code, uid, packageName, flags,
-                        AppOpsManager.MODE_IGNORED);
-                return AppOpsManager.MODE_IGNORED;
-            }
             final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
             if (attributedOp.isRunning()) {
                 Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
@@ -3145,6 +3140,12 @@
 
             final int switchCode = AppOpsManager.opToSwitch(code);
             final UidState uidState = ops.uidState;
+            if (isOpRestrictedLocked(uid, code, packageName, bypass)) {
+                attributedOp.rejected(uidState.state, flags);
+                scheduleOpNotedIfNeededLocked(code, uid, packageName, flags,
+                        AppOpsManager.MODE_IGNORED);
+                return AppOpsManager.MODE_IGNORED;
+            }
             // If there is a non-default per UID policy (we set UID op mode only if
             // non-default) it takes over, otherwise use the per package policy.
             if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index c956043..f37fbf5 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -257,11 +257,15 @@
                             mUserId,
                             mOpPackageName,
                             mOperationId);
+                    mState = STATE_AUTH_STARTED;
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Remote exception", e);
                 }
+            } else {
+                // The UI was already showing :)
+                mState = STATE_AUTH_STARTED_UI_SHOWING;
             }
-            mState = STATE_AUTH_STARTED;
+
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 81e90df..4925ce0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -435,13 +435,7 @@
         mScheduler = new BiometricScheduler(tag, null /* gestureAvailabilityDispatcher */);
         mLockoutCache = new LockoutCache();
         mAuthenticatorIds = new HashMap<>();
-        mLazySession = () -> {
-            if (mTestHalEnabled) {
-                return new TestSession(mCurrentSession.mHalSessionCallback);
-            } else {
-                return mCurrentSession != null ? mCurrentSession.mSession : null;
-            }
-        };
+        mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
     }
 
     @NonNull HalClientMonitor.LazyDaemon<ISession> getLazySession() {
@@ -497,6 +491,10 @@
 
     void setTestHalEnabled(boolean enabled) {
         Slog.w(mTag, "setTestHalEnabled: " + enabled);
+        if (enabled != mTestHalEnabled) {
+            // The framework should retrieve a new session from the HAL.
+            mCurrentSession = null;
+        }
         mTestHalEnabled = enabled;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
index a38da3a..ff65c93 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
@@ -17,12 +17,14 @@
 package com.android.server.biometrics.sensors.face.aidl;
 
 import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.face.Error;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
 import android.hardware.biometrics.face.ISessionCallback;
 import android.hardware.biometrics.face.SensorProps;
 import android.hardware.common.NativeHandle;
 import android.hardware.keymaster.HardwareAuthToken;
+import android.os.RemoteException;
 import android.util.Slog;
 
 /**
@@ -38,70 +40,96 @@
 
     @Override
     public ISession createSession(int sensorId, int userId, ISessionCallback cb) {
+        Slog.w(TAG, "createSession, sensorId: " + sensorId + " userId: " + userId);
+
         return new ISession.Stub() {
             @Override
-            public void generateChallenge(int cookie, int timeoutSec) {
+            public void generateChallenge(int cookie, int timeoutSec) throws RemoteException {
                 Slog.w(TAG, "generateChallenge, cookie: " + cookie);
+                cb.onChallengeGenerated(0L);
             }
 
             @Override
-            public void revokeChallenge(int cookie, long challenge) {
+            public void revokeChallenge(int cookie, long challenge) throws RemoteException {
                 Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie);
+                cb.onChallengeRevoked(challenge);
             }
 
             @Override
             public ICancellationSignal enroll(int cookie, HardwareAuthToken hat,
                     byte enrollmentType, byte[] features, NativeHandle previewSurface) {
                 Slog.w(TAG, "enroll, cookie: " + cookie);
-                return null;
+                return new ICancellationSignal.Stub() {
+                    @Override
+                    public void cancel() throws RemoteException {
+                        cb.onError(Error.CANCELED, 0 /* vendorCode */);
+                    }
+                };
             }
 
             @Override
             public ICancellationSignal authenticate(int cookie, long operationId) {
                 Slog.w(TAG, "authenticate, cookie: " + cookie);
-                return null;
+                return new ICancellationSignal.Stub() {
+                    @Override
+                    public void cancel() throws RemoteException {
+                        cb.onError(Error.CANCELED, 0 /* vendorCode */);
+                    }
+                };
             }
 
             @Override
             public ICancellationSignal detectInteraction(int cookie) {
                 Slog.w(TAG, "detectInteraction, cookie: " + cookie);
-                return null;
+                return new ICancellationSignal.Stub() {
+                    @Override
+                    public void cancel() throws RemoteException {
+                        cb.onError(Error.CANCELED, 0 /* vendorCode */);
+                    }
+                };
             }
 
             @Override
-            public void enumerateEnrollments(int cookie) {
+            public void enumerateEnrollments(int cookie) throws RemoteException {
                 Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie);
+                cb.onEnrollmentsEnumerated(new int[0]);
             }
 
             @Override
-            public void removeEnrollments(int cookie, int[] enrollmentIds) {
+            public void removeEnrollments(int cookie, int[] enrollmentIds) throws RemoteException {
                 Slog.w(TAG, "removeEnrollments, cookie: " + cookie);
+                cb.onEnrollmentsRemoved(enrollmentIds);
             }
 
             @Override
-            public void getFeatures(int cookie, int enrollmentId) {
+            public void getFeatures(int cookie, int enrollmentId) throws RemoteException {
                 Slog.w(TAG, "getFeatures, cookie: " + cookie);
+                cb.onFeaturesRetrieved(new byte[0], enrollmentId);
             }
 
             @Override
             public void setFeature(int cookie, HardwareAuthToken hat, int enrollmentId,
-                    byte feature, boolean enabled) {
+                    byte feature, boolean enabled) throws RemoteException {
                 Slog.w(TAG, "setFeature, cookie: " + cookie);
+                cb.onFeatureSet(enrollmentId, feature);
             }
 
             @Override
-            public void getAuthenticatorId(int cookie) {
+            public void getAuthenticatorId(int cookie) throws RemoteException {
                 Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie);
+                cb.onAuthenticatorIdRetrieved(0L);
             }
 
             @Override
-            public void invalidateAuthenticatorId(int cookie) {
+            public void invalidateAuthenticatorId(int cookie) throws RemoteException {
                 Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie);
+                cb.onAuthenticatorIdInvalidated(0L);
             }
 
             @Override
-            public void resetLockout(int cookie, HardwareAuthToken hat) {
+            public void resetLockout(int cookie, HardwareAuthToken hat) throws RemoteException {
                 Slog.w(TAG, "resetLockout, cookie: " + cookie);
+                cb.onLockoutCleared();
             }
         };
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestSession.java
deleted file mode 100644
index 23e6988..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestSession.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors.face.aidl;
-
-import android.annotation.NonNull;
-import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.face.Error;
-import android.hardware.biometrics.face.ISession;
-import android.hardware.common.NativeHandle;
-import android.hardware.keymaster.HardwareAuthToken;
-import android.os.Binder;
-import android.os.IBinder;
-import android.util.Slog;
-
-/**
- * Test session that provides mostly no-ops.
- */
-public class TestSession extends ISession.Stub {
-    private static final String TAG = "FaceTestSession";
-
-    @NonNull
-    private final Sensor.HalSessionCallback mHalSessionCallback;
-
-    TestSession(@NonNull Sensor.HalSessionCallback halSessionCallback) {
-        mHalSessionCallback = halSessionCallback;
-    }
-
-    @Override
-    public void generateChallenge(int cookie, int timeoutSec) {
-        mHalSessionCallback.onChallengeGenerated(0 /* challenge */);
-    }
-
-    @Override
-    public void revokeChallenge(int cookie, long challenge) {
-        mHalSessionCallback.onChallengeRevoked(challenge);
-    }
-
-    @Override
-    public ICancellationSignal enroll(int cookie, HardwareAuthToken hat, byte enrollmentType,
-            byte[] features, NativeHandle previewSurface) {
-        return null;
-    }
-
-    @Override
-    public ICancellationSignal authenticate(int cookie, long operationId) {
-        return new ICancellationSignal() {
-            @Override
-            public void cancel() {
-                mHalSessionCallback.onError(Error.CANCELED, 0 /* vendorCode */);
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return new Binder();
-            }
-        };
-    }
-
-    @Override
-    public ICancellationSignal detectInteraction(int cookie) {
-        return null;
-    }
-
-    @Override
-    public void enumerateEnrollments(int cookie) {
-
-    }
-
-    @Override
-    public void removeEnrollments(int cookie, int[] enrollmentIds) {
-
-    }
-
-    @Override
-    public void getFeatures(int cookie, int enrollmentId) {
-
-    }
-
-    @Override
-    public void setFeature(int cookie, HardwareAuthToken hat, int enrollmentId, byte feature,
-            boolean enabled) {
-
-    }
-
-    @Override
-    public void getAuthenticatorId(int cookie) {
-        Slog.d(TAG, "getAuthenticatorId");
-        // Immediately return a value so the framework can continue with subsequent requests.
-        mHalSessionCallback.onAuthenticatorIdRetrieved(0);
-    }
-
-    @Override
-    public void invalidateAuthenticatorId(int cookie) {
-        Slog.d(TAG, "invalidateAuthenticatorId");
-        // Immediately return a value so the framework can continue with subsequent requests.
-        mHalSessionCallback.onAuthenticatorIdInvalidated(0);
-    }
-
-    @Override
-    public void resetLockout(int cookie, HardwareAuthToken hat) {
-        mHalSessionCallback.onLockoutCleared();
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
index 00ca802..13bd1c2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
@@ -98,8 +98,11 @@
     }
 
     @Override
-    public int enumerate() {
+    public int enumerate() throws RemoteException {
         Slog.w(TAG, "enumerate");
+        if (mCallback != null) {
+            mCallback.onEnumerate(0 /* deviceId */, new ArrayList<>(), 0 /* userId */);
+        }
         return 0;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 73b59cf..c83c0fb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -415,13 +415,7 @@
         mScheduler = new BiometricScheduler(tag, gestureAvailabilityDispatcher);
         mLockoutCache = new LockoutCache();
         mAuthenticatorIds = new HashMap<>();
-        mLazySession = () -> {
-            if (mTestHalEnabled) {
-                return new TestSession(mCurrentSession.mHalSessionCallback);
-            } else {
-                return mCurrentSession != null ? mCurrentSession.mSession : null;
-            }
-        };
+        mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
     }
 
     @NonNull HalClientMonitor.LazyDaemon<ISession> getLazySession() {
@@ -476,7 +470,11 @@
     }
 
     void setTestHalEnabled(boolean enabled) {
-        Slog.w(mTag, "setTestHalEnabled, enabled");
+        Slog.w(mTag, "setTestHalEnabled: " + enabled);
+        if (enabled != mTestHalEnabled) {
+            // The framework should retrieve a new session from the HAL.
+            mCurrentSession = null;
+        }
         mTestHalEnabled = enabled;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
index 66b68ee..8ed24b6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
@@ -17,11 +17,13 @@
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
 import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.fingerprint.Error;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.ISessionCallback;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.keymaster.HardwareAuthToken;
+import android.os.RemoteException;
 import android.util.Slog;
 
 /**
@@ -38,58 +40,82 @@
 
     @Override
     public ISession createSession(int sensorId, int userId, ISessionCallback cb) {
+        Slog.w(TAG, "createSession, sensorId: " + sensorId + " userId: " + userId);
+
         return new ISession.Stub() {
             @Override
-            public void generateChallenge(int cookie, int timeoutSec) {
+            public void generateChallenge(int cookie, int timeoutSec) throws RemoteException {
                 Slog.w(TAG, "generateChallenge, cookie: " + cookie);
+                cb.onChallengeGenerated(0L);
             }
 
             @Override
-            public void revokeChallenge(int cookie, long challenge) {
+            public void revokeChallenge(int cookie, long challenge) throws RemoteException {
                 Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie);
+                cb.onChallengeRevoked(challenge);
             }
 
             @Override
             public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) {
                 Slog.w(TAG, "enroll, cookie: " + cookie);
-                return null;
+                return new ICancellationSignal.Stub() {
+                    @Override
+                    public void cancel() throws RemoteException {
+                        cb.onError(Error.CANCELED, 0 /* vendorCode */);
+                    }
+                };
             }
 
             @Override
             public ICancellationSignal authenticate(int cookie, long operationId) {
                 Slog.w(TAG, "authenticate, cookie: " + cookie);
-                return null;
+                return new ICancellationSignal.Stub() {
+                    @Override
+                    public void cancel() throws RemoteException {
+                        cb.onError(Error.CANCELED, 0 /* vendorCode */);
+                    }
+                };
             }
 
             @Override
             public ICancellationSignal detectInteraction(int cookie) {
                 Slog.w(TAG, "detectInteraction, cookie: " + cookie);
-                return null;
+                return new ICancellationSignal.Stub() {
+                    @Override
+                    public void cancel() throws RemoteException {
+                        cb.onError(Error.CANCELED, 0 /* vendorCode */);
+                    }
+                };
             }
 
             @Override
-            public void enumerateEnrollments(int cookie) {
+            public void enumerateEnrollments(int cookie) throws RemoteException {
                 Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie);
+                cb.onEnrollmentsEnumerated(new int[0]);
             }
 
             @Override
-            public void removeEnrollments(int cookie, int[] enrollmentIds) {
+            public void removeEnrollments(int cookie, int[] enrollmentIds) throws RemoteException {
                 Slog.w(TAG, "removeEnrollments, cookie: " + cookie);
+                cb.onEnrollmentsRemoved(enrollmentIds);
             }
 
             @Override
-            public void getAuthenticatorId(int cookie) {
+            public void getAuthenticatorId(int cookie) throws RemoteException {
                 Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie);
+                cb.onAuthenticatorIdRetrieved(0L);
             }
 
             @Override
-            public void invalidateAuthenticatorId(int cookie) {
+            public void invalidateAuthenticatorId(int cookie) throws RemoteException {
                 Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie);
+                cb.onAuthenticatorIdInvalidated(0L);
             }
 
             @Override
-            public void resetLockout(int cookie, HardwareAuthToken hat) {
+            public void resetLockout(int cookie, HardwareAuthToken hat) throws RemoteException {
                 Slog.w(TAG, "resetLockout, cookie: " + cookie);
+                cb.onLockoutCleared();
             }
 
             @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java
deleted file mode 100644
index ac4f665..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors.fingerprint.aidl;
-
-import android.annotation.NonNull;
-import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.face.Error;
-import android.hardware.biometrics.fingerprint.ISession;
-import android.hardware.keymaster.HardwareAuthToken;
-import android.os.Binder;
-import android.os.IBinder;
-import android.util.Slog;
-
-/**
- * Test session that provides mostly no-ops.
- */
-class TestSession extends ISession.Stub {
-
-    private static final String TAG = "FingerprintTestSession";
-
-    @NonNull private final Sensor.HalSessionCallback mHalSessionCallback;
-
-    TestSession(@NonNull Sensor.HalSessionCallback halSessionCallback) {
-        mHalSessionCallback = halSessionCallback;
-    }
-
-    @Override
-    public void generateChallenge(int cookie, int timeoutSec) {
-        mHalSessionCallback.onChallengeGenerated(0 /* challenge */);
-    }
-
-    @Override
-    public void revokeChallenge(int cookie, long challenge) {
-        mHalSessionCallback.onChallengeRevoked(challenge);
-    }
-
-    @Override
-    public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) {
-        return null;
-    }
-
-    @Override
-    public ICancellationSignal authenticate(int cookie, long operationId) {
-        return new ICancellationSignal() {
-            @Override
-            public void cancel() {
-                mHalSessionCallback.onError(Error.CANCELED, 0 /* vendorCode */);
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return new Binder();
-            }
-        };
-    }
-
-    @Override
-    public ICancellationSignal detectInteraction(int cookie) {
-        return null;
-    }
-
-    @Override
-    public void enumerateEnrollments(int cookie) {
-        Slog.d(TAG, "enumerate");
-    }
-
-    @Override
-    public void removeEnrollments(int cookie, int[] enrollmentIds) {
-        Slog.d(TAG, "remove");
-    }
-
-    @Override
-    public void getAuthenticatorId(int cookie) {
-        Slog.d(TAG, "getAuthenticatorId");
-        // Immediately return a value so the framework can continue with subsequent requests.
-        mHalSessionCallback.onAuthenticatorIdRetrieved(0);
-    }
-
-    @Override
-    public void invalidateAuthenticatorId(int cookie) {
-        Slog.d(TAG, "invalidateAuthenticatorId");
-        // Immediately return a value so the framework can continue with subsequent requests.
-        mHalSessionCallback.onAuthenticatorIdInvalidated(0);
-    }
-
-    @Override
-    public void resetLockout(int cookie, HardwareAuthToken hat) {
-        mHalSessionCallback.onLockoutCleared();
-    }
-
-    @Override
-    public void onPointerDown(int pointerId, int x, int y, float minor, float major) {
-
-    }
-
-    @Override
-    public void onPointerUp(int pointerId) {
-
-    }
-
-    @Override
-    public void onUiReady() {
-
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
index 57447f3..14fdb50 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
@@ -83,8 +83,12 @@
     }
 
     @Override
-    public int enumerate() {
+    public int enumerate() throws RemoteException {
         Slog.w(TAG, "Enumerate");
+        if (mCallback != null) {
+            mCallback.onEnumerate(0 /* deviceId */, 0 /* fingerId */, 0 /* groupId */,
+                    0 /* remaining */);
+        }
         return 0;
     }
 
diff --git a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
index 87b4c16..7ef315c 100644
--- a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
+++ b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
@@ -27,7 +27,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.telephony.data.EpsBearerQosSessionAttributes;
-import android.util.Slog;
+import android.util.Log;
 
 import com.android.internal.util.CollectionUtils;
 import com.android.server.ConnectivityService;
@@ -260,18 +260,18 @@
     }
 
     private static void log(final String msg) {
-        Slog.d(TAG, msg);
+        Log.d(TAG, msg);
     }
 
     private static void logw(final String msg) {
-        Slog.w(TAG, msg);
+        Log.w(TAG, msg);
     }
 
     private static void loge(final String msg) {
-        Slog.e(TAG, msg);
+        Log.e(TAG, msg);
     }
 
     private static void logwtf(final String msg) {
-        Slog.wtf(TAG, msg);
+        Log.wtf(TAG, msg);
     }
 }
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java
index e496d77..e693bcc 100644
--- a/services/core/java/com/android/server/devicestate/DeviceState.java
+++ b/services/core/java/com/android/server/devicestate/DeviceState.java
@@ -16,9 +16,14 @@
 
 package com.android.server.devicestate;
 
+import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
+
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.Objects;
 
 /**
@@ -35,24 +40,25 @@
  */
 public final class DeviceState {
     /** Unique identifier for the device state. */
-    @IntRange(from = 0)
+    @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE)
     private final int mIdentifier;
 
     /** String description of the device state. */
     @NonNull
     private final String mName;
 
-    public DeviceState(@IntRange(from = 0) int identifier,
+    public DeviceState(
+            @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier,
             @NonNull String name) {
-        if (identifier < 0) {
-            throw new IllegalArgumentException("Identifier must be greater than or equal to zero.");
-        }
+        Preconditions.checkArgumentInRange(identifier, MINIMUM_DEVICE_STATE, MAXIMUM_DEVICE_STATE,
+                "identifier");
+
         mIdentifier = identifier;
         mName = name;
     }
 
     /** Returns the unique identifier for the device state. */
-    @IntRange(from = 0)
+    @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE)
     public int getIdentifier() {
         return mIdentifier;
     }
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 984a176..b3a6f26 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -17,6 +17,8 @@
 package com.android.server.devicestate;
 
 import static android.Manifest.permission.CONTROL_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES;
 
 import android.annotation.IntRange;
@@ -89,7 +91,7 @@
     // the current state after the initial callback from the DeviceStateProvider.
     @GuardedBy("mLock")
     @NonNull
-    private DeviceState mCommittedState = new DeviceState(0, "UNSET");
+    private DeviceState mCommittedState = new DeviceState(MINIMUM_DEVICE_STATE, "UNSET");
     // The device state that is currently awaiting callback from the policy to be committed.
     @GuardedBy("mLock")
     @NonNull
@@ -598,8 +600,9 @@
         }
 
         @Override
-        public void onStateChanged(@IntRange(from = 0) int identifier) {
-            if (identifier < 0) {
+        public void onStateChanged(
+                @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier) {
+            if (identifier < MINIMUM_DEVICE_STATE || identifier > MAXIMUM_DEVICE_STATE) {
                 throw new IllegalArgumentException("Invalid identifier: " + identifier);
             }
 
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index 2d4377f..109bf63 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -16,6 +16,9 @@
 
 package com.android.server.devicestate;
 
+import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
+
 import android.annotation.IntRange;
 
 /**
@@ -65,8 +68,10 @@
          *
          * @param identifier the identifier of the new device state.
          *
-         * @throws IllegalArgumentException if the state is less than 0.
+         * @throws IllegalArgumentException if the state is less than {@link MINIMUM_DEVICE_STATE}
+         * or greater than {@link MAXIMUM_DEVICE_STATE}.
          */
-        void onStateChanged(@IntRange(from = 0) int identifier);
+        void onStateChanged(
+                @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier);
     }
 }
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index fa063b2..225da7a 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -42,8 +42,8 @@
 import android.util.Slog;
 import android.util.TimeUtils;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.EventLogTags;
 import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 4832e46..a62f67a 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -28,8 +28,8 @@
 import android.util.Slog;
 import android.util.Spline;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.util.Preconditions;
 import com.android.server.display.utils.Plog;
 
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
new file mode 100644
index 0000000..d4556ed
--- /dev/null
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.DisplayAddress;
+
+import com.android.server.display.layout.Layout;
+
+import java.util.Arrays;
+
+/**
+ * Mapping from device states into {@link Layout}s. This allows us to map device
+ * states into specific layouts for the connected displays; particularly useful for
+ * foldable and multi-display devices where the default display and which displays are ON can
+ * change depending on the state of the device.
+ */
+class DeviceStateToLayoutMap {
+    private static final String TAG = "DeviceStateToLayoutMap";
+
+    public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE;
+
+    // TODO - b/168208162 - Remove these when we check in static definitions for layouts
+    public static final int STATE_FOLDED = 100;
+    public static final int STATE_UNFOLDED = 101;
+
+    private final SparseArray<Layout> mLayoutMap = new SparseArray<>();
+
+    DeviceStateToLayoutMap(Context context) {
+        mLayoutMap.append(STATE_DEFAULT, new Layout());
+        loadFoldedDisplayConfig(context);
+    }
+
+    public void dumpLocked(IndentingPrintWriter ipw) {
+        ipw.println("DeviceStateToLayoutMap:");
+        ipw.increaseIndent();
+
+        ipw.println("Registered Layouts:");
+        for (int i = 0; i < mLayoutMap.size(); i++) {
+            ipw.println("state(" + mLayoutMap.keyAt(i) + "): " + mLayoutMap.valueAt(i));
+        }
+    }
+
+    Layout get(int state) {
+        Layout layout = mLayoutMap.get(state);
+        if (layout == null) {
+            layout = mLayoutMap.get(STATE_DEFAULT);
+        }
+        return layout;
+    }
+
+    private Layout create(int state) {
+        if (mLayoutMap.contains(state)) {
+            Slog.e(TAG, "Attempted to create a second layout for state " + state);
+            return null;
+        }
+
+        final Layout layout = new Layout();
+        mLayoutMap.append(state, layout);
+        return layout;
+    }
+
+    private void loadFoldedDisplayConfig(Context context) {
+        final String[] strDisplayIds = context.getResources().getStringArray(
+                com.android.internal.R.array.config_internalFoldedPhysicalDisplayIds);
+
+        if (strDisplayIds.length != 2 || TextUtils.isEmpty(strDisplayIds[0])
+                || TextUtils.isEmpty(strDisplayIds[1])) {
+            Slog.w(TAG, "Folded display configuration invalid: [" + Arrays.toString(strDisplayIds)
+                    + "]");
+            return;
+        }
+
+        final long[] displayIds;
+        try {
+            displayIds = new long[] {
+                Long.parseLong(strDisplayIds[0]),
+                Long.parseLong(strDisplayIds[1])
+            };
+        } catch (NumberFormatException nfe) {
+            Slog.w(TAG, "Folded display config non numerical: " + Arrays.toString(strDisplayIds));
+            return;
+        }
+
+        final int[] foldedDeviceStates = context.getResources().getIntArray(
+                com.android.internal.R.array.config_foldedDeviceStates);
+        // Only add folded states if folded state config is not empty
+        if (foldedDeviceStates.length == 0) {
+            return;
+        }
+
+        // Create the folded state layout
+        final Layout foldedLayout = create(STATE_FOLDED);
+        foldedLayout.createDisplayLocked(
+                DisplayAddress.fromPhysicalDisplayId(displayIds[0]), true /*isDefault*/);
+
+        // Create the unfolded state layout
+        final Layout unfoldedLayout = create(STATE_UNFOLDED);
+        unfoldedLayout.createDisplayLocked(
+                DisplayAddress.fromPhysicalDisplayId(displayIds[1]), true /*isDefault*/);
+    }
+}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index d687221..1b25427 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -23,8 +23,8 @@
 import android.util.Slog;
 import android.view.DisplayAddress;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.R;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.server.display.config.DisplayConfiguration;
 import com.android.server.display.config.DisplayQuirks;
 import com.android.server.display.config.HbmTiming;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index bf16a6d..501533d 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -26,7 +26,7 @@
 import android.view.RoundedCorners;
 import android.view.Surface;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 
 import java.util.Arrays;
 import java.util.Objects;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
index 5c0fceb..57f4486 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.util.Slog;
 import android.view.Display;
+import android.view.DisplayAddress;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.display.DisplayManagerService.SyncRoot;
@@ -112,12 +113,11 @@
         }
     }
 
-    public DisplayDevice getByIdLocked(@NonNull String uniqueId) {
-        final int count = mDisplayDevices.size();
-        for (int i = 0; i < count; i++) {
-            final DisplayDevice d = mDisplayDevices.get(i);
-            if (uniqueId.equals(d.getUniqueId())) {
-                return d;
+    public DisplayDevice getByAddressLocked(@NonNull DisplayAddress address) {
+        for (int i = mDisplayDevices.size() - 1; i >= 0; i--) {
+            final DisplayDevice device = mDisplayDevices.get(i);
+            if (address.equals(device.getDisplayDeviceInfoLocked().address)) {
+                return device;
             }
         }
         return null;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 01fee56..6a22941 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -106,9 +106,9 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.AnimationThread;
@@ -1188,6 +1188,7 @@
             final LogicalDisplay display = mLogicalDisplayMapper.getLocked(device);
             final int state;
             final int displayId = display.getDisplayIdLocked();
+
             if (display.isEnabled()) {
                 state = mDisplayStates.get(displayId);
             } else {
@@ -1564,12 +1565,6 @@
         }
     }
 
-    void setFoldOverride(Boolean isFolded) {
-        synchronized (mSyncRoot) {
-            mLogicalDisplayMapper.setFoldOverrideLocked(isFolded);
-        }
-    }
-
     private void clearViewportsLocked() {
         mViewports.clear();
     }
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index aaea15a..d1d0496 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -60,8 +60,6 @@
                 return setDisplayModeDirectorLoggingEnabled(false);
             case "dwb-set-cct":
                 return setAmbientColorTemperatureOverride();
-            case "set-fold":
-                return setFoldOverride();
             default:
                 return handleDefaultCommands(cmd);
         }
@@ -92,8 +90,6 @@
         pw.println("    Disable display mode director logging.");
         pw.println("  dwb-set-cct CCT");
         pw.println("    Sets the ambient color temperature override to CCT (use -1 to disable).");
-        pw.println("  set-fold [fold|unfold|reset]");
-        pw.println("    Simulates the 'fold' state of a device. 'reset' for default behavior.");
         pw.println();
         Intent.printIntentArgsHelp(pw , "");
     }
@@ -165,26 +161,4 @@
         mService.setAmbientColorTemperatureOverride(cct);
         return 0;
     }
-
-    private int setFoldOverride() {
-        String state = getNextArg();
-        if (state == null) {
-            getErrPrintWriter().println("Error: no parameter specified for set-fold");
-            return 1;
-        }
-        final Boolean isFolded;
-        if ("fold".equals(state)) {
-            isFolded = true;
-        } else if ("unfold".equals(state)) {
-            isFolded = false;
-        } else if ("reset".equals(state)) {
-            isFolded = null;
-        } else {
-            getErrPrintWriter().println("Error: Invalid fold state request: " + state);
-            return 1;
-        }
-
-        mService.setFoldOverride(isFolded);
-        return 0;
-    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2df33652..9320f50 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -53,8 +53,8 @@
 import android.util.TimeUtils;
 import android.view.Display;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.LocalServices;
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 173adce..1d20d87 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -26,7 +26,7 @@
 import android.view.Choreographer;
 import android.view.Display;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 
 import java.io.PrintWriter;
 
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 73ebb2e..3b66236 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -38,7 +38,7 @@
 import android.view.RoundedCorners;
 import android.view.SurfaceControl;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 80781d2..20b133c 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -32,6 +32,7 @@
 import com.android.server.wm.utils.InsetUtils;
 
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.Arrays;
 import java.util.Objects;
 
@@ -718,6 +719,7 @@
     public void dumpLocked(PrintWriter pw) {
         pw.println("mDisplayId=" + mDisplayId);
         pw.println("mLayerStack=" + mLayerStack);
+        pw.println("mIsEnabled=" + mIsEnabled);
         pw.println("mHasContent=" + mHasContent);
         pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}");
         pw.println("mRequestedColorMode=" + mRequestedColorMode);
@@ -731,4 +733,11 @@
         pw.println("mFrameRateOverrides=" + Arrays.toString(mFrameRateOverrides));
         pw.println("mPendingFrameRateOverrideUids=" + mPendingFrameRateOverrideUids);
     }
+
+    @Override
+    public String toString() {
+        StringWriter sw = new StringWriter();
+        dumpLocked(new PrintWriter(sw));
+        return sw.toString();
+    }
 }
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 16c4b26..a054db5 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -27,10 +27,10 @@
 import android.view.DisplayEventReceiver;
 import android.view.DisplayInfo;
 
+import com.android.server.display.layout.Layout;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
-import java.util.Objects;
 import java.util.function.Consumer;
 
 /**
@@ -75,40 +75,25 @@
     private final boolean mSingleDisplayDemoMode;
 
     /**
-     * Physical Display ID of the DisplayDevice to associate with the default LogicalDisplay
-     * when {@link mIsFolded} is set to {@code true}.
-     */
-    private String mDisplayIdToUseWhenFolded;
-
-    /**
-     * Physical Display ID of the DisplayDevice to associate with the default LogicalDisplay
-     * when {@link mIsFolded} is set to {@code false}.
-     */
-    private String mDisplayIdToUseWhenUnfolded;
-
-    /** Overrides the folded state of the device. For use with ADB commands. */
-    private Boolean mIsFoldedOverride;
-
-    /** Saves the last device fold state. */
-    private boolean mIsFolded;
-
-    /**
      * List of all logical displays indexed by logical display id.
      * Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache.
      * TODO: multi-display - Move the aforementioned comment?
      */
     private final SparseArray<LogicalDisplay> mLogicalDisplays =
             new SparseArray<LogicalDisplay>();
-    private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1;
-    private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
 
     /** A mapping from logical display id to display group. */
     private final SparseArray<DisplayGroup> mDisplayIdToGroupMap = new SparseArray<>();
 
     private final DisplayDeviceRepository mDisplayDeviceRepo;
+    private final DeviceStateToLayoutMap mDeviceStateToLayoutMap;
     private final Listener mListener;
     private final int[] mFoldedDeviceStates;
 
+    private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+    private Layout mCurrentLayout = null;
+    private boolean mIsFolded = false;
+
     LogicalDisplayMapper(Context context, DisplayDeviceRepository repo, Listener listener) {
         mDisplayDeviceRepo = repo;
         mListener = listener;
@@ -118,7 +103,7 @@
         mFoldedDeviceStates = context.getResources().getIntArray(
                 com.android.internal.R.array.config_foldedDeviceStates);
 
-        loadFoldedDisplayConfig(context);
+        mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(context);
     }
 
     @Override
@@ -213,13 +198,12 @@
         ipw.increaseIndent();
 
         ipw.println("mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
-        ipw.println("mNextNonDefaultDisplayId=" + mNextNonDefaultDisplayId);
+
+        ipw.println("mCurrentLayout=" + mCurrentLayout);
 
         final int logicalDisplayCount = mLogicalDisplays.size();
         ipw.println();
         ipw.println("Logical Displays: size=" + logicalDisplayCount);
-
-
         for (int i = 0; i < logicalDisplayCount; i++) {
             int displayId = mLogicalDisplays.keyAt(i);
             LogicalDisplay display = mLogicalDisplays.valueAt(i);
@@ -229,6 +213,7 @@
             ipw.decreaseIndent();
             ipw.println();
         }
+        mDeviceStateToLayoutMap.dumpLocked(ipw);
     }
 
     void setDeviceStateLocked(int state) {
@@ -244,79 +229,55 @@
 
     void setDeviceFoldedLocked(boolean isFolded) {
         mIsFolded = isFolded;
-        if (mIsFoldedOverride != null) {
-            isFolded = mIsFoldedOverride.booleanValue();
+
+        // Until we have fully functioning state mapping, use hardcoded states based on isFolded
+        final int state = mIsFolded ? DeviceStateToLayoutMap.STATE_FOLDED
+                : DeviceStateToLayoutMap.STATE_UNFOLDED;
+
+        if (DEBUG) {
+            Slog.d(TAG, "New device state: " + state);
         }
 
-        if (mDisplayIdToUseWhenFolded == null || mDisplayIdToUseWhenUnfolded == null
-                || mLogicalDisplays.size() < 2) {
-            // Do nothing if this behavior is disabled or there are less than two displays.
+        final Layout layout = mDeviceStateToLayoutMap.get(state);
+        if (layout == null) {
+            return;
+        }
+        final Layout.Display displayLayout = layout.getById(Display.DEFAULT_DISPLAY);
+        if (displayLayout == null) {
+            return;
+        }
+        final DisplayDevice newDefaultDevice =
+                mDisplayDeviceRepo.getByAddressLocked(displayLayout.getAddress());
+        if (newDefaultDevice == null) {
             return;
         }
 
-        final DisplayDevice deviceFolded =
-                mDisplayDeviceRepo.getByIdLocked(mDisplayIdToUseWhenFolded);
-        final DisplayDevice deviceUnfolded =
-                mDisplayDeviceRepo.getByIdLocked(mDisplayIdToUseWhenUnfolded);
-        if (deviceFolded == null || deviceUnfolded == null) {
-            // If the expected devices for folding functionality are not present, return early.
-            return;
-        }
-
-        // Find the associated LogicalDisplays for the configured "folding" DeviceDisplays.
-        final LogicalDisplay displayFolded = getLocked(deviceFolded);
-        final LogicalDisplay displayUnfolded = getLocked(deviceUnfolded);
-        if (displayFolded == null || displayUnfolded == null) {
-            // If the expected displays are not present, return early.
-            return;
-        }
-
-        // Find out which display is currently default and which is disabled.
         final LogicalDisplay defaultDisplay = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
-        final LogicalDisplay disabledDisplay;
-        if (defaultDisplay == displayFolded) {
-            disabledDisplay = displayUnfolded;
-        } else if (defaultDisplay == displayUnfolded) {
-            disabledDisplay = displayFolded;
-        } else {
-            // If neither folded or unfolded displays are currently set to the default display, we
-            // are in an unknown state and it's best to log the error and bail.
-            Slog.e(TAG, "Unexpected: when attempting to swap displays, neither of the two"
-                    + " configured displays were set up as the default display. Default: "
-                    + defaultDisplay.getDisplayInfoLocked() + ",  ConfiguredDisplays: [ folded="
-                    + displayFolded.getDisplayInfoLocked() + ", unfolded="
-                    + displayUnfolded.getDisplayInfoLocked() + " ]");
+        mCurrentLayout = layout;
+
+        // If we're already set up accurately, return early
+        if (defaultDisplay.getPrimaryDisplayDeviceLocked() == newDefaultDevice) {
             return;
         }
 
-        if (isFolded == (defaultDisplay == displayFolded)) {
-            // Nothing to do, already in the right state.
+        // We need to swap the default display's display-device with the one that is supposed
+        // to be the default in the new layout.
+        final LogicalDisplay displayToSwap = getLocked(newDefaultDevice);
+        if (displayToSwap == null) {
+            Slog.w(TAG, "Canceling display swap - unexpected empty second display for: "
+                    + newDefaultDevice);
             return;
         }
-
-        // Everything was checked and we need to swap, lets swap.
-        displayFolded.swapDisplaysLocked(displayUnfolded);
+        defaultDisplay.swapDisplaysLocked(displayToSwap);
 
         // We ensure that the non-default Display is always forced to be off. This was likely
         // already done in a previous iteration, but we do it with each swap in case something in
         // the underlying LogicalDisplays changed: like LogicalDisplay recreation, for example.
         defaultDisplay.setEnabled(true);
-        disabledDisplay.setEnabled(false);
+        displayToSwap.setEnabled(false);
 
         // Update the world
         updateLogicalDisplaysLocked();
-
-        if (DEBUG) {
-            Slog.d(TAG, "Folded displays: isFolded: " + isFolded + ", defaultDisplay? "
-                    + defaultDisplay.getDisplayInfoLocked());
-        }
-    }
-
-    void setFoldOverrideLocked(Boolean isFolded) {
-        if (!Objects.equals(isFolded, mIsFoldedOverride)) {
-            mIsFoldedOverride = isFolded;
-            setDeviceFoldedLocked(mIsFolded);
-        }
     }
 
     private void handleDisplayDeviceAddedLocked(DisplayDevice device) {
@@ -333,7 +294,7 @@
             return;
         }
 
-        final int displayId = assignDisplayIdLocked(isDefault);
+        final int displayId = Layout.assignDisplayIdLocked(isDefault);
         final int layerStack = assignLayerStackLocked(displayId);
 
         final DisplayGroup displayGroup;
@@ -356,8 +317,15 @@
             return;
         }
 
-        mLogicalDisplays.put(displayId, display);
+        // For foldable devices, we start the internal non-default displays as disabled.
+        // TODO - b/168208162 - this will be removed when we recalculate the layout with each
+        // display-device addition.
+        if (mFoldedDeviceStates.length > 0 && deviceInfo.type == Display.TYPE_INTERNAL
+                && !isDefault) {
+            display.setEnabled(false);
+        }
 
+        mLogicalDisplays.put(displayId, display);
         displayGroup.addDisplayLocked(display);
         mDisplayIdToGroupMap.append(displayId, displayGroup);
 
@@ -375,6 +343,10 @@
             mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
                     LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
         }
+
+        if (DEBUG) {
+            Slog.d(TAG, "New Display added: " + display);
+        }
     }
 
     /**
@@ -466,10 +438,6 @@
         }
     }
 
-    private int assignDisplayIdLocked(boolean isDefault) {
-        return isDefault ? Display.DEFAULT_DISPLAY : mNextNonDefaultDisplayId++;
-    }
-
     private int assignDisplayGroupIdLocked(boolean isDefault) {
         return isDefault ? Display.DEFAULT_DISPLAY_GROUP : mNextNonDefaultGroupId++;
     }
@@ -480,21 +448,6 @@
         return displayId;
     }
 
-    private void loadFoldedDisplayConfig(Context context) {
-        final String[] displayIds = context.getResources().getStringArray(
-                com.android.internal.R.array.config_internalFoldedPhysicalDisplayIds);
-
-        if (displayIds.length != 2 || TextUtils.isEmpty(displayIds[0])
-                || TextUtils.isEmpty(displayIds[1])) {
-            Slog.w(TAG, "Folded display configuration invalid: [" + Arrays.toString(displayIds)
-                    + "]");
-            return;
-        }
-
-        mDisplayIdToUseWhenFolded = displayIds[0];
-        mDisplayIdToUseWhenUnfolded = displayIds[1];
-    }
-
     public interface Listener {
         void onLogicalDisplayEventLocked(LogicalDisplay display, int event);
         void onDisplayGroupEventLocked(int groupId, int event);
diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java
index 7916d81..26004a8 100644
--- a/services/core/java/com/android/server/display/RampAnimator.java
+++ b/services/core/java/com/android/server/display/RampAnimator.java
@@ -20,7 +20,7 @@
 import android.util.FloatProperty;
 import android.view.Choreographer;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 
 /**
  * A custom animator that progressively updates a property value at
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
new file mode 100644
index 0000000..18f39e6
--- /dev/null
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.layout;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+import android.view.DisplayAddress;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Holds a collection of {@link Display}s. A single instance of this class describes
+ * how to organize one or more DisplayDevices into LogicalDisplays for a particular device
+ * state. For example, there may be one instance of this class to describe display layout when
+ * a foldable device is folded, and a second instance for when the device is unfolded.
+ */
+public class Layout {
+    private static final String TAG = "Layout";
+    private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
+
+    private final List<Display> mDisplays = new ArrayList<>(2);
+
+    /**
+     *  @return The default display ID, or a new unique one to use.
+     */
+    public static int assignDisplayIdLocked(boolean isDefault) {
+        return isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
+    }
+
+    @Override
+    public String toString() {
+        return mDisplays.toString();
+    }
+
+    /**
+     * Creates a simple 1:1 LogicalDisplay mapping for the specified DisplayDevice.
+     *
+     * @param address Address of the device.
+     * @param isDefault Indicates if the device is meant to be the default display.
+     * @return The new layout.
+     */
+    public Display createDisplayLocked(
+            @NonNull DisplayAddress address, boolean isDefault) {
+        if (contains(address)) {
+            Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
+            return null;
+        }
+
+        // See if we're dealing with the "default" display
+        if (isDefault && getById(DEFAULT_DISPLAY) != null) {
+            Slog.w(TAG, "Ignoring attempt to add a second default display: " + address);
+            isDefault = false;
+        }
+
+        // Assign a logical display ID and create the new display.
+        // Note that the logical display ID is saved into the layout, so when switching between
+        // different layouts, a logical display can be destroyed and later recreated with the
+        // same logical display ID.
+        final int logicalDisplayId = assignDisplayIdLocked(isDefault);
+        final Display layout = new Display(address, logicalDisplayId);
+
+        mDisplays.add(layout);
+        return layout;
+    }
+
+    /**
+     * @param address The address to check.
+     *
+     * @return True if the specified address is used in this layout.
+     */
+    public boolean contains(@NonNull DisplayAddress address) {
+        final int size = mDisplays.size();
+        for (int i = 0; i < size; i++) {
+            if (address.equals(mDisplays.get(i).getAddress())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @param id The display ID to check.
+     *
+     * @return The display corresponding to the specified display ID.
+     */
+    public Display getById(int id) {
+        for (int i = 0; i < mDisplays.size(); i++) {
+            Display layout = mDisplays.get(i);
+            if (id == layout.getLogicalDisplayId()) {
+                return layout;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @param index The index of the display to return.
+     *
+     * @return the display at the specified index.
+     */
+    public Display getAt(int index) {
+        return mDisplays.get(index);
+    }
+
+    /**
+     * @return The number of displays defined for this layout.
+     */
+    public int size() {
+        return mDisplays.size();
+    }
+
+    /**
+     * Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s.
+     */
+    public static class Display {
+        private final DisplayAddress mAddress;
+        private final int mLogicalDisplayId;
+
+        Display(@NonNull DisplayAddress address, int logicalDisplayId) {
+            mAddress = address;
+            mLogicalDisplayId = logicalDisplayId;
+        }
+
+        @Override
+        public String toString() {
+            return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId + "}";
+        }
+
+        public DisplayAddress getAddress() {
+            return mAddress;
+        }
+
+        public int getLogicalDisplayId() {
+            return mLogicalDisplayId;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
index 909fcda..66fc0d9 100644
--- a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
+++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
@@ -17,6 +17,7 @@
 
 import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_UNKNOWN;
 
+import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 import android.util.SparseIntArray;
@@ -108,7 +109,10 @@
     private void resetPowerStatus(List<HdmiDeviceInfo> deviceInfos) {
         mPowerStatus.clear();
         for (HdmiDeviceInfo info : deviceInfos) {
-            mPowerStatus.append(info.getLogicalAddress(), info.getDevicePowerStatus());
+            if (localDevice().mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0
+                    || info.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+                mPowerStatus.append(info.getLogicalAddress(), info.getDevicePowerStatus());
+            }
         }
     }
 
@@ -117,19 +121,22 @@
                 localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false);
         resetPowerStatus(deviceInfos);
         for (HdmiDeviceInfo info : deviceInfos) {
-            final int logicalAddress = info.getLogicalAddress();
-            sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(),
-                    logicalAddress),
-                    new SendMessageCallback() {
-                        @Override
-                        public void onSendCompleted(int error) {
-                            // If fails to send <Give Device Power Status>,
-                            // update power status into UNKNOWN.
-                            if (error != SendMessageResult.SUCCESS) {
-                               updatePowerStatus(logicalAddress, POWER_STATUS_UNKNOWN, true);
+            if (localDevice().mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0
+                    || info.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+                final int logicalAddress = info.getLogicalAddress();
+                sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(),
+                        logicalAddress),
+                        new SendMessageCallback() {
+                            @Override
+                            public void onSendCompleted(int error) {
+                                // If fails to send <Give Device Power Status>,
+                                // update power status into UNKNOWN.
+                                if (error != SendMessageResult.SUCCESS) {
+                                    updatePowerStatus(logicalAddress, POWER_STATUS_UNKNOWN, true);
+                                }
                             }
-                        }
-                    });
+                        });
+            }
         }
 
         mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 00ae30e..0754df0 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -178,6 +178,7 @@
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.os.TransferPipe;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.view.IInlineSuggestionsRequestCallback;
 import com.android.internal.view.IInlineSuggestionsResponseCallback;
@@ -5229,15 +5230,7 @@
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
-        boolean asProto = false;
-        for (int argIndex = 0; argIndex < args.length; argIndex++) {
-            if (args[argIndex].equals(PROTO_ARG)) {
-                asProto = true;
-                break;
-            }
-        }
-
-        if (asProto) {
+        if (ArrayUtils.contains(args, PROTO_ARG)) {
             final ImeTracing imeTracing = ImeTracing.getInstance();
             if (imeTracing.isEnabled()) {
                 imeTracing.stopTrace(null, false /* writeToFile */);
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index 43c965d..42b0add 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -36,9 +36,9 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.SystemService;
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index cb9793f..685e9e6 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -279,6 +279,7 @@
             super.onBootPhase(phase);
             if (phase == PHASE_ACTIVITY_MANAGER_READY) {
                 mLockSettingsService.migrateOldDataAfterSystemReady();
+                mLockSettingsService.loadEscrowData();
             }
         }
 
@@ -824,13 +825,17 @@
         mSpManager.initWeaverService();
         getAuthSecretHal();
         mDeviceProvisionedObserver.onSystemReady();
-        mRebootEscrowManager.loadRebootEscrowDataIfAvailable();
+
         // TODO: maybe skip this for split system user mode.
         mStorage.prefetchUser(UserHandle.USER_SYSTEM);
         mBiometricDeferredQueue.systemReady(mInjector.getFingerprintManager(),
                 mInjector.getFaceManager());
     }
 
+    private void loadEscrowData() {
+        mRebootEscrowManager.loadRebootEscrowDataIfAvailable(mHandler);
+    }
+
     private void getAuthSecretHal() {
         try {
             mAuthSecretService = IAuthSecret.getService(/* retry */ true);
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index 06962d4..53b62ca 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -21,6 +21,7 @@
 import android.annotation.UserIdInt;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.os.Handler;
 import android.os.SystemClock;
 import android.os.UserManager;
 import android.provider.DeviceConfig;
@@ -39,6 +40,7 @@
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
 
 import javax.crypto.SecretKey;
 
@@ -76,6 +78,13 @@
     private static final int BOOT_COUNT_TOLERANCE = 5;
 
     /**
+     * The default retry specs for loading reboot escrow data. We will attempt to retry loading
+     * escrow data on temporarily errors, e.g. unavailable network.
+     */
+    private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT = 3;
+    private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS = 30;
+
+    /**
      * Logs events for later debugging in bugreports.
      */
     private final RebootEscrowEventLog mEventLog;
@@ -148,6 +157,14 @@
             return null;
         }
 
+        void post(Handler handler, Runnable runnable) {
+            handler.post(runnable);
+        }
+
+        void postDelayed(Handler handler, Runnable runnable, long delayMillis) {
+            handler.postDelayed(runnable, delayMillis);
+        }
+
         public Context getContext() {
             return mContext;
         }
@@ -199,7 +216,18 @@
         mKeyStoreManager = injector.getKeyStoreManager();
     }
 
-    void loadRebootEscrowDataIfAvailable() {
+    private void onGetRebootEscrowKeyFailed(List<UserInfo> users) {
+        Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage.");
+        for (UserInfo user : users) {
+            mStorage.removeRebootEscrow(user.id);
+        }
+
+        // Clear the old key in keystore.
+        mKeyStoreManager.clearKeyStoreEncryptionKey();
+        onEscrowRestoreComplete(false);
+    }
+
+    void loadRebootEscrowDataIfAvailable(Handler retryHandler) {
         List<UserInfo> users = mUserManager.getUsers();
         List<UserInfo> rebootEscrowUsers = new ArrayList<>();
         for (UserInfo user : users) {
@@ -212,17 +240,49 @@
             return;
         }
 
+        mInjector.post(retryHandler, () -> loadRebootEscrowDataWithRetry(
+                retryHandler, 0, users, rebootEscrowUsers));
+    }
+
+    void scheduleLoadRebootEscrowDataOrFail(Handler retryHandler, int attemptNumber,
+            List<UserInfo> users, List<UserInfo> rebootEscrowUsers) {
+        Objects.requireNonNull(retryHandler);
+
+        final int retryLimit = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA,
+                "load_escrow_data_retry_count", DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT);
+        final int retryIntervalInSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA,
+                "load_escrow_data_retry_interval_seconds",
+                DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS);
+
+        if (attemptNumber < retryLimit) {
+            Slog.i(TAG, "Scheduling loadRebootEscrowData retry number: " + attemptNumber);
+            mInjector.postDelayed(retryHandler, () -> loadRebootEscrowDataWithRetry(
+                    retryHandler, attemptNumber, users, rebootEscrowUsers),
+                    retryIntervalInSeconds * 1000);
+            return;
+        }
+
+        Slog.w(TAG, "Failed to load reboot escrow data after " + attemptNumber + " attempts");
+        onGetRebootEscrowKeyFailed(users);
+    }
+
+    void loadRebootEscrowDataWithRetry(Handler retryHandler, int attemptNumber,
+            List<UserInfo> users, List<UserInfo> rebootEscrowUsers) {
         // Fetch the key from keystore to decrypt the escrow data & escrow key; this key is
         // generated before reboot. Note that we will clear the escrow key even if the keystore key
         // is null.
         SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey();
-        RebootEscrowKey escrowKey = getAndClearRebootEscrowKey(kk);
+        RebootEscrowKey escrowKey;
+        try {
+            escrowKey = getAndClearRebootEscrowKey(kk);
+        } catch (IOException e) {
+            scheduleLoadRebootEscrowDataOrFail(retryHandler, attemptNumber + 1, users,
+                    rebootEscrowUsers);
+            return;
+        }
+
         if (kk == null || escrowKey == null) {
-            Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage.");
-            for (UserInfo user : users) {
-                mStorage.removeRebootEscrow(user.id);
-            }
-            onEscrowRestoreComplete(false);
+            onGetRebootEscrowKeyFailed(users);
             return;
         }
 
@@ -249,7 +309,7 @@
         }
     }
 
-    private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) {
+    private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) throws IOException {
         RebootEscrowProviderInterface rebootEscrowProvider = mInjector.getRebootEscrowProvider();
         if (rebootEscrowProvider == null) {
             Slog.w(TAG,
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java
index 6c1040b..4b00772 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java
@@ -33,7 +33,7 @@
  * An implementation of the {@link RebootEscrowProviderInterface} by calling the RebootEscrow HAL.
  */
 class RebootEscrowProviderHalImpl implements RebootEscrowProviderInterface {
-    private static final String TAG = "RebootEscrowProvider";
+    private static final String TAG = "RebootEscrowProviderHal";
 
     private final Injector mInjector;
 
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java
index 857ad5f..af6faad 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java
@@ -16,6 +16,8 @@
 
 package com.android.server.locksettings;
 
+import java.io.IOException;
+
 import javax.crypto.SecretKey;
 
 /**
@@ -33,9 +35,10 @@
 
     /**
      * Returns the stored RebootEscrowKey, and clears the storage. If the stored key is encrypted,
-     * use the input key to decrypt the RebootEscrowKey. Returns null on failure.
+     * use the input key to decrypt the RebootEscrowKey. Returns null on failure. Throws an
+     * IOException if the failure is non-fatal, and a retry may succeed.
      */
-    RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey);
+    RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) throws IOException;
 
     /**
      * Clears the stored RebootEscrowKey.
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
index ba1a680..9d09637 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
@@ -35,7 +35,7 @@
  * encrypt & decrypt the blob.
  */
 class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterface {
-    private static final String TAG = "RebootEscrowProvider";
+    private static final String TAG = "RebootEscrowProviderServerBased";
 
     // Timeout for service binding
     private static final long DEFAULT_SERVICE_TIMEOUT_IN_SECONDS = 10;
@@ -50,6 +50,8 @@
 
     private final Injector mInjector;
 
+    private byte[] mServerBlob;
+
     static class Injector {
         private ResumeOnRebootServiceConnection mServiceConnection = null;
 
@@ -124,17 +126,20 @@
     }
 
     @Override
-    public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) {
-        byte[] serverBlob = mStorage.readRebootEscrowServerBlob();
+    public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) throws IOException {
+        if (mServerBlob == null) {
+            mServerBlob = mStorage.readRebootEscrowServerBlob();
+        }
         // Delete the server blob in storage.
         mStorage.removeRebootEscrowServerBlob();
-        if (serverBlob == null) {
+        if (mServerBlob == null) {
             Slog.w(TAG, "Failed to read reboot escrow server blob from storage");
             return null;
         }
 
+        Slog.i(TAG, "Loaded reboot escrow server blob from storage");
         try {
-            byte[] escrowKeyBytes = unwrapServerBlob(serverBlob, decryptionKey);
+            byte[] escrowKeyBytes = unwrapServerBlob(mServerBlob, decryptionKey);
             if (escrowKeyBytes == null) {
                 Slog.w(TAG, "Decrypted reboot escrow key bytes should not be null");
                 return null;
@@ -145,7 +150,7 @@
             }
 
             return RebootEscrowKey.fromKeyBytes(escrowKeyBytes);
-        } catch (TimeoutException | RemoteException | IOException e) {
+        } catch (TimeoutException | RemoteException e) {
             Slog.w(TAG, "Failed to decrypt the server blob ", e);
             return null;
         }
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 769b781..78c1a95 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -21,6 +21,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.IPackageManager;
+import android.content.pm.ServiceInfo;
 import android.net.Uri;
 import android.os.IBinder;
 import android.os.IInterface;
@@ -197,6 +198,11 @@
     }
 
     @Override
+    protected void ensureFilters(ServiceInfo si, int userId) {
+        // nothing to filter
+    }
+
+    @Override
     protected void loadDefaultsFromConfig() {
         String defaultDndAccess = mContext.getResources().getString(
                 R.string.config_defaultDndAccessPackages);
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 21537e6..bbdcac2 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -189,6 +189,8 @@
 
     abstract protected void onServiceAdded(ManagedServiceInfo info);
 
+    abstract protected void ensureFilters(ServiceInfo si, int userId);
+
     protected List<ManagedServiceInfo> getServices() {
         synchronized (mMutex) {
             List<ManagedServiceInfo> services = new ArrayList<>(mServices);
@@ -1342,8 +1344,10 @@
             for (ComponentName component : add) {
                 try {
                     ServiceInfo info = mPm.getServiceInfo(component,
-                            PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+                            PackageManager.GET_META_DATA
+                                    | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                            userId);
                     if (info == null) {
                         Slog.w(TAG, "Not binding " + getCaption() + " service " + component
                                 + ": service not found");
@@ -1356,7 +1360,7 @@
                     }
                     Slog.v(TAG,
                             "enabling " + getCaption() + " for " + userId + ": " + component);
-                    registerService(component, userId);
+                    registerService(info, userId);
                 } catch (RemoteException e) {
                     e.rethrowFromSystemServer();
                 }
@@ -1368,9 +1372,15 @@
      * Version of registerService that takes the name of a service component to bind to.
      */
     @VisibleForTesting
-    void registerService(final ComponentName name, final int userid) {
+    void registerService(final ServiceInfo si, final int userId) {
+        ensureFilters(si, userId);
+        registerService(si.getComponentName(), userId);
+    }
+
+    @VisibleForTesting
+    void registerService(final ComponentName cn, final int userId) {
         synchronized (mMutex) {
-            registerServiceLocked(name, userid);
+            registerServiceLocked(cn, userId);
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5703ffe..917be29 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -177,6 +177,7 @@
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.UserInfo;
+import android.content.pm.VersionedPackage;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.media.AudioAttributes;
@@ -214,6 +215,7 @@
 import android.provider.Settings;
 import android.service.notification.Adjustment;
 import android.service.notification.Condition;
+import android.service.notification.ConditionProviderService;
 import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.IConditionProvider;
 import android.service.notification.INotificationListener;
@@ -8969,7 +8971,8 @@
         NotificationListenerFilter nls = mListeners.getNotificationListenerFilter(listener.mKey);
         if (nls != null
                 && (!nls.isTypeAllowed(notificationType)
-                || !nls.isPackageAllowed(sbn.getPackageName()))) {
+                || !nls.isPackageAllowed(
+                        new VersionedPackage(sbn.getPackageName(), sbn.getUid())))) {
             return false;
         }
         return true;
@@ -9132,6 +9135,12 @@
         }
 
         @Override
+        protected void ensureFilters(ServiceInfo si, int userId) {
+            // nothing to filter; no user visible settings for types/packages like other
+            // listeners
+        }
+
+        @Override
         @GuardedBy("mNotificationLock")
         protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
             mListeners.unregisterService(removed.service, removed.userid);
@@ -9576,11 +9585,12 @@
 
     public class NotificationListeners extends ManagedServices {
         static final String TAG_ENABLED_NOTIFICATION_LISTENERS = "enabled_listeners";
-        static final String TAG_REQUESTED_LISTENERS = "req_listeners";
+        static final String TAG_REQUESTED_LISTENERS = "request_listeners";
         static final String TAG_REQUESTED_LISTENER = "listener";
         static final String ATT_COMPONENT = "component";
         static final String ATT_TYPES = "types";
-        static final String ATT_PKGS = "pkgs";
+        static final String ATT_PKG = "pkg";
+        static final String ATT_UID = "uid";
         static final String TAG_APPROVED = "allowed";
         static final String TAG_DISALLOWED= "disallowed";
         static final String XML_SEPARATOR = ",";
@@ -9698,28 +9708,6 @@
         }
 
         @Override
-        public void onUserUnlocked(int user) {
-            int flags = PackageManager.GET_SERVICES | PackageManager.GET_META_DATA;
-
-            final PackageManager pmWrapper = mContext.getPackageManager();
-            List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser(
-                    new Intent(getConfig().serviceInterface), flags, user);
-
-            for (ResolveInfo resolveInfo : installedServices) {
-                ServiceInfo info = resolveInfo.serviceInfo;
-
-                if (!getConfig().bindPermission.equals(info.permission)) {
-                    continue;
-                }
-                Pair key = Pair.create(info.getComponentName(), user);
-                if (!mRequestedNotificationListeners.containsKey(key)) {
-                    mRequestedNotificationListeners.put(key, new NotificationListenerFilter());
-                }
-            }
-            super.onUserUnlocked(user);
-        }
-
-        @Override
         public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uidList) {
             super.onPackagesChanged(removingPackage, pkgList, uidList);
 
@@ -9738,6 +9726,18 @@
                     }
                 }
             }
+
+            // clean up anything in the disallowed pkgs list
+            for (int i = 0; i < pkgList.length; i++) {
+                String pkg = pkgList[i];
+                int userId = UserHandle.getUserId(uidList[i]);
+                for (int j = mRequestedNotificationListeners.size() - 1; j >= 0; j--) {
+                    NotificationListenerFilter nlf = mRequestedNotificationListeners.valueAt(j);
+
+                    VersionedPackage ai = new VersionedPackage(pkg, uidList[i]);
+                    nlf.removePackage(ai);
+                }
+            }
         }
 
         @Override
@@ -9767,15 +9767,17 @@
                     int approved = FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ALERTING
                             | FLAG_FILTER_TYPE_SILENT | FLAG_FILTER_TYPE_ONGOING;
 
-                    ArraySet<String> disallowedPkgs = new ArraySet<>();
+                    ArraySet<VersionedPackage> disallowedPkgs = new ArraySet<>();
                     final int listenerOuterDepth = parser.getDepth();
                     while (XmlUtils.nextElementWithin(parser, listenerOuterDepth)) {
                         if (TAG_APPROVED.equals(parser.getName())) {
                             approved = XmlUtils.readIntAttribute(parser, ATT_TYPES);
                         } else if (TAG_DISALLOWED.equals(parser.getName())) {
-                            String pkgs = XmlUtils.readStringAttribute(parser, ATT_PKGS);
-                            if (!TextUtils.isEmpty(pkgs)) {
-                                disallowedPkgs = new ArraySet<>(pkgs.split(XML_SEPARATOR));
+                            String pkg = XmlUtils.readStringAttribute(parser, ATT_PKG);
+                            int uid = XmlUtils.readIntAttribute(parser, ATT_UID);
+                            if (!TextUtils.isEmpty(pkg)) {
+                                VersionedPackage ai = new VersionedPackage(pkg, uid);
+                                disallowedPkgs.add(ai);
                             }
                         }
                     }
@@ -9800,10 +9802,14 @@
                 XmlUtils.writeIntAttribute(out, ATT_TYPES, nlf.getTypes());
                 out.endTag(null, TAG_APPROVED);
 
-                out.startTag(null, TAG_DISALLOWED);
-                XmlUtils.writeStringAttribute(
-                        out, ATT_PKGS, String.join(XML_SEPARATOR, nlf.getDisallowedPackages()));
-                out.endTag(null, TAG_DISALLOWED);
+                for (VersionedPackage ai : nlf.getDisallowedPackages()) {
+                    if (!TextUtils.isEmpty(ai.getPackageName())) {
+                        out.startTag(null, TAG_DISALLOWED);
+                        XmlUtils.writeStringAttribute(out, ATT_PKG, ai.getPackageName());
+                        XmlUtils.writeIntAttribute(out, ATT_UID, ai.getVersionCode());
+                        out.endTag(null, TAG_DISALLOWED);
+                    }
+                }
 
                 out.endTag(null, TAG_REQUESTED_LISTENER);
             }
@@ -9821,6 +9827,38 @@
             mRequestedNotificationListeners.put(pair, nlf);
         }
 
+        @Override
+        protected void ensureFilters(ServiceInfo si, int userId) {
+            Pair listener = Pair.create(si.getComponentName(), userId);
+            NotificationListenerFilter existingNlf =
+                    mRequestedNotificationListeners.get(listener);
+            if (existingNlf  == null) {
+                // no stored filters for this listener; see if they provided a default
+                if (si.metaData != null) {
+                    String typeList = si.metaData.getString(
+                            NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES);
+                    if (typeList != null) {
+                        int types = 0;
+                        String[] typeStrings = typeList.split(XML_SEPARATOR);
+                        for (int i = 0; i < typeStrings.length; i++) {
+                            if (TextUtils.isEmpty(typeStrings[i])) {
+                                continue;
+                            }
+                            try {
+                                types |= Integer.parseInt(typeStrings[i]);
+                            } catch (NumberFormatException e) {
+                                // skip
+                            }
+                        }
+
+                         NotificationListenerFilter nlf =
+                                 new NotificationListenerFilter(types, new ArraySet<>());
+                        mRequestedNotificationListeners.put(listener, nlf);
+                    }
+                }
+            }
+        }
+
         @GuardedBy("mNotificationLock")
         public void setOnNotificationPostedTrimLocked(ManagedServiceInfo info, int trim) {
             if (trim == TRIM_LIGHT) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index b7b72d1..663fdee 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -335,6 +335,7 @@
 
         final List<OverlayInfo> overlaysForTarget =
                 mInterface.getOverlayInfosForTarget(overlayInfo.targetPackageName, userId);
+        overlaysForTarget.remove(overlayInfo);
         final OverlayManagerTransaction.Builder builder = new OverlayManagerTransaction.Builder();
         for (final OverlayInfo disableOverlay : overlaysForTarget) {
             if ((inCategory && !Objects.equals(disableOverlay.category,overlayInfo.category))
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index de85d9e..f31d1da 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -43,6 +43,7 @@
 import android.util.ArraySet;
 import android.util.Singleton;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -226,6 +227,12 @@
     abstract ApexSessionInfo getStagedSessionInfo(int sessionId);
 
     /**
+     * Returns array of all staged sessions known to apexd.
+     */
+    @NonNull
+    abstract SparseArray<ApexSessionInfo> getSessions();
+
+    /**
      * Submit a staged session to apex service. This causes the apex service to perform some initial
      * verification and accept or reject the session. Submitting a session successfully is not
      * enough for it to be activated at the next boot, the caller needs to call
@@ -691,6 +698,21 @@
         }
 
         @Override
+        SparseArray<ApexSessionInfo> getSessions() {
+            try {
+                final ApexSessionInfo[] sessions = waitForApexService().getSessions();
+                final SparseArray<ApexSessionInfo> result = new SparseArray<>(sessions.length);
+                for (int i = 0; i < sessions.length; i++) {
+                    result.put(sessions[i].sessionId, sessions[i]);
+                }
+                return result;
+            } catch (RemoteException re) {
+                Slog.e(TAG, "Unable to contact apexservice", re);
+                throw new RuntimeException(re);
+            }
+        }
+
+        @Override
         ApexInfoList submitStagedSession(ApexSessionParams params) throws PackageManagerException {
             try {
                 final ApexInfoList apexInfoList = new ApexInfoList();
@@ -1083,6 +1105,11 @@
         }
 
         @Override
+        SparseArray<ApexSessionInfo> getSessions() {
+            return new SparseArray<>(0);
+        }
+
+        @Override
         ApexInfoList submitStagedSession(ApexSessionParams params)
                 throws PackageManagerException {
             throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 402f646..af0aa76 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -217,7 +217,7 @@
             // trade-off worth doing to save boot time work.
             int result = pm.performDexOptWithStatus(new DexoptOptions(
                     pkg,
-                    PackageManagerService.REASON_BOOT,
+                    PackageManagerService.REASON_POST_BOOT,
                     DexoptOptions.DEXOPT_BOOT_COMPLETE));
             if (result == PackageDexOptimizer.DEX_OPT_PERFORMED)  {
                 updatedPackages.add(pkg);
diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
index 52fdc79..308e815 100644
--- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java
+++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
@@ -27,6 +27,8 @@
 import android.content.pm.IDataLoaderStatusListener;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -45,12 +47,20 @@
 public class DataLoaderManagerService extends SystemService {
     private static final String TAG = "DataLoaderManager";
     private final Context mContext;
+    private final HandlerThread mThread;
+    private final Handler mHandler;
     private final DataLoaderManagerBinderService mBinderService;
     private SparseArray<DataLoaderServiceConnection> mServiceConnections = new SparseArray<>();
 
     public DataLoaderManagerService(Context context) {
         super(context);
         mContext = context;
+
+        mThread = new HandlerThread(TAG);
+        mThread.start();
+
+        mHandler = new Handler(mThread.getLooper());
+
         mBinderService = new DataLoaderManagerBinderService();
     }
 
@@ -62,7 +72,7 @@
     final class DataLoaderManagerBinderService extends IDataLoaderManager.Stub {
         @Override
         public boolean bindToDataLoader(int dataLoaderId, DataLoaderParamsParcel params,
-                IDataLoaderStatusListener listener) {
+                long bindDelayMs, IDataLoaderStatusListener listener) {
             synchronized (mServiceConnections) {
                 if (mServiceConnections.get(dataLoaderId) != null) {
                     return true;
@@ -76,19 +86,21 @@
             }
 
             // Binds to the specific data loader service.
-            DataLoaderServiceConnection connection = new DataLoaderServiceConnection(dataLoaderId,
-                    listener);
+            final DataLoaderServiceConnection connection = new DataLoaderServiceConnection(
+                    dataLoaderId, listener);
 
-            Intent intent = new Intent();
+            final Intent intent = new Intent();
             intent.setComponent(dataLoaderComponent);
-            if (!mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
-                    UserHandle.of(UserHandle.getCallingUserId()))) {
-                Slog.e(TAG,
-                        "Failed to bind to: " + dataLoaderComponent + " for ID=" + dataLoaderId);
-                mContext.unbindService(connection);
-                return false;
-            }
-            return true;
+
+            return mHandler.postDelayed(() -> {
+                if (!mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
+                        mHandler, UserHandle.of(UserHandle.getCallingUserId()))) {
+                    Slog.e(TAG,
+                            "Failed to bind to: " + dataLoaderComponent + " for ID="
+                                    + dataLoaderId);
+                    mContext.unbindService(connection);
+                }
+            }, bindDelayMs);
         }
 
         /**
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index fc02b34..b9e3e0f 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -687,7 +687,8 @@
         boolean generateCompactDex = true;
         switch (compilationReason) {
             case PackageManagerService.REASON_FIRST_BOOT:
-            case PackageManagerService.REASON_BOOT:
+            case PackageManagerService.REASON_BOOT_AFTER_OTA:
+            case PackageManagerService.REASON_POST_BOOT:
             case PackageManagerService.REASON_INSTALL:
                  generateCompactDex = false;
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 2d393c0..2812830 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -295,23 +295,29 @@
         synchronized (mSessions) {
             for (int i = 0; i < mSessions.size(); i++) {
                 final PackageInstallerSession session = mSessions.valueAt(i);
-                if (session.isStaged()) {
-                    stagedSessionsToRestore.add(session.mStagedSession);
+                if (!session.isStaged()) {
+                    continue;
+                }
+                StagingManager.StagedSession stagedSession = session.mStagedSession;
+                if (!stagedSession.isInTerminalState() && stagedSession.hasParentSessionId()
+                        && getSession(stagedSession.getParentSessionId()) == null) {
+                    stagedSession.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                            "An orphan staged session " + stagedSession.sessionId() + " is found, "
+                                + "parent " + stagedSession.getParentSessionId() + " is missing");
+                    continue;
+                }
+                if (!stagedSession.hasParentSessionId() && stagedSession.isCommitted()
+                        && !stagedSession.isInTerminalState()) {
+                    // StagingManager.restoreSessions expects a list of committed, non-finalized
+                    // parent staged sessions.
+                    stagedSessionsToRestore.add(stagedSession);
                 }
             }
         }
-        // Don't hold mSessions lock when calling restoreSession, since it might trigger an APK
+        // Don't hold mSessions lock when calling restoreSessions, since it might trigger an APK
         // atomic install which needs to query sessions, which requires lock on mSessions.
-        boolean isDeviceUpgrading = mPm.isDeviceUpgrading();
-        for (StagingManager.StagedSession session : stagedSessionsToRestore) {
-            if (!session.isInTerminalState() && session.hasParentSessionId()
-                    && getSession(session.getParentSessionId()) == null) {
-                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
-                        "An orphan staged session " + session.sessionId() + " is found, "
-                                + "parent " + session.getParentSessionId() + " is missing");
-            }
-            mStagingManager.restoreSession(session, isDeviceUpgrading);
-        }
+        // Note: restoreSessions mutates content of stagedSessionsToRestore.
+        mStagingManager.restoreSessions(stagedSessionsToRestore, mPm.isDeviceUpgrading());
     }
 
     @GuardedBy("mSessions")
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 7c42569..0ce2673 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3778,7 +3778,9 @@
             }
         }
 
-        if (!dataLoaderManager.bindToDataLoader(sessionId, params.getData(), statusListener)) {
+        final long bindDelayMs = 0;
+        if (!dataLoaderManager.bindToDataLoader(sessionId, params.getData(), bindDelayMs,
+                statusListener)) {
             throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
                     "Failed to initialize data loader");
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4db6550..7b9cf73 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -780,17 +780,18 @@
     // Compilation reasons.
     public static final int REASON_UNKNOWN = -1;
     public static final int REASON_FIRST_BOOT = 0;
-    public static final int REASON_BOOT = 1;
-    public static final int REASON_INSTALL = 2;
-    public static final int REASON_INSTALL_FAST = 3;
-    public static final int REASON_INSTALL_BULK = 4;
-    public static final int REASON_INSTALL_BULK_SECONDARY = 5;
-    public static final int REASON_INSTALL_BULK_DOWNGRADED = 6;
-    public static final int REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = 7;
-    public static final int REASON_BACKGROUND_DEXOPT = 8;
-    public static final int REASON_AB_OTA = 9;
-    public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 10;
-    public static final int REASON_SHARED = 11;
+    public static final int REASON_BOOT_AFTER_OTA = 1;
+    public static final int REASON_POST_BOOT = 2;
+    public static final int REASON_INSTALL = 3;
+    public static final int REASON_INSTALL_FAST = 4;
+    public static final int REASON_INSTALL_BULK = 5;
+    public static final int REASON_INSTALL_BULK_SECONDARY = 6;
+    public static final int REASON_INSTALL_BULK_DOWNGRADED = 7;
+    public static final int REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = 8;
+    public static final int REASON_BACKGROUND_DEXOPT = 9;
+    public static final int REASON_AB_OTA = 10;
+    public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 11;
+    public static final int REASON_SHARED = 12;
 
     public static final int REASON_LAST = REASON_SHARED;
 
@@ -11637,10 +11638,7 @@
         //       first boot, as they do not have profile data.
         boolean causeFirstBoot = isFirstBoot() || mIsPreNUpgrade;
 
-        // We need to re-extract after a pruned cache, as AoT-ed files will be out of date.
-        boolean causePrunedCache = VMRuntime.didPruneDalvikCache();
-
-        if (!causeUpgrade && !causeFirstBoot && !causePrunedCache) {
+        if (!causeUpgrade && !causeFirstBoot) {
             return;
         }
 
@@ -11657,7 +11655,7 @@
 
         final long startTime = System.nanoTime();
         final int[] stats = performDexOptUpgrade(pkgs, mIsPreNUpgrade /* showDialog */,
-                    causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT,
+                    causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT_AFTER_OTA,
                     false /* bootComplete */);
 
         final int elapsedTimeSeconds =
@@ -26213,30 +26211,7 @@
 
         @Override
         public void setKeepUninstalledPackages(final List<String> packageList) {
-            Preconditions.checkNotNull(packageList);
-            List<String> removedFromList = null;
-            synchronized (mLock) {
-                if (mKeepUninstalledPackages != null) {
-                    final int packagesCount = mKeepUninstalledPackages.size();
-                    for (int i = 0; i < packagesCount; i++) {
-                        String oldPackage = mKeepUninstalledPackages.get(i);
-                        if (packageList != null && packageList.contains(oldPackage)) {
-                            continue;
-                        }
-                        if (removedFromList == null) {
-                            removedFromList = new ArrayList<>();
-                        }
-                        removedFromList.add(oldPackage);
-                    }
-                }
-                mKeepUninstalledPackages = new ArrayList<>(packageList);
-                if (removedFromList != null) {
-                    final int removedCount = removedFromList.size();
-                    for (int i = 0; i < removedCount; i++) {
-                        deletePackageIfUnusedLPr(removedFromList.get(i));
-                    }
-                }
-            }
+            PackageManagerService.this.setKeepUninstalledPackagesInternal(packageList);
         }
 
         @Override
@@ -27734,6 +27709,43 @@
     public DomainVerificationService.Connection getDomainVerificationConnection() {
         return mDomainVerificationConnection;
     }
+
+    @Override
+    public void setKeepUninstalledPackages(List<String> packageList) {
+        mContext.enforceCallingPermission(
+                Manifest.permission.KEEP_UNINSTALLED_PACKAGES,
+                "setKeepUninstalledPackages requires KEEP_UNINSTALLED_PACKAGES permission");
+        Objects.requireNonNull(packageList);
+
+        setKeepUninstalledPackagesInternal(packageList);
+    }
+
+    private void setKeepUninstalledPackagesInternal(List<String> packageList) {
+        Preconditions.checkNotNull(packageList);
+        List<String> removedFromList = null;
+        synchronized (mLock) {
+            if (mKeepUninstalledPackages != null) {
+                final int packagesCount = mKeepUninstalledPackages.size();
+                for (int i = 0; i < packagesCount; i++) {
+                    String oldPackage = mKeepUninstalledPackages.get(i);
+                    if (packageList != null && packageList.contains(oldPackage)) {
+                        continue;
+                    }
+                    if (removedFromList == null) {
+                        removedFromList = new ArrayList<>();
+                    }
+                    removedFromList.add(oldPackage);
+                }
+            }
+            mKeepUninstalledPackages = new ArrayList<>(packageList);
+            if (removedFromList != null) {
+                final int removedCount = removedFromList.size();
+                for (int i = 0; i < removedCount; i++) {
+                    deletePackageIfUnusedLPr(removedFromList.get(i));
+                }
+            }
+        }
+    }
 }
 
 interface PackageSender {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 9cd55a6..636db11 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -29,7 +29,8 @@
     // Names for compilation reasons.
     public static final String REASON_STRINGS[] = {
         "first-boot",
-        "boot",
+        "boot-after-ota",
+        "post-boot",
         "install",
         "install-fast",
         "install-bulk",
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 545567c..0a74032 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -50,8 +50,6 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
-import android.os.storage.IStorageManager;
-import android.os.storage.StorageManager;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.IntArray;
@@ -63,6 +61,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageHelper;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
@@ -143,10 +142,16 @@
     }
 
     StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier) {
+        this(context, packageParserSupplier, ApexManager.getInstance());
+    }
+
+    @VisibleForTesting
+    StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier,
+            ApexManager apexManager) {
         mContext = context;
         mPackageParserSupplier = packageParserSupplier;
 
-        mApexManager = ApexManager.getInstance();
+        mApexManager = apexManager;
         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
         mPreRebootVerificationHandler = new PreRebootVerificationHandler(
                 BackgroundThread.get().getLooper());
@@ -354,11 +359,11 @@
     }
 
     // Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device.
-    private void abortCheckpoint(int sessionId, String errorMsg) {
-        String failureReason = "Failed to install sessionId: " + sessionId + " Error: " + errorMsg;
+    private void abortCheckpoint(String failureReason, boolean supportsCheckpoint,
+            boolean needsCheckpoint) {
         Slog.e(TAG, failureReason);
         try {
-            if (supportsCheckpoint() && needsCheckpoint()) {
+            if (supportsCheckpoint && needsCheckpoint) {
                 // Store failure reason for next reboot
                 try (BufferedWriter writer =
                              new BufferedWriter(new FileWriter(mFailureReasonFile))) {
@@ -371,8 +376,9 @@
                 if (mApexManager.isApexSupported()) {
                     mApexManager.revertActiveSessions();
                 }
+
                 PackageHelper.getStorageManager().abortChanges(
-                        "StagingManager initiated", false /*retry*/);
+                        "abort-staged-install", false /*retry*/);
             }
         } catch (Exception e) {
             Slog.wtf(TAG, "Failed to abort checkpoint", e);
@@ -384,14 +390,6 @@
         }
     }
 
-    private boolean supportsCheckpoint() throws RemoteException {
-        return PackageHelper.getStorageManager().supportsCheckpoint();
-    }
-
-    private boolean needsCheckpoint() throws RemoteException {
-        return PackageHelper.getStorageManager().needsCheckpoint();
-    }
-
     /**
      * Utility function for extracting apex sessions out of multi-package/single session.
      */
@@ -517,96 +515,31 @@
         }
     }
 
-    private void resumeSession(@NonNull StagedSession session)
-            throws PackageManagerException {
+    private void resumeSession(@NonNull StagedSession session, boolean supportsCheckpoint,
+            boolean needsCheckpoint) throws PackageManagerException {
         Slog.d(TAG, "Resuming session " + session.sessionId());
 
         final boolean hasApex = session.containsApexSession();
-        ApexSessionInfo apexSessionInfo = null;
-        if (hasApex) {
-            // Check with apexservice whether the apex packages have been activated.
-            apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId());
-
-            // Prepare for logging a native crash during boot, if one occurred.
-            if (apexSessionInfo != null && !TextUtils.isEmpty(
-                    apexSessionInfo.crashingNativeProcess)) {
-                prepareForLoggingApexdRevert(session, apexSessionInfo.crashingNativeProcess);
-            }
-
-            if (apexSessionInfo != null && apexSessionInfo.isVerified) {
-                // Session has been previously submitted to apexd, but didn't complete all the
-                // pre-reboot verification, perhaps because the device rebooted in the meantime.
-                // Greedily re-trigger the pre-reboot verification. We want to avoid marking it as
-                // failed when not in checkpoint mode, hence it is being processed separately.
-                Slog.d(TAG, "Found pending staged session " + session.sessionId() + " still to "
-                        + "be verified, resuming pre-reboot verification");
-                mPreRebootVerificationHandler.startPreRebootVerification(session);
-                return;
-            }
-        }
 
         // Before we resume session, we check if revert is needed or not. Typically, we enter file-
         // system checkpoint mode when we reboot first time in order to install staged sessions. We
         // want to install staged sessions in this mode as rebooting now will revert user data. If
         // something goes wrong, then we reboot again to enter fs-rollback mode. Rebooting now will
         // have no effect on user data, so mark the sessions as failed instead.
-        try {
-            // If checkpoint is supported, then we only resume sessions if we are in checkpointing
-            // mode. If not, we fail all sessions.
-            if (supportsCheckpoint() && !needsCheckpoint()) {
-                String revertMsg = "Reverting back to safe state. Marking "
-                        + session.sessionId() + " as failed.";
-                final String reasonForRevert = getReasonForRevert();
-                if (!TextUtils.isEmpty(reasonForRevert)) {
-                    revertMsg += " Reason for revert: " + reasonForRevert;
-                }
-                Slog.d(TAG, revertMsg);
-                session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, revertMsg);
-                return;
+        // If checkpoint is supported, then we only resume sessions if we are in checkpointing mode.
+        // If not, we fail all sessions.
+        if (supportsCheckpoint && !needsCheckpoint) {
+            String revertMsg = "Reverting back to safe state. Marking " + session.sessionId()
+                    + " as failed.";
+            final String reasonForRevert = getReasonForRevert();
+            if (!TextUtils.isEmpty(reasonForRevert)) {
+                revertMsg += " Reason for revert: " + reasonForRevert;
             }
-        } catch (RemoteException e) {
-            // Cannot continue staged install without knowing if fs-checkpoint is supported
-            Slog.e(TAG, "Checkpoint support unknown. Aborting staged install for session "
-                    + session.sessionId(), e);
-            // TODO: Mark all staged sessions together and reboot only once
-            session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN,
-                    "Checkpoint support unknown. Aborting staged install.");
-            if (hasApex) {
-                mApexManager.revertActiveSessions();
-            }
-            mPowerManager.reboot("Checkpoint support unknown");
+            Slog.d(TAG, revertMsg);
+            session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, revertMsg);
             return;
         }
 
-        // Check if apex packages in the session failed to activate
-        if (hasApex) {
-            if (apexSessionInfo == null) {
-                final String errorMsg = "apexd did not know anything about a staged session "
-                        + "supposed to be activated";
-                throw new PackageManagerException(
-                        SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
-            }
-            if (isApexSessionFailed(apexSessionInfo)) {
-                String errorMsg = "APEX activation failed. Check logcat messages from apexd "
-                        + "for more information.";
-                if (!TextUtils.isEmpty(mNativeFailureReason)) {
-                    errorMsg = "Session reverted due to crashing native process: "
-                            + mNativeFailureReason;
-                }
-                throw new PackageManagerException(
-                        SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
-            }
-            if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
-                // Apexd did not apply the session for some unknown reason. There is no
-                // guarantee that apexd will install it next time. Safer to proactively mark
-                // it as failed.
-                final String errorMsg = "Staged session " + session.sessionId() + "at boot "
-                        + "didn't activate nor fail. Marking it as failed anyway.";
-                throw new PackageManagerException(
-                        SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
-            }
-        }
-
         // Handle apk and apk-in-apex installation
         if (hasApex) {
             checkInstallationOfApkInApexSuccessful(session);
@@ -622,28 +555,24 @@
         Slog.d(TAG, "Marking session " + session.sessionId() + " as applied");
         session.setSessionApplied();
         if (hasApex) {
-            try {
-                if (supportsCheckpoint()) {
-                    // Store the session ID, which will be marked as successful by ApexManager
-                    // upon boot completion.
-                    synchronized (mSuccessfulStagedSessionIds) {
-                        mSuccessfulStagedSessionIds.add(session.sessionId());
-                    }
-                } else {
-                    // Mark sessions as successful immediately on non-checkpointing devices.
-                    mApexManager.markStagedSessionSuccessful(session.sessionId());
+            if (supportsCheckpoint) {
+                // Store the session ID, which will be marked as successful by ApexManager upon
+                // boot completion.
+                synchronized (mSuccessfulStagedSessionIds) {
+                    mSuccessfulStagedSessionIds.add(session.sessionId());
                 }
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Checkpoint support unknown, marking session as successful "
-                        + "immediately.");
+            } else {
+                // Mark sessions as successful immediately on non-checkpointing devices.
                 mApexManager.markStagedSessionSuccessful(session.sessionId());
             }
         }
     }
 
-    void onInstallationFailure(StagedSession session, PackageManagerException e) {
+    void onInstallationFailure(StagedSession session, PackageManagerException e,
+            boolean supportsCheckpoint, boolean needsCheckpoint) {
         session.setSessionFailed(e.error, e.getMessage());
-        abortCheckpoint(session.sessionId(), e.getMessage());
+        abortCheckpoint("Failed to install sessionId: " + session.sessionId()
+                + " Error: " + e.getMessage(), supportsCheckpoint, needsCheckpoint);
 
         // If checkpoint is not supported, we have to handle failure for one staged session.
         if (!session.containsApexSession()) {
@@ -767,8 +696,13 @@
                     "Cannot stage session " + session.sessionId() + " with package name null");
         }
 
-        boolean supportsCheckpoint = ((StorageManager) mContext.getSystemService(
-                Context.STORAGE_SERVICE)).isCheckpointSupported();
+        boolean supportsCheckpoint;
+        try {
+            supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint();
+        } catch (RemoteException e) {
+            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                    "Can't query fs-checkpoint status : " + e);
+        }
 
         final boolean isRollback = isRollback(session);
 
@@ -911,60 +845,166 @@
                 || apexSessionInfo.isRevertFailed;
     }
 
-    void restoreSession(@NonNull StagedSession session, boolean isDeviceUpgrading) {
-        if (session.hasParentSessionId()) {
-            // Only parent sessions can be restored
-            return;
+    private void handleNonReadyAndDestroyedSessions(List<StagedSession> sessions) {
+        int j = sessions.size();
+        for (int i = 0; i < j; ) {
+            // Maintain following invariant:
+            //  * elements at positions [0, i) should be kept
+            //  * elements at positions [j, n) should be remove.
+            //  * n = sessions.size()
+            StagedSession session = sessions.get(i);
+            if (session.isDestroyed()) {
+                // Device rebooted before abandoned session was cleaned up.
+                session.abandon();
+                StagedSession session2 = sessions.set(j - 1, session);
+                sessions.set(i, session2);
+                j--;
+            } else if (!session.isSessionReady()) {
+                // The framework got restarted before the pre-reboot verification could complete,
+                // restart the verification.
+                mPreRebootVerificationHandler.startPreRebootVerification(session);
+                StagedSession session2 = sessions.set(j - 1, session);
+                sessions.set(i, session2);
+                j--;
+            } else {
+                i++;
+            }
         }
-        // Store this parent session which will be used to check overlapping later
-        createSession(session);
-        // The preconditions used during pre-reboot verification might have changed when device
-        // is upgrading. Updated staged sessions to activation failed before we resume the session.
-        StagedSession sessionToResume = session;
-        if (isDeviceUpgrading && !sessionToResume.isInTerminalState()) {
-            sessionToResume.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
-                        "Build fingerprint has changed");
-            return;
-        }
-        checkStateAndResume(sessionToResume);
+        // Delete last j elements.
+        sessions.subList(j, sessions.size()).clear();
     }
 
-    private void checkStateAndResume(@NonNull StagedSession session) {
-        // Do not resume session if boot completed already
+    void restoreSessions(@NonNull List<StagedSession> sessions, boolean isDeviceUpgrading) {
+        // Do not resume sessions if boot completed already
         if (SystemProperties.getBoolean("sys.boot_completed", false)) {
             return;
         }
 
-        if (!session.isCommitted()) {
-            // Session hasn't been committed yet, ignore.
+        for (int i = 0; i < sessions.size(); i++) {
+            StagedSession session = sessions.get(i);
+            // Quick check that PackageInstallerService gave us sessions we expected.
+            Preconditions.checkArgument(!session.hasParentSessionId(),
+                    session.sessionId() + " is a child session");
+            Preconditions.checkArgument(session.isCommitted(),
+                    session.sessionId() + " is not committed");
+            Preconditions.checkArgument(!session.isInTerminalState(),
+                    session.sessionId() + " is in terminal state");
+            // Store this parent session which will be used to check overlapping later
+            createSession(session);
+        }
+
+        if (isDeviceUpgrading) {
+            // TODO(ioffe): check that corresponding apex sessions are failed.
+            // The preconditions used during pre-reboot verification might have changed when device
+            // is upgrading. Fail all the sessions and exit early.
+            for (int i = 0; i < sessions.size(); i++) {
+                StagedSession session = sessions.get(i);
+                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                        "Build fingerprint has changed");
+            }
             return;
         }
-        // Check the state of the session and decide what to do next.
-        if (session.isSessionFailed() || session.isSessionApplied()) {
-            // Final states, nothing to do.
+
+        boolean needsCheckpoint = false;
+        boolean supportsCheckpoint = false;
+        try {
+            supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint();
+            needsCheckpoint = PackageHelper.getStorageManager().needsCheckpoint();
+        } catch (RemoteException e) {
+            // This means that vold has crashed, and device is in a bad state.
+            throw new IllegalStateException("Failed to get checkpoint status", e);
+        }
+
+        if (sessions.size() > 1 && !supportsCheckpoint) {
+            throw new IllegalStateException("Detected multiple staged sessions on a device without "
+                    + "fs-checkpoint support");
+        }
+
+        // Do a set of quick checks before resuming individual sessions:
+        //   1. Schedule a pre-reboot verification for non-ready sessions.
+        //   2. Abandon destroyed sessions.
+        handleNonReadyAndDestroyedSessions(sessions); // mutates |sessions|
+
+        //   3. Check state of apex sessions is consistent. All non-applied sessions will be marked
+        //      as failed.
+        final SparseArray<ApexSessionInfo> apexSessions = mApexManager.getSessions();
+        boolean hasFailedApexSession = false;
+        boolean hasAppliedApexSession = false;
+        for (int i = 0; i < sessions.size(); i++) {
+            StagedSession session = sessions.get(i);
+            if (!session.containsApexSession()) {
+                // At this point we are only interested in apex sessions.
+                continue;
+            }
+            final ApexSessionInfo apexSession = apexSessions.get(session.sessionId());
+            if (apexSession == null || apexSession.isUnknown) {
+                hasFailedApexSession = true;
+                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, "apexd did "
+                        + "not know anything about a staged session supposed to be activated");
+                continue;
+            } else if (isApexSessionFailed(apexSession)) {
+                hasFailedApexSession = true;
+                String errorMsg = "APEX activation failed. Check logcat messages from apexd "
+                        + "for more information.";
+                if (!TextUtils.isEmpty(apexSession.crashingNativeProcess)) {
+                    prepareForLoggingApexdRevert(session, apexSession.crashingNativeProcess);
+                    errorMsg = "Session reverted due to crashing native process: "
+                            + apexSession.crashingNativeProcess;
+                }
+                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
+                continue;
+            } else if (apexSession.isActivated || apexSession.isSuccess) {
+                hasAppliedApexSession = true;
+                continue;
+            } else if (apexSession.isStaged) {
+                // Apexd did not apply the session for some unknown reason. There is no guarantee
+                // that apexd will install it next time. Safer to proactively mark it as failed.
+                hasFailedApexSession = true;
+                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                        "Staged session " + session.sessionId() + " at boot didn't activate nor "
+                        + "fail. Marking it as failed anyway.");
+            } else {
+                Slog.w(TAG, "Apex session " + session.sessionId() + " is in impossible state");
+                hasFailedApexSession = true;
+                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                        "Impossible state");
+            }
+        }
+
+        if (hasAppliedApexSession && hasFailedApexSession) {
+            abortCheckpoint("Found both applied and failed apex sessions", supportsCheckpoint,
+                    needsCheckpoint);
             return;
         }
-        if (session.isDestroyed()) {
-            // Device rebooted before abandoned session was cleaned up.
-            session.abandon();
+
+        if (hasFailedApexSession) {
+            // Either of those means that we failed at least one apex session, hence we should fail
+            // all other sessions.
+            for (int i = 0; i < sessions.size(); i++) {
+                StagedSession session = sessions.get(i);
+                if (session.isSessionFailed()) {
+                    // Session has been already failed in the loop above.
+                    continue;
+                }
+                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                        "Another apex session failed");
+            }
             return;
         }
-        if (!session.isSessionReady()) {
-            // The framework got restarted before the pre-reboot verification could complete,
-            // restart the verification.
-            mPreRebootVerificationHandler.startPreRebootVerification(session);
-        } else {
-            // Session had already being marked ready. Start the checks to verify if there is any
-            // follow-up work.
+
+        // Time to resume sessions.
+        for (int i = 0; i < sessions.size(); i++) {
+            StagedSession session = sessions.get(i);
             try {
-                resumeSession(session);
+                resumeSession(session, supportsCheckpoint, needsCheckpoint);
             } catch (PackageManagerException e) {
-                onInstallationFailure(session, e);
+                onInstallationFailure(session, e, supportsCheckpoint, needsCheckpoint);
             } catch (Exception e) {
                 Slog.e(TAG, "Staged install failed due to unhandled exception", e);
                 onInstallationFailure(session, new PackageManagerException(
                         SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
-                        "Staged install failed due to unhandled exception: " + e));
+                        "Staged install failed due to unhandled exception: " + e),
+                        supportsCheckpoint, needsCheckpoint);
             }
         }
     }
@@ -992,9 +1032,7 @@
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context ctx, Intent intent) {
-                mPreRebootVerificationHandler.readyToStart();
-                BackgroundThread.getExecutor().execute(
-                        () -> logFailedApexSessionsIfNecessary());
+                onBootCompletedBroadcastReceived();
                 ctx.unregisterReceiver(this);
             }
         }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
@@ -1002,6 +1040,12 @@
         mFailureReasonFile.delete();
     }
 
+    @VisibleForTesting
+    void onBootCompletedBroadcastReceived() {
+        mPreRebootVerificationHandler.readyToStart();
+        BackgroundThread.getExecutor().execute(() -> logFailedApexSessionsIfNecessary());
+    }
+
     private static class LocalIntentReceiverSync {
         private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
 
@@ -1286,9 +1330,8 @@
         private void handlePreRebootVerification_End(@NonNull StagedSession session) {
             // Before marking the session as ready, start checkpoint service if available
             try {
-                IStorageManager storageManager = PackageHelper.getStorageManager();
-                if (storageManager.supportsCheckpoint()) {
-                    storageManager.startCheckpoint(2);
+                if (PackageHelper.getStorageManager().supportsCheckpoint()) {
+                    PackageHelper.getStorageManager().startCheckpoint(2);
                 }
             } catch (Exception e) {
                 // Failed to get hold of StorageManager
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 139654e..3576950 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -587,7 +587,7 @@
     private static final int TRON_COMPILATION_REASON_ERROR = 0;
     private static final int TRON_COMPILATION_REASON_UNKNOWN = 1;
     private static final int TRON_COMPILATION_REASON_FIRST_BOOT = 2;
-    private static final int TRON_COMPILATION_REASON_BOOT = 3;
+    private static final int TRON_COMPILATION_REASON_BOOT_DEPRECATED_SINCE_S = 3;
     private static final int TRON_COMPILATION_REASON_INSTALL = 4;
     private static final int TRON_COMPILATION_REASON_BG_DEXOPT = 5;
     private static final int TRON_COMPILATION_REASON_AB_OTA = 6;
@@ -605,6 +605,8 @@
     private static final int TRON_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED_WITH_DM = 18;
     private static final int
             TRON_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED_WITH_DM = 19;
+    private static final int TRON_COMPILATION_REASON_BOOT_AFTER_OTA = 20;
+    private static final int TRON_COMPILATION_REASON_POST_BOOT = 21;
 
     // The annotation to add as a suffix to the compilation reason when dexopt was
     // performed with dex metadata.
@@ -618,7 +620,8 @@
             case "unknown" : return TRON_COMPILATION_REASON_UNKNOWN;
             case "error" : return TRON_COMPILATION_REASON_ERROR;
             case "first-boot" : return TRON_COMPILATION_REASON_FIRST_BOOT;
-            case "boot" : return TRON_COMPILATION_REASON_BOOT;
+            case "boot-after-ota": return TRON_COMPILATION_REASON_BOOT_AFTER_OTA;
+            case "post-boot" : return TRON_COMPILATION_REASON_POST_BOOT;
             case "install" : return TRON_COMPILATION_REASON_INSTALL;
             case "bg-dexopt" : return TRON_COMPILATION_REASON_BG_DEXOPT;
             case "ab-ota" : return TRON_COMPILATION_REASON_AB_OTA;
diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS
index 0e88862..e05ef48 100644
--- a/services/core/java/com/android/server/pm/permission/OWNERS
+++ b/services/core/java/com/android/server/pm/permission/OWNERS
@@ -1,4 +1,3 @@
-moltmann@google.com
 zhanghai@google.com
 per-file DefaultPermissionGrantPolicy.java = hackbod@android.com
 per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com
@@ -7,5 +6,4 @@
 per-file DefaultPermissionGrantPolicy.java = yamasani@google.com
 per-file DefaultPermissionGrantPolicy.java = patb@google.com
 per-file DefaultPermissionGrantPolicy.java = eugenesusla@google.com
-per-file DefaultPermissionGrantPolicy.java = moltmann@google.com
 per-file DefaultPermissionGrantPolicy.java = zhanghai@google.com
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 0669581..e486f08 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -2590,6 +2590,7 @@
         boolean runtimePermissionsRevoked = false;
         int[] updatedUserIds = EMPTY_INT_ARRAY;
 
+        ArraySet<String> isPrivilegedPermissionAllowlisted = null;
         ArraySet<String> shouldGrantSignaturePermission = null;
         ArraySet<String> shouldGrantInternalPermission = null;
         final List<String> requestedPermissions = pkg.getRequestedPermissions();
@@ -2604,7 +2605,14 @@
             if (permission == null) {
                 continue;
             }
-            if (permission.isSignature() && (shouldGrantSignaturePermission(pkg, permission)
+            if (permission.isPrivileged()
+                    && checkPrivilegedPermissionAllowlist(pkg, ps, permission)) {
+                if (isPrivilegedPermissionAllowlisted == null) {
+                    isPrivilegedPermissionAllowlisted = new ArraySet<>();
+                }
+                isPrivilegedPermissionAllowlisted.add(permissionName);
+            }
+            if (permission.isSignature() && (shouldGrantPermissionBySignature(pkg, permission)
                     || shouldGrantPermissionByProtectionFlags(pkg, ps, permission))) {
                 if (shouldGrantSignaturePermission == null) {
                     shouldGrantSignaturePermission = new ArraySet<>();
@@ -2830,13 +2838,17 @@
 
                     if ((bp.isNormal() && shouldGrantNormalPermission)
                             || (bp.isSignature()
-                                    && ((shouldGrantSignaturePermission != null
-                                            && shouldGrantSignaturePermission.contains(permName))
+                                    && (!bp.isPrivileged() || CollectionUtils.contains(
+                                            isPrivilegedPermissionAllowlisted, permName))
+                                    && (CollectionUtils.contains(shouldGrantSignaturePermission,
+                                            permName)
                                             || ((bp.isDevelopment() || bp.isRole())
                                                     && origState.isPermissionGranted(permName))))
                             || (bp.isInternal()
-                                    && ((shouldGrantInternalPermission != null
-                                            && shouldGrantInternalPermission.contains(permName))
+                                    && (!bp.isPrivileged() || CollectionUtils.contains(
+                                            isPrivilegedPermissionAllowlisted, permName))
+                                    && (CollectionUtils.contains(shouldGrantInternalPermission,
+                                            permName)
                                             || ((bp.isDevelopment() || bp.isRole())
                                                     && origState.isPermissionGranted(permName))))) {
                         // Grant an install permission.
@@ -3343,7 +3355,92 @@
         return allowed;
     }
 
-    private boolean shouldGrantSignaturePermission(@NonNull AndroidPackage pkg,
+    private boolean checkPrivilegedPermissionAllowlist(@NonNull AndroidPackage pkg,
+            @NonNull PackageSetting packageSetting, @NonNull Permission permission) {
+        if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
+            return true;
+        }
+        final String packageName = pkg.getPackageName();
+        if (Objects.equals(packageName, PLATFORM_PACKAGE_NAME)) {
+            return true;
+        }
+        if (!pkg.isPrivileged()) {
+            return true;
+        }
+        if (!Objects.equals(permission.getPackageName(), PLATFORM_PACKAGE_NAME)) {
+            return true;
+        }
+        final String permissionName = permission.getName();
+        if (isInSystemConfigPrivAppPermissions(pkg, permissionName)) {
+            return true;
+        }
+        // Only enforce the allowlist on boot
+        if (!mSystemReady
+                // Updated system apps do not need to be allowlisted
+                && !packageSetting.getPkgState().isUpdatedSystemApp()) {
+            final ApexManager apexManager = ApexManager.getInstance();
+            final String containingApexPackageName =
+                    apexManager.getActiveApexPackageNameContainingPackage(packageName);
+            final boolean isInUpdatedApex = containingApexPackageName != null
+                    && !apexManager.isFactory(apexManager.getPackageInfo(containingApexPackageName,
+                    MATCH_ACTIVE_PACKAGE));
+            // Apps that are in updated apexs' do not need to be allowlisted
+            if (!isInUpdatedApex) {
+                // it's only a reportable violation if the permission isn't explicitly
+                // denied
+                if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName)) {
+                    return false;
+                }
+                Slog.w(TAG, "Privileged permission " + permissionName + " for package "
+                        + packageName + " (" + pkg.getPath()
+                        + ") not in privapp-permissions allowlist");
+                if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
+                    synchronized (mLock) {
+                        if (mPrivappPermissionsViolations == null) {
+                            mPrivappPermissionsViolations = new ArraySet<>();
+                        }
+                        mPrivappPermissionsViolations.add(packageName + " (" + pkg.getPath() + "): "
+                                + permissionName);
+                    }
+                }
+            }
+        }
+        return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE;
+    }
+
+    private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg,
+            @NonNull String permission) {
+        final SystemConfig systemConfig = SystemConfig.getInstance();
+        final Set<String> permissions;
+        if (pkg.isVendor()) {
+            permissions = systemConfig.getVendorPrivAppPermissions(pkg.getPackageName());
+        } else if (pkg.isProduct()) {
+            permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName());
+        } else if (pkg.isSystemExt()) {
+            permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName());
+        } else {
+            permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName());
+        }
+        return CollectionUtils.contains(permissions, permission);
+    }
+
+    private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg,
+            @NonNull String permission) {
+        final SystemConfig systemConfig = SystemConfig.getInstance();
+        final Set<String> permissions;
+        if (pkg.isVendor()) {
+            permissions = systemConfig.getVendorPrivAppDenyPermissions(pkg.getPackageName());
+        } else if (pkg.isProduct()) {
+            permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName());
+        } else if (pkg.isSystemExt()) {
+            permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName());
+        } else {
+            permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName());
+        }
+        return CollectionUtils.contains(permissions, permission);
+    }
+
+    private boolean shouldGrantPermissionBySignature(@NonNull AndroidPackage pkg,
             @NonNull Permission bp) {
         // expect single system package
         String systemPackageName = ArrayUtils.firstOrNull(mPackageManagerInt.getKnownPackageNames(
@@ -3373,8 +3470,7 @@
     private boolean shouldGrantPermissionByProtectionFlags(@NonNull AndroidPackage pkg,
             @NonNull PackageSetting pkgSetting, @NonNull Permission bp) {
         boolean allowed = false;
-        final boolean isVendorPrivilegedPermission = bp.isVendorPrivileged();
-        final boolean isPrivilegedPermission = bp.isPrivileged() || isVendorPrivilegedPermission;
+        final boolean isPrivilegedPermission = bp.isPrivileged();
         final boolean isOemPermission = bp.isOem();
         if (!allowed && (isPrivilegedPermission || isOemPermission) && pkg.isSystem()) {
             final String permissionName = bp.getName();
@@ -3386,19 +3482,18 @@
                 final AndroidPackage disabledPkg = disabledPs == null ? null : disabledPs.pkg;
                 if (disabledPkg != null && disabledPkg.getRequestedPermissions().contains(
                         permissionName)) {
-                    allowed = (isPrivilegedPermission && canGrantPrivilegedPermission(disabledPkg,
-                            true, bp)) || (isOemPermission && canGrantOemPermission(disabledPkg,
+                    allowed = (isPrivilegedPermission && disabledPkg.isPrivileged())
+                            || (isOemPermission && canGrantOemPermission(disabledPkg,
                             permissionName));
                 }
             } else {
-                allowed = (isPrivilegedPermission && canGrantPrivilegedPermission(pkg, false, bp))
+                allowed = (isPrivilegedPermission && pkg.isPrivileged())
                         || (isOemPermission && canGrantOemPermission(pkg, 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 && !isVendorPrivilegedPermission
-                    && pkg.isVendor()) {
+            if (allowed && isPrivilegedPermission && !bp.isVendorPrivileged() && pkg.isVendor()) {
                 Slog.w(TAG, "Permission " + permissionName
                         + " cannot be granted to privileged vendor apk " + pkg.getPackageName()
                         + " because it isn't a 'vendorPrivileged' permission.");
@@ -3541,90 +3636,6 @@
         return mPackageManagerInt.getPackageSetting(sourcePackageName);
     }
 
-    private boolean canGrantPrivilegedPermission(@NonNull AndroidPackage pkg,
-            boolean isUpdatedSystemApp, @NonNull Permission permission) {
-        if (!pkg.isPrivileged()) {
-            return false;
-        }
-        final boolean isPlatformPermission = PLATFORM_PACKAGE_NAME.equals(
-                permission.getPackageName());
-        if (!isPlatformPermission) {
-            return true;
-        }
-        if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
-            return true;
-        }
-        final String permissionName = permission.getName();
-        if (isInSystemConfigPrivAppPermissions(pkg, permissionName)) {
-            return true;
-        }
-        // Only enforce the allowlist on boot
-        if (!mSystemReady
-                // Updated system apps do not need to be allowlisted
-                && !isUpdatedSystemApp) {
-            final ApexManager apexManager = ApexManager.getInstance();
-            final String packageName = pkg.getPackageName();
-            final String containingApexPackageName =
-                    apexManager.getActiveApexPackageNameContainingPackage(packageName);
-            final boolean isInUpdatedApex = containingApexPackageName != null
-                    && !apexManager.isFactory(apexManager.getPackageInfo(containingApexPackageName,
-                    MATCH_ACTIVE_PACKAGE));
-            // Apps that are in updated apexs' do not need to be allowlisted
-            if (!isInUpdatedApex) {
-                // it's only a reportable violation if the permission isn't explicitly
-                // denied
-                if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName)) {
-                    return false;
-                }
-                Slog.w(TAG, "Privileged permission " + permissionName + " for package "
-                        + packageName + " (" + pkg.getPath()
-                        + ") not in privapp-permissions allowlist");
-                if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
-                    synchronized (mLock) {
-                        if (mPrivappPermissionsViolations == null) {
-                            mPrivappPermissionsViolations = new ArraySet<>();
-                        }
-                        mPrivappPermissionsViolations.add(packageName + " (" + pkg.getPath() + "): "
-                                + permissionName);
-                    }
-                }
-            }
-        }
-        return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE;
-    }
-
-    private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg,
-            @NonNull String permission) {
-        final SystemConfig systemConfig = SystemConfig.getInstance();
-        final Set<String> permissions;
-        if (pkg.isVendor()) {
-            permissions = systemConfig.getVendorPrivAppPermissions(pkg.getPackageName());
-        } else if (pkg.isProduct()) {
-            permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName());
-        } else if (pkg.isSystemExt()) {
-            permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName());
-        } else {
-            permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName());
-        }
-        return permissions != null && permissions.contains(permission);
-    }
-
-    private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg,
-            @NonNull String permission) {
-        final SystemConfig systemConfig = SystemConfig.getInstance();
-        final Set<String> permissions;
-        if (pkg.isVendor()) {
-            permissions = systemConfig.getVendorPrivAppDenyPermissions(pkg.getPackageName());
-        } else if (pkg.isProduct()) {
-            permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName());
-        } else if (pkg.isSystemExt()) {
-            permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName());
-        } else {
-            permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName());
-        }
-        return permissions != null && permissions.contains(permission);
-    }
-
     private static boolean canGrantOemPermission(AndroidPackage pkg, String permission) {
         if (!pkg.isOem()) {
             return false;
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index ac358db..4e1065a 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -17,6 +17,7 @@
 package com.android.server.policy;
 
 import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -84,7 +85,8 @@
     private static final BooleanSupplier TRUE_BOOLEAN_SUPPLIER = () -> true;
 
     @VisibleForTesting
-    static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(0, "DEFAULT");
+    static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(MINIMUM_DEVICE_STATE,
+            "DEFAULT");
 
     private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/";
     private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/";
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 8e0d632..88fdc4a 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -90,11 +90,11 @@
 import android.view.Display;
 import android.view.KeyEvent;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index ea41980..b7285d5 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -73,6 +73,8 @@
     private TimerTrigger mTimerTrigger;
     @Nullable
     private StatsPullAtomCallbackImpl mPullAtomCallback;
+    @Nullable
+    private PowerStatsInternal mPowerStatsInternal;
 
     @VisibleForTesting
     static class Injector {
@@ -125,8 +127,8 @@
         }
 
         StatsPullAtomCallbackImpl createStatsPullerImpl(Context context,
-                IPowerStatsHALWrapper powerStatsHALWrapper) {
-            return new StatsPullAtomCallbackImpl(context, powerStatsHALWrapper);
+                PowerStatsInternal powerStatsInternal) {
+            return new StatsPullAtomCallbackImpl(context, powerStatsInternal);
         }
     }
 
@@ -175,21 +177,14 @@
     @Override
     public void onStart() {
         if (getPowerStatsHal().isInitialized()) {
-            // Only create internal service if PowerStatsHal is available.
-            publishLocalService(PowerStatsInternal.class, new LocalService());
+            mPowerStatsInternal = new LocalService();
+            publishLocalService(PowerStatsInternal.class, mPowerStatsInternal);
         }
         publishBinderService(Context.POWER_STATS_SERVICE, new BinderService());
     }
 
     private void onSystemServicesReady() {
-        if (getPowerStatsHal().isInitialized()) {
-            if (DEBUG) Slog.d(TAG, "Starting PowerStatsService statsd pullers");
-
-            // Only start statsd pullers if initialization is successful.
-            mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, getPowerStatsHal());
-        } else {
-            Slog.e(TAG, "Failed to start PowerStatsService statsd pullers");
-        }
+        mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, mPowerStatsInternal);
     }
 
     private void onBootCompleted() {
diff --git a/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java b/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
index 7c6999a..bdabefb 100644
--- a/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
+++ b/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
@@ -24,26 +24,31 @@
 import android.hardware.power.stats.State;
 import android.hardware.power.stats.StateResidency;
 import android.hardware.power.stats.StateResidencyResult;
+import android.power.PowerStatsInternal;
+import android.util.Slog;
 import android.util.StatsEvent;
 
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
 
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 /**
  * StatsPullAtomCallbackImpl is responsible implementing the stats pullers for
  * SUBSYSTEM_SLEEP_STATE and ON_DEVICE_POWER_MEASUREMENT statsd atoms.
  */
 public class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback {
+    private static final String TAG = StatsPullAtomCallbackImpl.class.getSimpleName();
     private Context mContext;
-    private IPowerStatsHALWrapper mPowerStatsHALWrapper;
+    private PowerStatsInternal mPowerStatsInternal;
     private Map<Integer, Channel> mChannels = new HashMap();
     private Map<Integer, String> mEntityNames = new HashMap();
-    private Map<Integer, Map<Integer, String>> mStateNames = new HashMap();;
+    private Map<Integer, Map<Integer, String>> mStateNames = new HashMap();
+    private static final int STATS_PULL_TIMEOUT_MILLIS = 2000;
+    private static final boolean DEBUG = false;
 
     @Override
     public int onPullAtom(int atomTag, List<StatsEvent> data) {
@@ -57,21 +62,28 @@
         }
     }
 
-    private void initPullOnDevicePowerMeasurement() {
-        Channel[] channels = mPowerStatsHALWrapper.getEnergyMeterInfo();
-        if (channels == null) {
-            return;
+    private boolean initPullOnDevicePowerMeasurement() {
+        Channel[] channels = mPowerStatsInternal.getEnergyMeterInfo();
+        if (channels == null || channels.length == 0) {
+            Slog.e(TAG, "Failed to init OnDevicePowerMeasurement puller");
+            return false;
         }
 
         for (int i = 0; i < channels.length; i++) {
             final Channel channel = channels[i];
             mChannels.put(channel.id, channel);
         }
+
+        return true;
     }
 
     private int pullOnDevicePowerMeasurement(int atomTag, List<StatsEvent> events) {
-        EnergyMeasurement[] energyMeasurements = mPowerStatsHALWrapper.readEnergyMeter(new int[0]);
-        if (energyMeasurements == null) {
+        final EnergyMeasurement[] energyMeasurements;
+        try {
+            energyMeasurements = mPowerStatsInternal.readEnergyMeterAsync(new int[0])
+                    .get(STATS_PULL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (Exception e) {
+            Slog.e(TAG, "Failed to readEnergyMeterAsync", e);
             return StatsManager.PULL_SKIP;
         }
 
@@ -91,10 +103,11 @@
         return StatsManager.PULL_SUCCESS;
     }
 
-    private void initSubsystemSleepState() {
-        PowerEntity[] entities = mPowerStatsHALWrapper.getPowerEntityInfo();
-        if (entities == null) {
-            return;
+    private boolean initSubsystemSleepState() {
+        PowerEntity[] entities = mPowerStatsInternal.getPowerEntityInfo();
+        if (entities == null || entities.length == 0) {
+            Slog.e(TAG, "Failed to init SubsystemSleepState puller");
+            return false;
         }
 
         for (int i = 0; i < entities.length; i++) {
@@ -108,13 +121,20 @@
             mEntityNames.put(entity.id, entity.name);
             mStateNames.put(entity.id, states);
         }
+
+        return true;
     }
 
     private int pullSubsystemSleepState(int atomTag, List<StatsEvent> events) {
-        StateResidencyResult[] results =  mPowerStatsHALWrapper.getStateResidency(new int[0]);
-        if (results == null) {
+        final StateResidencyResult[] results;
+        try {
+            results = mPowerStatsInternal.getStateResidencyAsync(new int[0])
+                    .get(STATS_PULL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (Exception e) {
+            Slog.e(TAG, "Failed to getStateResidencyAsync", e);
             return StatsManager.PULL_SKIP;
         }
+
         for (int i = 0; i < results.length; i++) {
             final StateResidencyResult result = results[i];
             for (int j = 0; j < result.stateResidencyData.length; j++) {
@@ -131,22 +151,33 @@
         return StatsManager.PULL_SUCCESS;
     }
 
-    public StatsPullAtomCallbackImpl(Context context, IPowerStatsHALWrapper powerStatsHALWrapper) {
+    public StatsPullAtomCallbackImpl(Context context, PowerStatsInternal powerStatsInternal) {
+        if (DEBUG) Slog.d(TAG, "Starting PowerStatsService statsd pullers");
+
         mContext = context;
-        mPowerStatsHALWrapper = powerStatsHALWrapper;
-        initPullOnDevicePowerMeasurement();
-        initSubsystemSleepState();
+        mPowerStatsInternal = powerStatsInternal;
+
+        if (powerStatsInternal == null) {
+            Slog.e(TAG, "Failed to start PowerStatsService statsd pullers");
+            return;
+        }
 
         StatsManager manager = mContext.getSystemService(StatsManager.class);
-        manager.setPullAtomCallback(
-                FrameworkStatsLog.SUBSYSTEM_SLEEP_STATE,
-                null, // use default PullAtomMetadata values
-                ConcurrentUtils.DIRECT_EXECUTOR,
-                this);
-        manager.setPullAtomCallback(
-                FrameworkStatsLog.ON_DEVICE_POWER_MEASUREMENT,
-                null, // use default PullAtomMetadata values
-                ConcurrentUtils.DIRECT_EXECUTOR,
-                this);
+
+        if (initPullOnDevicePowerMeasurement()) {
+            manager.setPullAtomCallback(
+                    FrameworkStatsLog.ON_DEVICE_POWER_MEASUREMENT,
+                    null, // use default PullAtomMetadata values
+                    ConcurrentUtils.DIRECT_EXECUTOR,
+                    this);
+        }
+
+        if (initSubsystemSleepState()) {
+            manager.setPullAtomCallback(
+                    FrameworkStatsLog.SUBSYSTEM_SLEEP_STATE,
+                    null, // use default PullAtomMetadata values
+                    ConcurrentUtils.DIRECT_EXECUTOR,
+                    this);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/role/OWNERS b/services/core/java/com/android/server/role/OWNERS
index b94d988..31e3549 100644
--- a/services/core/java/com/android/server/role/OWNERS
+++ b/services/core/java/com/android/server/role/OWNERS
@@ -1,5 +1,4 @@
 svetoslavganov@google.com
-moltmann@google.com
 zhanghai@google.com
 evanseverson@google.com
 eugenesusla@google.com
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 539b413..0d43600 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -1612,9 +1612,6 @@
         // Aggregate times for the same uids.
         SparseArray<long[]> aggregated = new SparseArray<>();
         mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
-            // For uids known to be aggregated from many entries allow mutating in place to avoid
-            // many copies. Otherwise, copy before aggregating.
-            boolean mutateInPlace = false;
             if (UserHandle.isIsolated(uid)) {
                 // Skip individual isolated uids because they are recycled and quickly removed from
                 // the underlying data source.
@@ -1622,26 +1619,18 @@
             } else if (UserHandle.isSharedAppGid(uid)) {
                 // All shared app gids are accounted together.
                 uid = LAST_SHARED_APPLICATION_GID;
-                mutateInPlace = true;
             } else {
                 // Everything else is accounted under their base uid.
                 uid = UserHandle.getAppId(uid);
             }
 
             long[] aggCpuFreqTimeMs = aggregated.get(uid);
-            if (aggCpuFreqTimeMs != null) {
-                if (!mutateInPlace) {
-                    aggCpuFreqTimeMs = Arrays.copyOf(aggCpuFreqTimeMs, cpuFreqTimeMs.length);
-                    aggregated.put(uid, aggCpuFreqTimeMs);
-                }
-                for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
-                    aggCpuFreqTimeMs[freqIndex] += cpuFreqTimeMs[freqIndex];
-                }
-            } else {
-                if (mutateInPlace) {
-                    cpuFreqTimeMs = Arrays.copyOf(cpuFreqTimeMs, cpuFreqTimeMs.length);
-                }
-                aggregated.put(uid, cpuFreqTimeMs);
+            if (aggCpuFreqTimeMs == null) {
+                aggCpuFreqTimeMs = new long[cpuFreqTimeMs.length];
+                aggregated.put(uid, aggCpuFreqTimeMs);
+            }
+            for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
+                aggCpuFreqTimeMs[freqIndex] += cpuFreqTimeMs[freqIndex];
             }
         });
 
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index bbbd19f..b210339 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
 import android.app.timedetector.GnssTimeSuggestion;
 import android.app.timedetector.ITimeDetectorService;
 import android.app.timedetector.ManualTimeSuggestion;
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index 6a4c276..792f372 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -18,7 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
 import android.app.timedetector.GnssTimeSuggestion;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 7cd4184..c4c620c 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -23,7 +23,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AlarmManager;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
 import android.app.timedetector.GnssTimeSuggestion;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index bee66637..04dac7c 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -92,13 +92,6 @@
     @GuardedBy("mLock")
     private boolean mForceStop;
 
-    // TODO(b/159207608): Remove this constructor once VibratorService is removed
-    public VibrationThread(Vibration vib, VibratorController vibrator,
-            PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
-            VibrationCallbacks callbacks) {
-        this(vib, toSparseArray(vibrator), wakeLock, batteryStatsService, callbacks);
-    }
-
     public VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators,
             PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
             VibrationCallbacks callbacks) {
@@ -286,12 +279,6 @@
         return filteredEffects;
     }
 
-    private static SparseArray<VibratorController> toSparseArray(VibratorController controller) {
-        SparseArray<VibratorController> array = new SparseArray<>(1);
-        array.put(controller.getVibratorInfo().getId(), controller);
-        return array;
-    }
-
     /**
      * Get the duration the vibrator will be on for given {@code waveform}, starting at {@code
      * startIndex} until the next time it's vibrating amplitude is zero.
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 3456e51..79f8229 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -772,11 +772,6 @@
             newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true);
         }
         newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT, new IntentSender(target));
-        ActivityOptions options = mRequest.activityOptions.getOptions(mRequest.intent,
-                mRequest.activityInfo,
-                mService.getProcessController(mRequest.caller),
-                mSupervisor);
-        newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_ACTIVITY_OPTIONS, options.toBundle());
         heavy.updateIntentForHeavyWeightActivity(newIntent);
         newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP,
                 mRequest.activityInfo.packageName);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index bd93e04..ceebe95 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -847,8 +847,6 @@
         mWmService.openSurfaceTransaction();
         try {
             applySurfaceChangesTransaction();
-            // Send any pending task-info changes that were queued-up during a layout deferment
-            mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
             mWmService.mSyncEngine.onSurfacePlacement();
         } catch (RuntimeException e) {
             Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
@@ -861,6 +859,8 @@
             }
         }
 
+        // Send any pending task-info changes that were queued-up during a layout deferment
+        mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
         mWmService.mAnimator.executeAfterPrepareSurfacesRunnables();
 
         checkAppTransitionReady(surfacePlacer);
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 1c4b034..a7abf6a 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -149,7 +149,7 @@
         "android.hardware.power@1.1",
         "android.hardware.power-V1-cpp",
         "android.hardware.power.stats@1.0",
-        "android.hardware.power.stats-ndk_platform",
+        "android.hardware.power.stats-V1-ndk_platform",
         "android.hardware.thermal@1.0",
         "android.hardware.tv.input@1.0",
         "android.hardware.vibrator-V2-cpp",
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index 4e47984..a6029cd 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -73,10 +73,6 @@
               static_cast<uint8_t>(aidl::Effect::TEXTURE_TICK));
 
 static std::shared_ptr<vibrator::HalController> findVibrator(int32_t vibratorId) {
-    // TODO(b/167946816): remove this once VibratorService is removed.
-    if (vibratorId < 0) {
-        return std::move(std::make_unique<vibrator::HalController>());
-    }
     vibrator::ManagerHalController* manager = android_server_VibratorManagerService_getManager();
     if (manager == nullptr) {
         return nullptr;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 07eb7bf..b063e67 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -62,6 +62,7 @@
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
 import static android.app.admin.DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
@@ -92,7 +93,6 @@
 import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED;
 import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED;
 import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_STARTING_PROFILE_FAILED;
-import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE;
 import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
 import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
 import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
@@ -157,9 +157,9 @@
 import android.app.admin.DevicePolicyEventLogger;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
 import android.app.admin.DevicePolicyManager.PasswordComplexity;
 import android.app.admin.DevicePolicyManager.PersonalAppsSuspensionReason;
-import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.DevicePolicySafetyChecker;
 import android.app.admin.DeviceStateCache;
@@ -1101,7 +1101,7 @@
      */
     private void checkCanExecuteOrThrowUnsafe(@DevicePolicyOperation int operation) {
         int reason = getUnsafeOperationReason(operation);
-        if (reason == UNSAFE_OPERATION_REASON_NONE) return;
+        if (reason == OPERATION_SAFETY_REASON_NONE) return;
 
         if (mSafetyChecker == null) {
             // Happens on CTS after it's set just once (by OneTimeSafetyChecker)
@@ -1114,23 +1114,28 @@
     /**
      * Returns whether it's safe to execute the given {@code operation}, and why.
      */
-    @UnsafeOperationReason
+    @OperationSafetyReason
     int getUnsafeOperationReason(@DevicePolicyOperation int operation) {
-        return mSafetyChecker == null ? UNSAFE_OPERATION_REASON_NONE
+        return mSafetyChecker == null ? OPERATION_SAFETY_REASON_NONE
                 : mSafetyChecker.getUnsafeOperationReason(operation);
     }
 
     @Override
     public void setNextOperationSafety(@DevicePolicyOperation int operation,
-            @UnsafeOperationReason int reason) {
+            @OperationSafetyReason int reason) {
         Preconditions.checkCallAuthorization(
                 hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
         Slog.i(LOG_TAG, String.format("setNextOperationSafety(%s, %s)",
                 DevicePolicyManager.operationToString(operation),
-                DevicePolicyManager.unsafeOperationReasonToString(reason)));
+                DevicePolicyManager.operationSafetyReasonToString(reason)));
         mSafetyChecker = new OneTimeSafetyChecker(this, operation, reason);
     }
 
+    @Override
+    public boolean isSafeOperation(@OperationSafetyReason int reason) {
+        return mSafetyChecker == null ? true : mSafetyChecker.isSafeOperation(reason);
+    }
+
     // Used by DevicePolicyManagerServiceShellCommand
     List<OwnerDto> listAllOwners() {
         Preconditions.checkCallAuthorization(
@@ -7522,19 +7527,23 @@
         sendActiveAdminCommand(action, extras, deviceOwnerUserId, receiverComponent);
     }
 
-    private void sendProfileOwnerCommand(String action, Bundle extras, int userHandle) {
-        sendActiveAdminCommand(action, extras, userHandle,
-                mOwners.getProfileOwnerComponent(userHandle));
+    private void sendProfileOwnerCommand(String action, Bundle extras, @UserIdInt int userId) {
+        sendActiveAdminCommand(action, extras, userId,
+                mOwners.getProfileOwnerComponent(userId));
     }
 
     private void sendActiveAdminCommand(String action, Bundle extras,
-            int userHandle, ComponentName receiverComponent) {
+            @UserIdInt int userId, ComponentName receiverComponent) {
+        if (VERBOSE_LOG) {
+            Slog.v(LOG_TAG, "sending intent " + action + " to "
+                    + receiverComponent.flattenToShortString() + " on user " + userId);
+        }
         final Intent intent = new Intent(action);
         intent.setComponent(receiverComponent);
         if (extras != null) {
             intent.putExtras(extras);
         }
-        mContext.sendBroadcastAsUser(intent, UserHandle.of(userHandle));
+        mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
     }
 
     private void sendOwnerChangedBroadcast(String broadcast, int userId) {
@@ -12224,6 +12233,32 @@
                             packageName, findInteractAcrossProfilesResetMode(packageName), userId);
         }
 
+        @Override
+        public void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker, int reason,
+                boolean isSafe) {
+            // TODO(b/178494483): use EventLog instead
+            // TODO(b/178494483): log metrics?
+            if (VERBOSE_LOG) {
+                Slog.v(LOG_TAG, String.format("notifyUnsafeOperationStateChanged(): %s=%b",
+                        DevicePolicyManager.operationSafetyReasonToString(reason), isSafe));
+            }
+
+            Preconditions.checkArgument(mSafetyChecker == checker,
+                    "invalid checker: should be %s, was %s", mSafetyChecker, checker);
+
+            Bundle extras = new Bundle();
+            extras.putInt(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_REASON, reason);
+            extras.putBoolean(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_STATE, isSafe);
+
+            // TODO(b/178494483): add CTS test
+            sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED,
+                    extras);
+            for (int profileOwnerId : mOwners.getProfileOwnerKeys()) {
+                sendProfileOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED,
+                        extras, profileOwnerId);
+            }
+        }
+
         private @Mode int findInteractAcrossProfilesResetMode(String packageName) {
             return getDefaultCrossProfilePackages().contains(packageName)
                     ? AppOpsManager.MODE_ALLOWED
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
index 222c987..5484a14 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
@@ -27,6 +27,7 @@
 final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
 
     private static final String CMD_IS_SAFE_OPERATION = "is-operation-safe";
+    private static final String CMD_IS_SAFE_OPERATION_BY_REASON = "is-operation-safe-by-reason";
     private static final String CMD_SET_SAFE_OPERATION = "set-operation-safe";
     private static final String CMD_LIST_OWNERS = "list-owners";
 
@@ -53,6 +54,8 @@
             switch (cmd) {
                 case CMD_IS_SAFE_OPERATION:
                     return runIsSafeOperation(pw);
+                case CMD_IS_SAFE_OPERATION_BY_REASON:
+                    return runIsSafeOperationByReason(pw);
                 case CMD_SET_SAFE_OPERATION:
                     return runSetSafeOperation(pw);
                 case CMD_LIST_OWNERS:
@@ -73,12 +76,13 @@
         return -1;
     }
 
-
     private void showHelp(PrintWriter pw) {
         pw.printf("  help\n");
         pw.printf("    Prints this help text.\n\n");
         pw.printf("  %s <OPERATION_ID>\n", CMD_IS_SAFE_OPERATION);
         pw.printf("    Checks if the give operation is safe \n\n");
+        pw.printf("  %s <REASON_ID>\n", CMD_IS_SAFE_OPERATION_BY_REASON);
+        pw.printf("    Checks if the operations are safe for the given reason\n\n");
         pw.printf("  %s <OPERATION_ID> <REASON_ID>\n", CMD_SET_SAFE_OPERATION);
         pw.printf("    Emulates the result of the next call to check if the given operation is safe"
                 + " \n\n");
@@ -89,10 +93,19 @@
     private int runIsSafeOperation(PrintWriter pw) {
         int operation = Integer.parseInt(getNextArgRequired());
         int reason = mService.getUnsafeOperationReason(operation);
-        boolean safe = reason == DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE;
+        boolean safe = reason == DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
         pw.printf("Operation %s is %b. Reason: %s\n",
                 DevicePolicyManager.operationToString(operation), safe,
-                DevicePolicyManager.unsafeOperationReasonToString(reason));
+                DevicePolicyManager.operationSafetyReasonToString(reason));
+        return 0;
+    }
+
+    private int runIsSafeOperationByReason(PrintWriter pw) {
+        int reason = Integer.parseInt(getNextArgRequired());
+        boolean safe = mService.isSafeOperation(reason);
+        pw.printf("Operations affected by %s are %s\n",
+                DevicePolicyManager.operationSafetyReasonToString(reason),
+                (safe ? "SAFE" : "UNSAFE"));
         return 0;
     }
 
@@ -102,7 +115,7 @@
         mService.setNextOperationSafety(operation, reason);
         pw.printf("Next call to check operation %s will return %s\n",
                 DevicePolicyManager.operationToString(operation),
-                DevicePolicyManager.unsafeOperationReasonToString(reason));
+                DevicePolicyManager.operationSafetyReasonToString(reason));
         return 0;
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
index 883f95d..7de1bd5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
@@ -15,16 +15,18 @@
  */
 package com.android.server.devicepolicy;
 
-import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE;
+import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
+import static android.app.admin.DevicePolicyManager.operationSafetyReasonToString;
 import static android.app.admin.DevicePolicyManager.operationToString;
-import static android.app.admin.DevicePolicyManager.unsafeOperationReasonToString;
 
 import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
-import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.DevicePolicySafetyChecker;
 import android.util.Slog;
 
 import com.android.internal.os.IResultReceiver;
+import com.android.server.LocalServices;
 
 import java.util.Objects;
 
@@ -43,10 +45,10 @@
     private final DevicePolicyManagerService mService;
     private final DevicePolicySafetyChecker mRealSafetyChecker;
     private final @DevicePolicyOperation int mOperation;
-    private final @UnsafeOperationReason int mReason;
+    private final @OperationSafetyReason int mReason;
 
     OneTimeSafetyChecker(DevicePolicyManagerService service,
-            @DevicePolicyOperation int operation, @UnsafeOperationReason int reason) {
+            @DevicePolicyOperation int operation, @OperationSafetyReason int reason) {
         mService = Objects.requireNonNull(service);
         mOperation = operation;
         mReason = reason;
@@ -55,24 +57,42 @@
     }
 
     @Override
-    @UnsafeOperationReason
+    @OperationSafetyReason
     public int getUnsafeOperationReason(@DevicePolicyOperation int operation) {
         String name = operationToString(operation);
-        int reason = UNSAFE_OPERATION_REASON_NONE;
+        Slog.i(TAG, "getUnsafeOperationReason(" + name + ")");
+        int reason = OPERATION_SAFETY_REASON_NONE;
         if (operation == mOperation) {
             reason = mReason;
         } else {
             Slog.wtf(TAG, "invalid call to isDevicePolicyOperationSafe(): asked for " + name
                     + ", should be " + operationToString(mOperation));
         }
-        Slog.i(TAG, "getDevicePolicyOperationSafety(" + name + "): returning "
-                + unsafeOperationReasonToString(reason)
+        String reasonName = operationSafetyReasonToString(reason);
+        DevicePolicyManagerInternal dpmi = LocalServices
+                .getService(DevicePolicyManagerInternal.class);
+
+        Slog.i(TAG, "notifying " + reasonName + " is active");
+        dpmi.notifyUnsafeOperationStateChanged(this, reason, true);
+
+        Slog.i(TAG, "notifying " + reasonName + " is inactive");
+        dpmi.notifyUnsafeOperationStateChanged(this, reason, false);
+
+        Slog.i(TAG, "returning " + reasonName
                 + " and restoring DevicePolicySafetyChecker to " + mRealSafetyChecker);
         mService.setDevicePolicySafetyCheckerUnchecked(mRealSafetyChecker);
         return reason;
     }
 
     @Override
+    public boolean isSafeOperation(@OperationSafetyReason int reason) {
+        boolean safe = mReason != reason;
+        Slog.i(TAG, "isSafeOperation(" + operationSafetyReasonToString(reason) + "): " + safe);
+
+        return safe;
+    }
+
+    @Override
     public void onFactoryReset(IResultReceiver callback) {
         throw new UnsupportedOperationException();
     }
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 56cb3d1..886c1e5 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -69,6 +69,14 @@
     static constexpr auto progressUpdateInterval = 1000ms;
     static constexpr auto perUidTimeoutOffset = progressUpdateInterval * 2;
     static constexpr auto minPerUidTimeout = progressUpdateInterval * 3;
+
+    // If DL was up and not crashing for 10mins, we consider it healthy and reset all delays.
+    static constexpr auto healthyDataLoaderUptime = 10min;
+    // 10s, 100s (~2min), 1000s (~15min), 10000s (~3hrs)
+    static constexpr auto minBindDelay = 10s;
+    static constexpr auto maxBindDelay = 10000s;
+    static constexpr auto bindDelayMultiplier = 10;
+    static constexpr auto bindDelayJitterDivider = 10;
 };
 
 static const Constants& constants() {
@@ -386,6 +394,28 @@
     dprintf(fd, "}\n");
 }
 
+bool IncrementalService::needStartDataLoaderLocked(IncFsMount& ifs) {
+    if (ifs.dataLoaderStub->params().packageName == Constants::systemPackage) {
+        return true;
+    }
+
+    // Check all permanent binds.
+    for (auto&& [_, bindPoint] : ifs.bindPoints) {
+        if (bindPoint.kind != BindKind::Permanent) {
+            continue;
+        }
+        const auto progress = getLoadingProgressFromPath(ifs, bindPoint.sourceDir,
+                                                         /*stopOnFirstIncomplete=*/true);
+        if (!progress.isError() && !progress.fullyLoaded()) {
+            LOG(INFO) << "Non system mount: [" << bindPoint.sourceDir
+                      << "], partial progress: " << progress.getProgress() * 100 << "%";
+            return true;
+        }
+    }
+
+    return false;
+}
+
 void IncrementalService::onSystemReady() {
     if (mSystemReady.exchange(true)) {
         return;
@@ -396,8 +426,11 @@
         std::lock_guard l(mLock);
         mounts.reserve(mMounts.size());
         for (auto&& [id, ifs] : mMounts) {
-            if (ifs->mountId == id &&
-                ifs->dataLoaderStub->params().packageName == Constants::systemPackage) {
+            if (ifs->mountId != id) {
+                continue;
+            }
+
+            if (needStartDataLoaderLocked(*ifs)) {
                 mounts.push_back(ifs);
             }
         }
@@ -1539,6 +1572,11 @@
     return std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
 }
 
+template <class Duration>
+static constexpr auto castToMs(Duration d) {
+    return std::chrono::duration_cast<std::chrono::milliseconds>(d);
+}
+
 // Extract lib files from zip, create new files in incfs and write data to them
 // Lib files should be placed next to the APK file in the following matter:
 // Example:
@@ -2134,9 +2172,43 @@
                << status << " (current " << mCurrentStatus << ")";
 }
 
+Milliseconds IncrementalService::DataLoaderStub::updateBindDelay() {
+    std::unique_lock lock(mMutex);
+    const auto previousBindTs = mPreviousBindTs;
+    const auto now = Clock::now();
+    mPreviousBindTs = now;
+
+    const auto nonCrashingInterval = std::max(castToMs(now - previousBindTs), 100ms);
+    if (previousBindTs.time_since_epoch() == Clock::duration::zero() ||
+        nonCrashingInterval > Constants::healthyDataLoaderUptime) {
+        mPreviousBindDelay = 0ms;
+        return mPreviousBindDelay;
+    }
+
+    constexpr auto minBindDelayMs = castToMs(Constants::minBindDelay);
+    constexpr auto maxBindDelayMs = castToMs(Constants::maxBindDelay);
+
+    const auto bindDelayMs =
+            std::min(std::max(mPreviousBindDelay * Constants::bindDelayMultiplier, minBindDelayMs),
+                     maxBindDelayMs)
+                    .count();
+    const auto bindDelayJitterRangeMs = bindDelayMs / Constants::bindDelayJitterDivider;
+    const auto bindDelayJitterMs = rand() % (bindDelayJitterRangeMs * 2) - bindDelayJitterRangeMs;
+    mPreviousBindDelay = std::chrono::milliseconds(bindDelayMs + bindDelayJitterMs);
+
+    return mPreviousBindDelay;
+}
+
 bool IncrementalService::DataLoaderStub::bind() {
+    const auto bindDelay = updateBindDelay();
+    if (bindDelay > 1s) {
+        LOG(INFO) << "Delaying bind to " << mParams.packageName << " by "
+                  << bindDelay.count() / 1000 << "s";
+    }
+
     bool result = false;
-    auto status = mService.mDataLoaderManager->bindToDataLoader(id(), mParams, this, &result);
+    auto status = mService.mDataLoaderManager->bindToDataLoader(id(), mParams, bindDelay.count(),
+                                                                this, &result);
     if (!status.isOk() || !result) {
         LOG(ERROR) << "Failed to bind a data loader for mount " << id();
         return false;
@@ -2249,7 +2321,8 @@
 
         listener = mStatusListener;
 
-        if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE) {
+        if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE ||
+            mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE) {
             // For unavailable, unbind from DataLoader to ensure proper re-commit.
             setTargetStatusLocked(IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
         }
@@ -2544,6 +2617,9 @@
         dprintf(fd, "          blockIndex: %d\n", pendingRead.block);
         dprintf(fd, "          bootClockTsUs: %lld\n", (long long)pendingRead.bootClockTsUs);
     }
+    dprintf(fd, "        bind: %llds ago (delay: %llds)\n",
+            (long long)(elapsedMcs(mPreviousBindTs, Clock::now()) / 1000000),
+            (long long)(mPreviousBindDelay.count() / 1000));
     dprintf(fd, "      }\n");
     const auto& params = mParams;
     dprintf(fd, "      dataLoaderParams: {\n");
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 5d53bac..459ed32 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -245,7 +245,6 @@
         void setTargetStatusLocked(int status);
 
         bool fsmStep();
-        bool fsmStep(int currentStatus, int targetStatus);
 
         void onHealthStatus(StorageHealthListener healthListener, int healthStatus);
         void updateHealthStatus(bool baseline = false);
@@ -259,6 +258,8 @@
 
         BootClockTsUs getOldestPendingReadTs();
 
+        Milliseconds updateBindDelay();
+
         void registerForPendingReads();
         void unregisterFromPendingReads();
 
@@ -276,6 +277,9 @@
         int mTargetStatus = content::pm::IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
         TimePoint mTargetStatusTs = {};
 
+        TimePoint mPreviousBindTs = {};
+        Milliseconds mPreviousBindDelay = {};
+
         std::string mHealthPath;
         incfs::UniqueControl mHealthControl;
         struct {
@@ -370,6 +374,8 @@
     void addBindMountRecordLocked(IncFsMount& ifs, StorageId storage, std::string&& metadataName,
                                   std::string&& source, std::string&& target, BindKind kind);
 
+    bool needStartDataLoaderLocked(IncFsMount& ifs);
+
     DataLoaderStubPtr prepareDataLoader(IncFsMount& ifs,
                                         content::pm::DataLoaderParamsParcel&& params,
                                         const DataLoaderStatusListener* statusListener = nullptr,
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 25d3f77..659d650 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -70,9 +70,10 @@
     ~RealDataLoaderManager() = default;
     binder::Status bindToDataLoader(MountId mountId,
                                     const content::pm::DataLoaderParamsParcel& params,
+                                    int bindDelayMs,
                                     const sp<content::pm::IDataLoaderStatusListener>& listener,
                                     bool* _aidl_return) const final {
-        return mInterface->bindToDataLoader(mountId, params, listener, _aidl_return);
+        return mInterface->bindToDataLoader(mountId, params, bindDelayMs, listener, _aidl_return);
     }
     binder::Status getDataLoader(MountId mountId,
                                  sp<content::pm::IDataLoader>* _aidl_return) const final {
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index 71fd3ac..d60035a 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -63,7 +63,7 @@
 public:
     virtual ~DataLoaderManagerWrapper() = default;
     virtual binder::Status bindToDataLoader(
-            MountId mountId, const content::pm::DataLoaderParamsParcel& params,
+            MountId mountId, const content::pm::DataLoaderParamsParcel& params, int bindDelayMs,
             const sp<content::pm::IDataLoaderStatusListener>& listener, bool* result) const = 0;
     virtual binder::Status getDataLoader(MountId mountId,
                                          sp<content::pm::IDataLoader>* result) const = 0;
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 8713f9d..ab491ef 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -208,8 +208,9 @@
         EXPECT_TRUE(mDataLoaderHolder != nullptr);
     }
 
-    MOCK_CONST_METHOD4(bindToDataLoader,
+    MOCK_CONST_METHOD5(bindToDataLoader,
                        binder::Status(int32_t mountId, const DataLoaderParamsParcel& params,
+                                      int bindDelayMs,
                                       const sp<IDataLoaderStatusListener>& listener,
                                       bool* _aidl_return));
     MOCK_CONST_METHOD2(getDataLoader,
@@ -217,11 +218,11 @@
     MOCK_CONST_METHOD1(unbindFromDataLoader, binder::Status(int32_t mountId));
 
     void bindToDataLoaderSuccess() {
-        ON_CALL(*this, bindToDataLoader(_, _, _, _))
+        ON_CALL(*this, bindToDataLoader(_, _, _, _, _))
                 .WillByDefault(Invoke(this, &MockDataLoaderManager::bindToDataLoaderOk));
     }
     void bindToDataLoaderFails() {
-        ON_CALL(*this, bindToDataLoader(_, _, _, _))
+        ON_CALL(*this, bindToDataLoader(_, _, _, _, _))
                 .WillByDefault(Return(
                         (binder::Status::fromExceptionCode(1, String8("failed to prepare")))));
     }
@@ -234,6 +235,7 @@
                 .WillByDefault(Invoke(this, &MockDataLoaderManager::unbindFromDataLoaderOk));
     }
     binder::Status bindToDataLoaderOk(int32_t mountId, const DataLoaderParamsParcel& params,
+                                      int bindDelayMs,
                                       const sp<IDataLoaderStatusListener>& listener,
                                       bool* _aidl_return) {
         mId = mountId;
@@ -245,6 +247,40 @@
         }
         return binder::Status::ok();
     }
+    binder::Status bindToDataLoaderOkWith10sDelay(int32_t mountId,
+                                                  const DataLoaderParamsParcel& params,
+                                                  int bindDelayMs,
+                                                  const sp<IDataLoaderStatusListener>& listener,
+                                                  bool* _aidl_return) {
+        CHECK(1000 * 9 <= bindDelayMs && bindDelayMs <= 1000 * 11) << bindDelayMs;
+        return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+    }
+    binder::Status bindToDataLoaderOkWith100sDelay(int32_t mountId,
+                                                   const DataLoaderParamsParcel& params,
+                                                   int bindDelayMs,
+                                                   const sp<IDataLoaderStatusListener>& listener,
+                                                   bool* _aidl_return) {
+        CHECK(1000 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11) << bindDelayMs;
+        return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+    }
+    binder::Status bindToDataLoaderOkWith1000sDelay(int32_t mountId,
+                                                    const DataLoaderParamsParcel& params,
+                                                    int bindDelayMs,
+                                                    const sp<IDataLoaderStatusListener>& listener,
+                                                    bool* _aidl_return) {
+        CHECK(1000 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11 * 11) << bindDelayMs;
+        return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+    }
+    binder::Status bindToDataLoaderOkWith10000sDelay(int32_t mountId,
+                                                     const DataLoaderParamsParcel& params,
+                                                     int bindDelayMs,
+                                                     const sp<IDataLoaderStatusListener>& listener,
+                                                     bool* _aidl_return) {
+        CHECK(1000 * 9 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11 * 11 * 11)
+                << bindDelayMs;
+        return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+    }
+
     binder::Status getDataLoaderOk(int32_t mountId, sp<IDataLoader>* _aidl_return) {
         *_aidl_return = mDataLoader;
         return binder::Status::ok();
@@ -261,6 +297,9 @@
     void setDataLoaderStatusUnavailable() {
         mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE);
     }
+    void setDataLoaderStatusUnrecoverable() {
+        mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE);
+    }
     binder::Status unbindFromDataLoaderOk(int32_t id) {
         if (mDataLoader) {
             if (auto status = mDataLoader->destroy(id); !status.isOk()) {
@@ -676,7 +715,7 @@
 
 TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsFails) {
     mVold->mountIncFsFails();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
     TemporaryDir tempDir;
     int storageId =
             mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
@@ -686,7 +725,7 @@
 
 TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsInvalidControlParcel) {
     mVold->mountIncFsInvalidControlParcel();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
     TemporaryDir tempDir;
     int storageId =
@@ -698,7 +737,7 @@
 TEST_F(IncrementalServiceTest, testCreateStorageMakeFileFails) {
     mVold->mountIncFsSuccess();
     mIncFs->makeFileFails();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
     EXPECT_CALL(*mVold, unmountIncFs(_));
     TemporaryDir tempDir;
@@ -712,7 +751,7 @@
     mVold->mountIncFsSuccess();
     mIncFs->makeFileSuccess();
     mVold->bindMountFails();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
     EXPECT_CALL(*mVold, unmountIncFs(_));
     TemporaryDir tempDir;
@@ -727,7 +766,7 @@
     mIncFs->makeFileSuccess();
     mVold->bindMountSuccess();
     mDataLoaderManager->bindToDataLoaderFails();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(0);
     EXPECT_CALL(*mDataLoader, start(_)).Times(0);
@@ -755,11 +794,11 @@
     mIncrementalService->deleteStorage(storageId);
 }
 
-TEST_F(IncrementalServiceTest, testDataLoaderDestroyed) {
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(2);
+TEST_F(IncrementalServiceTest, testDataLoaderDestroyedAndDelayed) {
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(6);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
-    EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2);
-    EXPECT_CALL(*mDataLoader, start(_)).Times(2);
+    EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(6);
+    EXPECT_CALL(*mDataLoader, start(_)).Times(6);
     EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     TemporaryDir tempDir;
@@ -768,13 +807,38 @@
                                                IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
     mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {}, {}, {});
+
     // Simulated crash/other connection breakage.
+
+    ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+            .WillByDefault(Invoke(mDataLoaderManager,
+                                  &MockDataLoaderManager::bindToDataLoaderOkWith10sDelay));
+    mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+    ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+            .WillByDefault(Invoke(mDataLoaderManager,
+                                  &MockDataLoaderManager::bindToDataLoaderOkWith100sDelay));
+    mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+    ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+            .WillByDefault(Invoke(mDataLoaderManager,
+                                  &MockDataLoaderManager::bindToDataLoaderOkWith1000sDelay));
+    mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+    ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+            .WillByDefault(Invoke(mDataLoaderManager,
+                                  &MockDataLoaderManager::bindToDataLoaderOkWith10000sDelay));
+    mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+    ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+            .WillByDefault(Invoke(mDataLoaderManager,
+                                  &MockDataLoaderManager::bindToDataLoaderOkWith10000sDelay));
     mDataLoaderManager->setDataLoaderStatusDestroyed();
 }
 
 TEST_F(IncrementalServiceTest, testStartDataLoaderCreate) {
     mDataLoader->initializeCreateOkNoStatus();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoader, start(_)).Times(1);
@@ -793,7 +857,7 @@
 
 TEST_F(IncrementalServiceTest, testStartDataLoaderPendingStart) {
     mDataLoader->initializeCreateOkNoStatus();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoader, start(_)).Times(1);
@@ -811,7 +875,7 @@
 
 TEST_F(IncrementalServiceTest, testStartDataLoaderCreateUnavailable) {
     mDataLoader->initializeCreateOkNoStatus();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoader, start(_)).Times(0);
@@ -827,12 +891,30 @@
     mDataLoaderManager->setDataLoaderStatusUnavailable();
 }
 
+TEST_F(IncrementalServiceTest, testStartDataLoaderCreateUnrecoverable) {
+    mDataLoader->initializeCreateOkNoStatus();
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
+    EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoader, start(_)).Times(0);
+    EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
+    EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+    TemporaryDir tempDir;
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
+    ASSERT_GE(storageId, 0);
+    ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+                                                  {}, {}));
+    mDataLoaderManager->setDataLoaderStatusUnrecoverable();
+}
+
 TEST_F(IncrementalServiceTest, testStartDataLoaderRecreateOnPendingReads) {
     mIncFs->waitForPendingReadsSuccess();
     mIncFs->openMountSuccess();
     mDataLoader->initializeCreateOkNoStatus();
 
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(2);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(2);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(2);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2);
     EXPECT_CALL(*mDataLoader, start(_)).Times(0);
@@ -856,7 +938,7 @@
 TEST_F(IncrementalServiceTest, testStartDataLoaderUnhealthyStorage) {
     mIncFs->openMountSuccess();
 
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoader, start(_)).Times(1);
@@ -1406,7 +1488,7 @@
 }
 
 TEST_F(IncrementalServiceTest, testPerUidTimeoutsTooShort) {
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoader, start(_)).Times(1);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 97e7582..a0e5c5d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1291,7 +1291,6 @@
         t.traceBegin("startOtherServices");
 
         final Context context = mSystemContext;
-        VibratorService vibrator = null;
         DynamicSystemService dynamicSystem = null;
         IStorageManager storageManager = null;
         NetworkManagementService networkManagement = null;
@@ -1417,11 +1416,6 @@
             mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class);
             t.traceEnd();
 
-            t.traceBegin("StartVibratorService");
-            vibrator = new VibratorService(context);
-            ServiceManager.addService("vibrator", vibrator);
-            t.traceEnd();
-
             t.traceBegin("StartDynamicSystemService");
             dynamicSystem = new DynamicSystemService(context);
             ServiceManager.addService("dynamic_system", dynamicSystem);
@@ -2490,14 +2484,6 @@
 
         // It is now time to start up the app processes...
 
-        t.traceBegin("MakeVibratorServiceReady");
-        try {
-            vibrator.systemReady();
-        } catch (Throwable e) {
-            reportWtf("making Vibrator Service ready", e);
-        }
-        t.traceEnd();
-
         t.traceBegin("MakeLockSettingsServiceReady");
         if (lockSettings != null) {
             try {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
new file mode 100644
index 0000000..195cc01
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -0,0 +1,725 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.apex.ApexSessionInfo;
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode;
+import android.os.SystemProperties;
+import android.os.storage.IStorageManager;
+import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.content.PackageHelper;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.Preconditions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+@Presubmit
+@RunWith(JUnit4.class)
+public class StagingManagerTest {
+    @Rule
+    public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+    @Mock private Context mContext;
+    @Mock private IStorageManager mStorageManager;
+    @Mock private ApexManager mApexManager;
+
+    private File mTmpDir;
+    private StagingManager mStagingManager;
+
+    private MockitoSession mMockitoSession;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getSystemService(eq(Context.POWER_SERVICE))).thenReturn(null);
+
+        mMockitoSession = ExtendedMockito.mockitoSession()
+                    .strictness(Strictness.LENIENT)
+                    .mockStatic(SystemProperties.class)
+                    .mockStatic(PackageHelper.class)
+                    .startMocking();
+
+        when(mStorageManager.supportsCheckpoint()).thenReturn(true);
+        when(mStorageManager.needsCheckpoint()).thenReturn(true);
+        when(PackageHelper.getStorageManager()).thenReturn(mStorageManager);
+
+        when(SystemProperties.get(eq("ro.apex.updatable"))).thenReturn("true");
+        when(SystemProperties.get(eq("ro.apex.updatable"), anyString())).thenReturn("true");
+
+        mTmpDir = mTemporaryFolder.newFolder("StagingManagerTest");
+        mStagingManager = new StagingManager(mContext, null, mApexManager);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mMockitoSession != null) {
+            mMockitoSession.finishMocking();
+        }
+    }
+
+    /**
+     * Tests that sessions committed later shouldn't cause earlier ones to fail the overlapping
+     * check.
+     */
+    @Test
+    public void checkNonOverlappingWithStagedSessions_laterSessionShouldNotFailEarlierOnes()
+            throws Exception {
+        // Create 2 sessions with overlapping packages
+        StagingManager.StagedSession session1 = createSession(111, "com.foo", 1);
+        StagingManager.StagedSession session2 = createSession(222, "com.foo", 2);
+
+        mStagingManager.createSession(session1);
+        mStagingManager.createSession(session2);
+        // Session1 should not fail in spite of the overlapping packages
+        mStagingManager.checkNonOverlappingWithStagedSessions(session1);
+        // Session2 should fail due to overlapping packages
+        assertThrows(PackageManagerException.class,
+                () -> mStagingManager.checkNonOverlappingWithStagedSessions(session2));
+    }
+
+    @Test
+    public void restoreSessions_nonParentSession_throwsIAE() throws Exception {
+        FakeStagedSession session = new FakeStagedSession(239);
+        session.setParentSessionId(1543);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mStagingManager.restoreSessions(Arrays.asList(session), false));
+    }
+
+    @Test
+    public void restoreSessions_nonCommittedSession_throwsIAE() throws Exception {
+        FakeStagedSession session = new FakeStagedSession(239);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mStagingManager.restoreSessions(Arrays.asList(session), false));
+    }
+
+    @Test
+    public void restoreSessions_terminalSession_throwsIAE() throws Exception {
+        FakeStagedSession session = new FakeStagedSession(239);
+        session.setCommitted(true);
+        session.setSessionApplied();
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mStagingManager.restoreSessions(Arrays.asList(session), false));
+    }
+
+    @Test
+    public void restoreSessions_deviceUpgrading_failsAllSessions() throws Exception {
+        FakeStagedSession session1 = new FakeStagedSession(37);
+        session1.setCommitted(true);
+        FakeStagedSession session2 = new FakeStagedSession(57);
+        session2.setCommitted(true);
+
+        mStagingManager.restoreSessions(Arrays.asList(session1, session2), true);
+
+        assertThat(session1.getErrorCode()).isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(session1.getErrorMessage()).isEqualTo("Build fingerprint has changed");
+
+        assertThat(session2.getErrorCode()).isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(session2.getErrorMessage()).isEqualTo("Build fingerprint has changed");
+    }
+
+    @Test
+    public void restoreSessions_multipleSessions_deviceWithoutFsCheckpointSupport_throwISE()
+            throws Exception {
+        FakeStagedSession session1 = new FakeStagedSession(37);
+        session1.setCommitted(true);
+        FakeStagedSession session2 = new FakeStagedSession(57);
+        session2.setCommitted(true);
+
+        when(mStorageManager.supportsCheckpoint()).thenReturn(false);
+
+        assertThrows(IllegalStateException.class,
+                () -> mStagingManager.restoreSessions(Arrays.asList(session1, session2), false));
+    }
+
+    @Test
+    public void restoreSessions_handlesDestroyedAndNotReadySessions() throws Exception {
+        FakeStagedSession destroyedApkSession = new FakeStagedSession(23);
+        destroyedApkSession.setCommitted(true);
+        destroyedApkSession.setDestroyed(true);
+
+        FakeStagedSession destroyedApexSession = new FakeStagedSession(37);
+        destroyedApexSession.setCommitted(true);
+        destroyedApexSession.setDestroyed(true);
+        destroyedApexSession.setIsApex(true);
+
+        FakeStagedSession nonReadyApkSession = new FakeStagedSession(57);
+        nonReadyApkSession.setCommitted(true);
+
+        FakeStagedSession nonReadyApexSession = new FakeStagedSession(73);
+        nonReadyApexSession.setCommitted(true);
+        nonReadyApexSession.setIsApex(true);
+
+        FakeStagedSession destroyedNonReadySession = new FakeStagedSession(101);
+        destroyedNonReadySession.setCommitted(true);
+        destroyedNonReadySession.setDestroyed(true);
+
+        FakeStagedSession regularApkSession = new FakeStagedSession(239);
+        regularApkSession.setCommitted(true);
+        regularApkSession.setSessionReady();
+
+        List<StagingManager.StagedSession> sessions = new ArrayList<>();
+        sessions.add(destroyedApkSession);
+        sessions.add(destroyedApexSession);
+        sessions.add(nonReadyApkSession);
+        sessions.add(nonReadyApexSession);
+        sessions.add(destroyedNonReadySession);
+        sessions.add(regularApkSession);
+
+        mStagingManager.restoreSessions(sessions, false);
+
+        assertThat(sessions).containsExactly(regularApkSession);
+        assertThat(destroyedApkSession.isDestroyed()).isTrue();
+        assertThat(destroyedApexSession.isDestroyed()).isTrue();
+        assertThat(destroyedNonReadySession.isDestroyed()).isTrue();
+
+        mStagingManager.onBootCompletedBroadcastReceived();
+        assertThat(nonReadyApkSession.hasPreRebootVerificationStarted()).isTrue();
+        assertThat(nonReadyApexSession.hasPreRebootVerificationStarted()).isTrue();
+    }
+
+    @Test
+    public void restoreSessions_unknownApexSession_failsAllSessions() throws Exception {
+        FakeStagedSession apkSession = new FakeStagedSession(239);
+        apkSession.setCommitted(true);
+        apkSession.setSessionReady();
+
+        FakeStagedSession apexSession = new FakeStagedSession(1543);
+        apexSession.setCommitted(true);
+        apexSession.setIsApex(true);
+        apexSession.setSessionReady();
+
+        List<StagingManager.StagedSession> sessions = new ArrayList<>();
+        sessions.add(apkSession);
+        sessions.add(apexSession);
+
+        when(mApexManager.getSessions()).thenReturn(new SparseArray<>());
+        mStagingManager.restoreSessions(sessions, false);
+
+        // Validate checkpoint wasn't aborted.
+        verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
+
+        assertThat(apexSession.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apexSession.getErrorMessage()).isEqualTo("apexd did not know anything about a "
+                + "staged session supposed to be activated");
+
+        assertThat(apkSession.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
+    }
+
+    @Test
+    public void restoreSessions_failedApexSessions_failsAllSessions() throws Exception {
+        FakeStagedSession apkSession = new FakeStagedSession(239);
+        apkSession.setCommitted(true);
+        apkSession.setSessionReady();
+
+        FakeStagedSession apexSession1 = new FakeStagedSession(1543);
+        apexSession1.setCommitted(true);
+        apexSession1.setIsApex(true);
+        apexSession1.setSessionReady();
+
+        FakeStagedSession apexSession2 = new FakeStagedSession(101);
+        apexSession2.setCommitted(true);
+        apexSession2.setIsApex(true);
+        apexSession2.setSessionReady();
+
+        FakeStagedSession apexSession3 = new FakeStagedSession(57);
+        apexSession3.setCommitted(true);
+        apexSession3.setIsApex(true);
+        apexSession3.setSessionReady();
+
+        ApexSessionInfo activationFailed = new ApexSessionInfo();
+        activationFailed.sessionId = 1543;
+        activationFailed.isActivationFailed = true;
+
+        ApexSessionInfo staged = new ApexSessionInfo();
+        staged.sessionId = 101;
+        staged.isStaged = true;
+
+        SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>();
+        apexdSessions.put(1543, activationFailed);
+        apexdSessions.put(101, staged);
+        when(mApexManager.getSessions()).thenReturn(apexdSessions);
+
+        List<StagingManager.StagedSession> sessions = new ArrayList<>();
+        sessions.add(apkSession);
+        sessions.add(apexSession1);
+        sessions.add(apexSession2);
+        sessions.add(apexSession3);
+
+        mStagingManager.restoreSessions(sessions, false);
+
+        // Validate checkpoint wasn't aborted.
+        verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
+
+        assertThat(apexSession1.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apexSession1.getErrorMessage()).isEqualTo("APEX activation failed. Check logcat "
+                + "messages from apexd for more information.");
+
+        assertThat(apexSession2.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apexSession2.getErrorMessage()).isEqualTo("Staged session 101 at boot didn't "
+                + "activate nor fail. Marking it as failed anyway.");
+
+        assertThat(apexSession3.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apexSession3.getErrorMessage()).isEqualTo("apexd did not know anything about a "
+                + "staged session supposed to be activated");
+
+        assertThat(apkSession.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
+    }
+
+    @Test
+    public void restoreSessions_stagedApexSession_failsAllSessions() throws Exception {
+        FakeStagedSession apkSession = new FakeStagedSession(239);
+        apkSession.setCommitted(true);
+        apkSession.setSessionReady();
+
+        FakeStagedSession apexSession = new FakeStagedSession(1543);
+        apexSession.setCommitted(true);
+        apexSession.setIsApex(true);
+        apexSession.setSessionReady();
+
+        ApexSessionInfo staged = new ApexSessionInfo();
+        staged.sessionId = 1543;
+        staged.isStaged = true;
+
+        SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>();
+        apexdSessions.put(1543, staged);
+        when(mApexManager.getSessions()).thenReturn(apexdSessions);
+
+        List<StagingManager.StagedSession> sessions = new ArrayList<>();
+        sessions.add(apkSession);
+        sessions.add(apexSession);
+
+        mStagingManager.restoreSessions(sessions, false);
+
+        // Validate checkpoint wasn't aborted.
+        verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
+
+        assertThat(apexSession.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apexSession.getErrorMessage()).isEqualTo("Staged session 1543 at boot didn't "
+                + "activate nor fail. Marking it as failed anyway.");
+
+        assertThat(apkSession.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
+    }
+
+    @Test
+    public void restoreSessions_failedAndActivatedApexSessions_abortsCheckpoint() throws Exception {
+        FakeStagedSession apkSession = new FakeStagedSession(239);
+        apkSession.setCommitted(true);
+        apkSession.setSessionReady();
+
+        FakeStagedSession apexSession1 = new FakeStagedSession(1543);
+        apexSession1.setCommitted(true);
+        apexSession1.setIsApex(true);
+        apexSession1.setSessionReady();
+
+        FakeStagedSession apexSession2 = new FakeStagedSession(101);
+        apexSession2.setCommitted(true);
+        apexSession2.setIsApex(true);
+        apexSession2.setSessionReady();
+
+        FakeStagedSession apexSession3 = new FakeStagedSession(57);
+        apexSession3.setCommitted(true);
+        apexSession3.setIsApex(true);
+        apexSession3.setSessionReady();
+
+        FakeStagedSession apexSession4 = new FakeStagedSession(37);
+        apexSession4.setCommitted(true);
+        apexSession4.setIsApex(true);
+        apexSession4.setSessionReady();
+
+        ApexSessionInfo activationFailed = new ApexSessionInfo();
+        activationFailed.sessionId = 1543;
+        activationFailed.isActivationFailed = true;
+
+        ApexSessionInfo activated = new ApexSessionInfo();
+        activated.sessionId = 101;
+        activated.isActivated = true;
+
+        ApexSessionInfo staged = new ApexSessionInfo();
+        staged.sessionId = 57;
+        staged.isActivationFailed = true;
+
+        SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>();
+        apexdSessions.put(1543, activationFailed);
+        apexdSessions.put(101, activated);
+        apexdSessions.put(57, staged);
+        when(mApexManager.getSessions()).thenReturn(apexdSessions);
+
+        List<StagingManager.StagedSession> sessions = new ArrayList<>();
+        sessions.add(apkSession);
+        sessions.add(apexSession1);
+        sessions.add(apexSession2);
+        sessions.add(apexSession3);
+        sessions.add(apexSession4);
+
+        mStagingManager.restoreSessions(sessions, false);
+
+        // Validate checkpoint was aborted.
+        verify(mStorageManager, times(1)).abortChanges(eq("abort-staged-install"), eq(false));
+    }
+
+    @Test
+    public void restoreSessions_apexSessionInImpossibleState_failsAllSessions() throws Exception {
+        FakeStagedSession apkSession = new FakeStagedSession(239);
+        apkSession.setCommitted(true);
+        apkSession.setSessionReady();
+
+        FakeStagedSession apexSession = new FakeStagedSession(1543);
+        apexSession.setCommitted(true);
+        apexSession.setIsApex(true);
+        apexSession.setSessionReady();
+
+        ApexSessionInfo impossible  = new ApexSessionInfo();
+        impossible.sessionId = 1543;
+
+        SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>();
+        apexdSessions.put(1543, impossible);
+        when(mApexManager.getSessions()).thenReturn(apexdSessions);
+
+        List<StagingManager.StagedSession> sessions = new ArrayList<>();
+        sessions.add(apkSession);
+        sessions.add(apexSession);
+
+        mStagingManager.restoreSessions(sessions, false);
+
+        // Validate checkpoint wasn't aborted.
+        verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
+
+        assertThat(apexSession.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apexSession.getErrorMessage()).isEqualTo("Impossible state");
+
+        assertThat(apkSession.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
+    }
+
+    private StagingManager.StagedSession createSession(int sessionId, String packageName,
+            long committedMillis) {
+        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+        params.isStaged = true;
+
+        InstallSource installSource = InstallSource.create("testInstallInitiator",
+                "testInstallOriginator", "testInstaller", "testAttributionTag");
+
+        PackageInstallerSession session = new PackageInstallerSession(
+                /* callback */ null,
+                /* context */ null,
+                /* pm */ null,
+                /* sessionProvider */ null,
+                /* looper */ BackgroundThread.getHandler().getLooper(),
+                /* stagingManager */ null,
+                /* sessionId */ sessionId,
+                /* userId */ 456,
+                /* installerUid */ -1,
+                /* installSource */ installSource,
+                /* sessionParams */ params,
+                /* createdMillis */ 0L,
+                /* committedMillis */ committedMillis,
+                /* stageDir */ mTmpDir,
+                /* stageCid */ null,
+                /* files */ null,
+                /* checksums */ null,
+                /* prepared */ true,
+                /* committed */ true,
+                /* destroyed */ false,
+                /* sealed */ false,  // Setting to true would trigger some PM logic.
+                /* childSessionIds */ null,
+                /* parentSessionId */ -1,
+                /* isReady */ false,
+                /* isFailed */ false,
+                /* isApplied */false,
+                /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.STAGED_SESSION_NO_ERROR,
+                /* stagedSessionErrorMessage */ "no error");
+
+        StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
+        doReturn(packageName).when(stagedSession).getPackageName();
+        doAnswer(invocation -> {
+            Predicate<StagingManager.StagedSession> filter = invocation.getArgument(0);
+            return filter.test(stagedSession);
+        }).when(stagedSession).sessionContains(any());
+        return stagedSession;
+    }
+
+    private static final class FakeStagedSession implements StagingManager.StagedSession {
+        private final int mSessionId;
+        private boolean mIsApex = false;
+        private boolean mIsCommitted = false;
+        private boolean mIsReady = false;
+        private boolean mIsApplied = false;
+        private boolean mIsFailed = false;
+        private @StagedSessionErrorCode int mErrorCode = -1;
+        private String mErrorMessage;
+        private boolean mIsDestroyed = false;
+        private int mParentSessionId = -1;
+        private String mPackageName;
+        private boolean mIsAbandonded = false;
+        private boolean mPreRebootVerificationStarted = false;
+        private final List<StagingManager.StagedSession> mChildSessions = new ArrayList<>();
+
+        private FakeStagedSession(int sessionId) {
+            mSessionId = sessionId;
+        }
+
+        private void setParentSessionId(int parentSessionId) {
+            mParentSessionId = parentSessionId;
+        }
+
+        private void setCommitted(boolean isCommitted) {
+            mIsCommitted = isCommitted;
+        }
+
+        private void setIsApex(boolean isApex) {
+            mIsApex = isApex;
+        }
+
+        private void setDestroyed(boolean isDestroyed) {
+            mIsDestroyed = isDestroyed;
+        }
+
+        private void setPackageName(String packageName) {
+            mPackageName = packageName;
+        }
+
+        private boolean isAbandonded() {
+            return mIsAbandonded;
+        }
+
+        private boolean hasPreRebootVerificationStarted() {
+            return mPreRebootVerificationStarted;
+        }
+
+        private FakeStagedSession addChildSession(FakeStagedSession session) {
+            mChildSessions.add(session);
+            session.setParentSessionId(sessionId());
+            return this;
+        }
+
+        private @StagedSessionErrorCode int getErrorCode() {
+            return mErrorCode;
+        }
+
+        private String getErrorMessage() {
+            return mErrorMessage;
+        }
+
+        @Override
+        public boolean isMultiPackage() {
+            return !mChildSessions.isEmpty();
+        }
+
+        @Override
+        public boolean isApexSession() {
+            return mIsApex;
+        }
+
+        @Override
+        public boolean isCommitted() {
+            return mIsCommitted;
+        }
+
+        @Override
+        public boolean isInTerminalState() {
+            return isSessionApplied() || isSessionFailed();
+        }
+
+        @Override
+        public boolean isDestroyed() {
+            return mIsDestroyed;
+        }
+
+        @Override
+        public boolean isSessionReady() {
+            return mIsReady;
+        }
+
+        @Override
+        public boolean isSessionApplied() {
+            return mIsApplied;
+        }
+
+        @Override
+        public boolean isSessionFailed() {
+            return mIsFailed;
+        }
+
+        @Override
+        public List<StagingManager.StagedSession> getChildSessions() {
+            return mChildSessions;
+        }
+
+        @Override
+        public String getPackageName() {
+            return mPackageName;
+        }
+
+        @Override
+        public int getParentSessionId() {
+            return mParentSessionId;
+        }
+
+        @Override
+        public int sessionId() {
+            return mSessionId;
+        }
+
+        @Override
+        public PackageInstaller.SessionParams sessionParams() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean sessionContains(Predicate<StagingManager.StagedSession> filter) {
+            return filter.test(this);
+        }
+
+        @Override
+        public boolean containsApkSession() {
+            Preconditions.checkState(!hasParentSessionId(), "Child session");
+            if (!isMultiPackage()) {
+                return !isApexSession();
+            }
+            for (StagingManager.StagedSession session : mChildSessions) {
+                if (!session.isApexSession()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public boolean containsApexSession() {
+            Preconditions.checkState(!hasParentSessionId(), "Child session");
+            if (!isMultiPackage()) {
+                return isApexSession();
+            }
+            for (StagingManager.StagedSession session : mChildSessions) {
+                if (session.isApexSession()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public void setSessionReady() {
+            mIsReady = true;
+        }
+
+        @Override
+        public void setSessionFailed(@StagedSessionErrorCode int errorCode, String errorMessage) {
+            Preconditions.checkState(!mIsApplied, "Already marked as applied");
+            mIsFailed = true;
+            mErrorCode = errorCode;
+            mErrorMessage = errorMessage;
+        }
+
+        @Override
+        public void setSessionApplied() {
+            Preconditions.checkState(!mIsFailed, "Already marked as failed");
+            mIsApplied = true;
+        }
+
+        @Override
+        public void installSession(IntentSender statusReceiver) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean hasParentSessionId() {
+            return mParentSessionId != -1;
+        }
+
+        @Override
+        public long getCommittedMillis() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void abandon() {
+            mIsAbandonded = true;
+        }
+
+        @Override
+        public boolean notifyStartPreRebootVerification() {
+            mPreRebootVerificationStarted = true;
+            // TODO(ioffe): change to true when tests for pre-reboot verification are added.
+            return false;
+        }
+
+        @Override
+        public void notifyEndPreRebootVerification() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void verifySession() {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
index f0d7006..da3d1d6 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
@@ -198,6 +198,10 @@
                         return mVibratorProviders.get(vibratorId)
                                 .newVibratorController(vibratorId, listener);
                     }
+
+                    @Override
+                    void addService(String name, IBinder service) {
+                    }
                 });
         service.systemReady();
         return service;
diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
deleted file mode 100644
index 633957a..0000000
--- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
+++ /dev/null
@@ -1,757 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.AppOpsManager;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.PackageManagerInternal;
-import android.hardware.input.IInputManager;
-import android.hardware.input.InputManager;
-import android.hardware.vibrator.IVibrator;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IVibratorStateListener;
-import android.os.Looper;
-import android.os.PowerManagerInternal;
-import android.os.PowerSaveState;
-import android.os.Process;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.os.VibratorInfo;
-import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
-import android.provider.Settings;
-import android.view.InputDevice;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.vibrator.FakeVibrator;
-import com.android.server.vibrator.FakeVibratorControllerProvider;
-import com.android.server.vibrator.VibratorController;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-/**
- * Tests for {@link VibratorService}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:VibratorServiceTest
- */
-@Presubmit
-public class VibratorServiceTest {
-
-    private static final int TEST_TIMEOUT_MILLIS = 1_000;
-    private static final int UID = Process.ROOT_UID;
-    private static final int VIBRATOR_ID = 1;
-    private static final String PACKAGE_NAME = "package";
-    private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
-    private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
-            .setBatterySaverEnabled(true).build();
-    private static final VibrationAttributes ALARM_ATTRS =
-            new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
-    private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS =
-            new VibrationAttributes.Builder().setUsage(
-                    VibrationAttributes.USAGE_TOUCH).build();
-    private static final VibrationAttributes NOTIFICATION_ATTRS =
-            new VibrationAttributes.Builder().setUsage(
-                    VibrationAttributes.USAGE_NOTIFICATION).build();
-    private static final VibrationAttributes RINGTONE_ATTRS =
-            new VibrationAttributes.Builder().setUsage(
-                    VibrationAttributes.USAGE_RINGTONE).build();
-
-    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
-    @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
-
-    @Mock private PackageManagerInternal mPackageManagerInternalMock;
-    @Mock private PowerManagerInternal mPowerManagerInternalMock;
-    @Mock private AppOpsManager mAppOpsManagerMock;
-    @Mock private IVibratorStateListener mVibratorStateListenerMock;
-    @Mock private IInputManager mIInputManagerMock;
-    @Mock private IBinder mVibratorStateListenerBinderMock;
-
-    private TestLooper mTestLooper;
-    private ContextWrapper mContextSpy;
-    private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
-    private FakeVibrator mFakeVibrator;
-    private FakeVibratorControllerProvider mVibratorProvider;
-
-    @Before
-    public void setUp() throws Exception {
-        mTestLooper = new TestLooper();
-        mFakeVibrator = new FakeVibrator();
-        mVibratorProvider = new FakeVibratorControllerProvider(mTestLooper.getLooper());
-        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
-        InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
-
-        ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
-        when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
-        when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mFakeVibrator);
-        when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager);
-        when(mContextSpy.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManagerMock);
-        when(mVibratorStateListenerMock.asBinder()).thenReturn(mVibratorStateListenerBinderMock);
-        when(mPackageManagerInternalMock.getSystemUiServiceComponent())
-                .thenReturn(new ComponentName("", ""));
-        doAnswer(invocation -> {
-            mRegisteredPowerModeListener = invocation.getArgument(0);
-            return null;
-        }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
-
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-
-        addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternalMock);
-        addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        InputManager.clearInstance();
-        LocalServices.removeServiceForTest(PackageManagerInternal.class);
-        LocalServices.removeServiceForTest(PowerManagerInternal.class);
-    }
-
-    private VibratorService createService() {
-        VibratorService service = new VibratorService(mContextSpy,
-                new VibratorService.Injector() {
-                    @Override
-                    VibratorController createVibratorController(
-                            VibratorController.OnVibrationCompleteListener listener) {
-                        return mVibratorProvider.newVibratorController(VIBRATOR_ID, listener);
-                    }
-
-                    @Override
-                    Handler createHandler(Looper looper) {
-                        return new Handler(mTestLooper.getLooper());
-                    }
-
-                    @Override
-                    void addService(String name, IBinder service) {
-                        // ignore
-                    }
-                });
-        service.systemReady();
-        return service;
-    }
-
-    @Test
-    public void createService_initializesNativeService() {
-        createService();
-        assertTrue(mVibratorProvider.isInitialized());
-    }
-
-    @Test
-    public void hasVibrator_withVibratorHalPresent_returnsTrue() {
-        assertTrue(createService().hasVibrator());
-    }
-
-    @Test
-    public void hasVibrator_withNoVibratorHalPresent_returnsFalse() {
-        mVibratorProvider.disableVibrators();
-        assertFalse(createService().hasVibrator());
-    }
-
-    @Test
-    public void hasAmplitudeControl_withAmplitudeControlSupport_returnsTrue() {
-        mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        assertTrue(createService().hasAmplitudeControl());
-    }
-
-    @Test
-    public void hasAmplitudeControl_withNoAmplitudeControlSupport_returnsFalse() {
-        assertFalse(createService().hasAmplitudeControl());
-    }
-
-    @Test
-    public void hasAmplitudeControl_withInputDevices_returnsTrue() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
-        mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
-        assertTrue(createService().hasAmplitudeControl());
-    }
-
-    @Test
-    public void getVibratorInfo_returnsSameInfoFromNative() {
-        mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
-                IVibrator.CAP_AMPLITUDE_CONTROL);
-        mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        mVibratorProvider.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
-
-        VibratorInfo info = createService().getVibratorInfo();
-        assertTrue(info.hasAmplitudeControl());
-        assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
-                info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
-        assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
-                info.isEffectSupported(VibrationEffect.EFFECT_TICK));
-        assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
-        assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
-    }
-
-    @Test
-    public void vibrate_withRingtone_usesRingtoneSettings() throws Exception {
-        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
-        setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
-        vibrate(createService(), VibrationEffect.createOneShot(1, 1), RINGTONE_ATTRS);
-
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
-        setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
-        vibrateAndWait(createService(), VibrationEffect.createOneShot(10, 10), RINGTONE_ATTRS);
-
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
-        setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
-        vibrateAndWait(createService(), VibrationEffect.createOneShot(100, 100), RINGTONE_ATTRS);
-
-        List<VibrationEffect> effects = mVibratorProvider.getEffects();
-        assertEquals(2, effects.size());
-        assertEquals(10, effects.get(0).getDuration());
-        assertEquals(100, effects.get(1).getDuration());
-    }
-
-    @Test
-    public void vibrate_withPowerModeChange_usesLowPowerModeState() throws Exception {
-        VibratorService service = createService();
-        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
-        vibrate(service, VibrationEffect.createOneShot(1, 1), HAPTIC_FEEDBACK_ATTRS);
-        vibrateAndWait(service, VibrationEffect.createOneShot(2, 2), RINGTONE_ATTRS);
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
-        vibrateAndWait(service, VibrationEffect.createOneShot(3, 3), /* attributes= */ null);
-        vibrateAndWait(service, VibrationEffect.createOneShot(4, 4), NOTIFICATION_ATTRS);
-
-        List<VibrationEffect> effects = mVibratorProvider.getEffects();
-        assertEquals(3, effects.size());
-        assertEquals(2, effects.get(0).getDuration());
-        assertEquals(3, effects.get(1).getDuration());
-        assertEquals(4, effects.get(2).getDuration());
-    }
-
-    @Test
-    public void vibrate_withAudioAttributes_usesOriginalAudioUsageInAppOpsManager() {
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
-        AudioAttributes audioAttributes = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build();
-        VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder(
-                audioAttributes, effect).build();
-
-        vibrate(service, effect, vibrationAttributes);
-
-        verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY), anyInt(), anyString());
-    }
-
-    @Test
-    public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() {
-        VibratorService service = createService();
-
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), NOTIFICATION_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
-                new VibrationAttributes.Builder().setUsage(
-                        VibrationAttributes.USAGE_COMMUNICATION_REQUEST).build());
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
-                new VibrationAttributes.Builder().setUsage(
-                        VibrationAttributes.USAGE_UNKNOWN).build());
-
-        InOrder inOrderVerifier = inOrder(mAppOpsManagerMock);
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_ALARM), anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_NOTIFICATION), anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST),
-                anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
-    }
-
-    @Test
-    public void vibrate_withOneShotAndInputDevices_vibratesInputDevices() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.createOneShot(100, 128);
-        vibrate(service, effect, ALARM_ATTRS);
-        verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
-
-        // VibrationThread will start this vibration async, so wait before checking it never played.
-        assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
-                /* timeout= */ 20));
-    }
-
-    @Test
-    public void vibrate_withOneShotAndAmplitudeControl_turnsVibratorOnAndSetsAmplitude()
-            throws Exception {
-        mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        VibratorService service = createService();
-
-        vibrateAndWait(service, VibrationEffect.createOneShot(100, 128), ALARM_ATTRS);
-
-        List<VibrationEffect> effects = mVibratorProvider.getEffects();
-        assertEquals(1, effects.size());
-        assertEquals(100, effects.get(0).getDuration());
-        assertEquals(Arrays.asList(128), mVibratorProvider.getAmplitudes());
-    }
-
-    @Test
-    public void vibrate_withOneShotAndNoAmplitudeControl_turnsVibratorOnAndIgnoresAmplitude()
-            throws Exception {
-        VibratorService service = createService();
-        clearInvocations();
-
-        vibrateAndWait(service, VibrationEffect.createOneShot(100, 128), ALARM_ATTRS);
-
-        List<VibrationEffect> effects = mVibratorProvider.getEffects();
-        assertEquals(1, effects.size());
-        assertEquals(100, effects.get(0).getDuration());
-        assertTrue(mVibratorProvider.getAmplitudes().isEmpty());
-    }
-
-    @Test
-    public void vibrate_withPrebaked_performsEffect() throws Exception {
-        mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
-        vibrateAndWait(service, effect, ALARM_ATTRS);
-
-        VibrationEffect.Prebaked expectedEffect = new VibrationEffect.Prebaked(
-                VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
-        assertEquals(Arrays.asList(expectedEffect), mVibratorProvider.getEffects());
-    }
-
-    @Test
-    public void vibrate_withPrebakedAndInputDevices_vibratesFallbackWaveformOnInputDevices()
-            throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
-        VibratorService service = createService();
-
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
-        verify(mIInputManagerMock).vibrate(eq(1), any(), any());
-
-        // VibrationThread will start this vibration async, so wait before checking it never played.
-        assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
-                /* timeout= */ 20));
-    }
-
-    @Test
-    public void vibrate_enteringLowPowerMode_cancelVibration() throws Exception {
-        VibratorService service = createService();
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
-        vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
-        assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
-        assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-    }
-
-    @Test
-    public void vibrate_enteringLowPowerModeAndRingtone_doNotCancelVibration() throws Exception {
-        VibratorService service = createService();
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
-        vibrate(service, VibrationEffect.createOneShot(1000, 100), RINGTONE_ATTRS);
-        assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
-        // Settings callback is async, so wait before checking it never got cancelled.
-        assertFalse(waitUntil(s -> !s.isVibrating(), service, /* timeout= */ 20));
-    }
-
-    @Test
-    public void vibrate_withSettingsChanged_doNotCancelVibration() throws Exception {
-        VibratorService service = createService();
-        vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
-        assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-
-        // FakeSettingsProvider don't support testing triggering ContentObserver yet.
-        service.updateVibrators();
-
-        // Settings callback is async, so wait before checking it never got cancelled.
-        assertFalse(waitUntil(s -> !s.isVibrating(), service, /* timeout= */ 20));
-    }
-
-    @Test
-    public void vibrate_withComposed_performsEffect() throws Exception {
-        mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
-                .compose();
-        vibrateAndWait(service, effect, ALARM_ATTRS);
-        assertEquals(Arrays.asList(effect), mVibratorProvider.getEffects());
-    }
-
-    @Test
-    public void vibrate_withComposedAndInputDevices_vibratesInputDevices() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
-        when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
-        when(mIInputManagerMock.getInputDevice(2)).thenReturn(createInputDeviceWithVibrator(2));
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
-                .compose();
-        vibrate(service, effect, ALARM_ATTRS);
-        InOrder inOrderVerifier = inOrder(mIInputManagerMock);
-        inOrderVerifier.verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
-        inOrderVerifier.verify(mIInputManagerMock).vibrate(eq(2), eq(effect), any());
-
-        // VibrationThread will start this vibration async, so wait before checking it never played.
-        assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
-                /* timeout= */ 20));
-    }
-
-    @Test
-    public void vibrate_withWaveform_controlsVibratorAmplitudeDuringTotalVibrationTime()
-            throws Exception {
-        mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.createWaveform(
-                new long[]{10, 10, 10}, new int[]{100, 200, 50}, -1);
-        vibrateAndWait(service, effect, ALARM_ATTRS);
-
-        assertEquals(Arrays.asList(100, 200, 50), mVibratorProvider.getAmplitudes());
-        assertEquals(
-                Arrays.asList(VibrationEffect.createOneShot(30, VibrationEffect.DEFAULT_AMPLITUDE)),
-                mVibratorProvider.getEffects());
-    }
-
-    @Test
-    public void vibrate_withWaveformAndInputDevices_vibratesInputDevices() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.createWaveform(
-                new long[]{10, 10, 10}, new int[]{100, 200, 50}, -1);
-        vibrate(service, effect, ALARM_ATTRS);
-        verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
-
-        // VibrationThread will start this vibration async, so wait before checking it never played.
-        assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
-                /* timeout= */ 20));
-    }
-
-    @Test
-    public void vibrate_withNativeCallbackTriggered_finishesVibration() throws Exception {
-        mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        VibratorService service = createService();
-
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
-        assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        // Trigger callbacks from controller.
-        mTestLooper.moveTimeForward(50);
-        mTestLooper.dispatchAll();
-        assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-    }
-
-    @Test
-    public void cancelVibrate_withDeviceVibrating_callsOff() throws Exception {
-        VibratorService service = createService();
-
-        vibrate(service, VibrationEffect.createOneShot(100, 100), ALARM_ATTRS);
-        assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        service.cancelVibrate(service);
-        assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-    }
-
-    @Test
-    public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
-        VibratorService service = createService();
-        service.registerVibratorStateListener(mVibratorStateListenerMock);
-
-        vibrateAndWait(service, VibrationEffect.createOneShot(100, 100), ALARM_ATTRS);
-
-        InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
-        // First notification done when listener is registered.
-        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
-        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true));
-        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
-        inOrderVerifier.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception {
-        VibratorService service = createService();
-
-        service.registerVibratorStateListener(mVibratorStateListenerMock);
-
-        vibrate(service, VibrationEffect.createOneShot(30, 100), ALARM_ATTRS);
-        assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        service.unregisterVibratorStateListener(mVibratorStateListenerMock);
-        // Trigger callbacks from controller.
-        mTestLooper.moveTimeForward(50);
-        mTestLooper.dispatchAll();
-        assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
-        // First notification done when listener is registered.
-        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
-        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true));
-        inOrderVerifier.verify(mVibratorStateListenerMock, atLeastOnce()).asBinder(); // unregister
-        inOrderVerifier.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void scale_withPrebaked_userIntensitySettingAsEffectStrength() throws Exception {
-        // Alarm vibration is always VIBRATION_INTENSITY_HIGH.
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-        mVibratorProvider.setSupportedEffects(
-                VibrationEffect.EFFECT_CLICK,
-                VibrationEffect.EFFECT_TICK,
-                VibrationEffect.EFFECT_DOUBLE_CLICK,
-                VibrationEffect.EFFECT_HEAVY_CLICK);
-        VibratorService service = createService();
-
-        vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
-        vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
-                NOTIFICATION_ATTRS);
-        vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK),
-                HAPTIC_FEEDBACK_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), RINGTONE_ATTRS);
-
-        List<Integer> playedStrengths = mVibratorProvider.getEffects().stream()
-                .map(VibrationEffect.Prebaked.class::cast)
-                .map(VibrationEffect.Prebaked::getEffectStrength)
-                .collect(Collectors.toList());
-        assertEquals(Arrays.asList(
-                VibrationEffect.EFFECT_STRENGTH_STRONG,
-                VibrationEffect.EFFECT_STRENGTH_MEDIUM,
-                VibrationEffect.EFFECT_STRENGTH_LIGHT),
-                playedStrengths);
-    }
-
-    @Test
-    public void scale_withOneShotAndWaveform_usesScaleLevelOnAmplitude() throws Exception {
-        mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_HIGH);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-
-        mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        VibratorService service = createService();
-
-        vibrateAndWait(service, VibrationEffect.createOneShot(20, 100), ALARM_ATTRS);
-        vibrateAndWait(service, VibrationEffect.createOneShot(20, 100), NOTIFICATION_ATTRS);
-        vibrateAndWait(service,
-                VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1),
-                HAPTIC_FEEDBACK_ATTRS);
-        vibrate(service, VibrationEffect.createOneShot(20, 255), RINGTONE_ATTRS);
-
-        List<Integer> amplitudes = mVibratorProvider.getAmplitudes();
-        assertEquals(3, amplitudes.size());
-        // Alarm vibration is never scaled.
-        assertEquals(100, amplitudes.get(0).intValue());
-        // Notification vibrations will be scaled with SCALE_VERY_HIGH.
-        assertTrue(amplitudes.get(1) > 150);
-        // Haptic feedback vibrations will be scaled with SCALE_LOW.
-        assertTrue(amplitudes.get(2) < 100 && amplitudes.get(2) > 50);
-    }
-
-    @Test
-    public void scale_withComposed_usesScaleLevelOnPrimitiveScaleValues() throws Exception {
-        mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_HIGH);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-
-        mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
-                .compose();
-
-        vibrateAndWait(service, effect, ALARM_ATTRS);
-        vibrateAndWait(service, effect, NOTIFICATION_ATTRS);
-        vibrateAndWait(service, effect, HAPTIC_FEEDBACK_ATTRS);
-        vibrate(service, effect, RINGTONE_ATTRS);
-
-        List<VibrationEffect.Composition.PrimitiveEffect> primitives =
-                mVibratorProvider.getEffects().stream()
-                        .map(VibrationEffect.Composed.class::cast)
-                        .map(VibrationEffect.Composed::getPrimitiveEffects)
-                        .flatMap(List::stream)
-                        .collect(Collectors.toList());
-
-        // Ringtone vibration is off, so only the other 3 are propagated to native.
-        assertEquals(6, primitives.size());
-
-        // Alarm vibration is never scaled.
-        assertEquals(1f, primitives.get(0).scale, /* delta= */ 1e-2);
-        assertEquals(0.5f, primitives.get(1).scale, /* delta= */ 1e-2);
-
-        // Notification vibrations will be scaled with SCALE_VERY_HIGH.
-        assertEquals(1f, primitives.get(2).scale, /* delta= */ 1e-2);
-        assertTrue(0.7 < primitives.get(3).scale);
-
-        // Haptic feedback vibrations will be scaled with SCALE_LOW.
-        assertTrue(0.5 < primitives.get(4).scale);
-        assertTrue(0.5 > primitives.get(5).scale);
-    }
-
-    private void vibrate(VibratorService service, VibrationEffect effect,
-            VibrationAttributes attrs) {
-        service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
-    }
-
-    private void vibrateAndWait(VibratorService service, VibrationEffect effect,
-            VibrationAttributes attrs) throws Exception {
-        CountDownLatch startedCount = new CountDownLatch(1);
-        CountDownLatch finishedCount = new CountDownLatch(1);
-        service.registerVibratorStateListener(new IVibratorStateListener() {
-            @Override
-            public void onVibrating(boolean vibrating) {
-                if (vibrating) {
-                    startedCount.countDown();
-                } else if (startedCount.getCount() == 0) {
-                    finishedCount.countDown();
-                }
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return mock(IBinder.class);
-            }
-        });
-
-        mTestLooper.startAutoDispatch();
-        service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
-        assertTrue(startedCount.await(1, TimeUnit.SECONDS));
-        assertTrue(finishedCount.await(1, TimeUnit.SECONDS));
-        mTestLooper.stopAutoDispatchAndIgnoreExceptions();
-    }
-
-    private InputDevice createInputDeviceWithVibrator(int id) {
-        return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
-                null, /* hasVibrator= */ true, false, false, false /* hasSensor */,
-                false /* hasBattery */);
-    }
-
-    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
-        LocalServices.removeServiceForTest(clazz);
-        LocalServices.addService(clazz, mock);
-    }
-
-    private void setRingerMode(int ringerMode) {
-        AudioManager audioManager = mContextSpy.getSystemService(AudioManager.class);
-        audioManager.setRingerModeInternal(ringerMode);
-        assertEquals(ringerMode, audioManager.getRingerModeInternal());
-    }
-
-    private void setUserSetting(String settingName, int value) {
-        Settings.System.putIntForUser(
-                mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
-    }
-
-    private void setGlobalSetting(String settingName, int value) {
-        Settings.Global.putInt(mContextSpy.getContentResolver(), settingName, value);
-    }
-
-    private boolean waitUntil(Predicate<VibratorService> predicate,
-            VibratorService service, long timeout) throws InterruptedException {
-        long timeoutTimestamp = SystemClock.uptimeMillis() + timeout;
-        boolean predicateResult = false;
-        while (!predicateResult && SystemClock.uptimeMillis() < timeoutTimestamp) {
-            Thread.sleep(10);
-            predicateResult = predicate.test(service);
-        }
-        return predicateResult;
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
index fdf5095..a946534 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.am;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 
 import android.content.Context;
@@ -30,15 +28,12 @@
 import android.hardware.power.stats.StateResidencyResult;
 import android.power.PowerStatsInternal;
 import android.util.SparseArray;
-import android.util.SparseLongArray;
 
 import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.os.BatteryStatsImpl;
-import com.android.internal.power.MeasuredEnergyArray;
 
 import org.junit.Before;
-import org.junit.Test;
 
 import java.util.concurrent.CompletableFuture;
 
@@ -63,44 +58,6 @@
                 mBatteryStatsImpl);
     }
 
-    @Test
-    public void getEnergyConsumptionData() {
-        SparseLongArray expectSubsystems = new SparseLongArray();
-        // Add some energy consumers used by BatteryExternalStatsWorker.
-        final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0,
-                "display");
-        mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345);
-        expectSubsystems.put(MeasuredEnergyArray.SUBSYSTEM_DISPLAY, 12345);
-
-        // Add an arbitrary energy consumer unused by BatteryExternalStatsWorker.
-        // Must be changed if '154' ever becomes an EnergyConsumerType used by BESW.
-        final int someId = mPowerStatsInternal.addEnergyConsumer((byte) 154, 0, "some_consumer");
-        mPowerStatsInternal.incrementEnergyConsumption(someId, 34567);
-
-        // Inform BESW that PowerStatsInternal is ready to query
-        mBatteryExternalStatsWorker.systemServicesReady();
-
-        MeasuredEnergyArray energies = mBatteryExternalStatsWorker.getEnergyConsumptionData();
-
-        assertEquals(expectSubsystems.size(), energies.size());
-        final int size = expectSubsystems.size();
-
-        for (int i = 0; i < size; i++) {
-            int subsystem = expectSubsystems.keyAt(i);
-            // find the subsystem in the returned MeasuredEnergyArray
-            int subsystemIndex = -1;
-            for (int j = 0; j < size; j++) {
-                if (subsystem == energies.getSubsystem(i)) {
-                    subsystemIndex = i;
-                    break;
-                }
-            }
-            assertNotEquals("Subsystem " + subsystem + " not found in MeasuredEnergyArray", -1,
-                    subsystemIndex);
-            assertEquals(expectSubsystems.valueAt(i), energies.getEnergy(subsystemIndex));
-        }
-    }
-
     public class TestInjector extends BatteryExternalStatsWorker.Injector {
         public TestInjector(Context context) {
             super(context);
diff --git a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
index 67d379a..1efce39 100644
--- a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
@@ -16,18 +16,23 @@
 
 package com.android.server.am;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static com.android.server.am.MeasuredEnergySnapshot.UNAVAILABLE;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerAttribution;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.util.SparseArray;
 import android.util.SparseLongArray;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.power.MeasuredEnergyArray;
+import com.android.server.am.MeasuredEnergySnapshot.MeasuredEnergyDeltaData;
 
-import org.junit.Before;
 import org.junit.Test;
 
 /**
@@ -38,134 +43,198 @@
  */
 @SmallTest
 public final class MeasuredEnergySnapshotTest {
-    private static final int NUMBER_SUBSYSTEMS = 3;
-    private static final int SUBSYSTEM_DISPLAY = 0;
-    private static final int SUBSYSTEM_NEVER_USED = 1;
-    private static final int SUBSYSTEM_CATAPULT = 2;
+    private static final EnergyConsumer CONSUMER_DISPLAY = createEnergyConsumer(
+            0, 0, EnergyConsumerType.DISPLAY, "Display");
+    private static final  EnergyConsumer CONSUMER_OTHER_0 = createEnergyConsumer(
+            47, 0, EnergyConsumerType.OTHER, "GPU");
+    private static final  EnergyConsumer CONSUMER_OTHER_1 = createEnergyConsumer(
+            1, 1, EnergyConsumerType.OTHER, "HPU");
+    private static final  EnergyConsumer CONSUMER_OTHER_2 = createEnergyConsumer(
+            436, 2, EnergyConsumerType.OTHER, "IPU");
 
-    private MeasuredEnergySnapshot mSnapshot;
+    private static final SparseArray<EnergyConsumer> ALL_ID_CONSUMER_MAP = createIdToConsumerMap(
+            CONSUMER_DISPLAY, CONSUMER_OTHER_0, CONSUMER_OTHER_1, CONSUMER_OTHER_2);
+    private static final SparseArray<EnergyConsumer> SOME_ID_CONSUMER_MAP = createIdToConsumerMap(
+            CONSUMER_DISPLAY);
 
-    // Basic MeasuredEnergyArray that supports all the subsystems. Out of order on purpose.
-    private final int[] mAllSubsystems =
-            {SUBSYSTEM_DISPLAY, SUBSYSTEM_CATAPULT, SUBSYSTEM_NEVER_USED};
-    // E.g. mAllSubsystems[mSubsystemIndices[SUBSYSTEM_CATAPULT]]=SUBSYSTEM_CATAPULT
-    private final int[] mSubsystemIndices = {0, 2, 1};
-    private final long[] mCurrentSubsystemEnergyUJ = {111, 0, 0};
-    private final MeasuredEnergyArray mOmniEnergyArray = new MeasuredEnergyArray() {
-        @Override
-        public int getSubsystem(int index) {
-            return mAllSubsystems[index];
-        }
-
-        @Override
-        public long getEnergy(int index) {
-            return mCurrentSubsystemEnergyUJ[index];
-        }
-
-        @Override
-        public int size() {
-            return mAllSubsystems.length;
-        }
+    // Elements in each results are purposefully out of order.
+    private static final  EnergyConsumerResult[] RESULTS_0 = new EnergyConsumerResult[] {
+        createEnergyConsumerResult(CONSUMER_OTHER_0.id, 90, new int[] {47, 3}, new long[] {14, 13}),
+        createEnergyConsumerResult(CONSUMER_DISPLAY.id, 14, null, null),
+        createEnergyConsumerResult(CONSUMER_OTHER_1.id, 0, null, null),
+        // No CONSUMER_OTHER_2
     };
-    private final MeasuredEnergyArray mJustDisplayEnergyArray = new MeasuredEnergyArray() {
-        @Override
-        public int getSubsystem(int index) {
-            return mAllSubsystems[0];
-        }
-
-        @Override
-        public long getEnergy(int index) {
-            return mCurrentSubsystemEnergyUJ[0];
-        }
-
-        @Override
-        public int size() {
-            return 1;
-        }
+    private static final  EnergyConsumerResult[] RESULTS_1 = new EnergyConsumerResult[] {
+        createEnergyConsumerResult(CONSUMER_DISPLAY.id, 24, null, null),
+        createEnergyConsumerResult(CONSUMER_OTHER_0.id, 90, new int[] {47, 3}, new long[] {14, 13}),
+        createEnergyConsumerResult(CONSUMER_OTHER_2.id, 12, new int[] {6}, new long[] {10}),
+        createEnergyConsumerResult(CONSUMER_OTHER_1.id, 12_000, null, null),
+    };
+    private static final  EnergyConsumerResult[] RESULTS_2 = new EnergyConsumerResult[] {
+        createEnergyConsumerResult(CONSUMER_DISPLAY.id, 36, null, null),
+        // No CONSUMER_OTHER_0
+        // No CONSUMER_OTHER_1
+        // No CONSUMER_OTHER_2
+    };
+    private static final  EnergyConsumerResult[] RESULTS_3 = new EnergyConsumerResult[] {
+        // No CONSUMER_DISPLAY
+        createEnergyConsumerResult(CONSUMER_OTHER_2.id, 13, new int[] {6}, new long[] {10}),
+        createEnergyConsumerResult(
+                CONSUMER_OTHER_0.id, 190, new int[] {2, 3, 47, 7}, new long[] {9, 18, 14, 6}),
+        createEnergyConsumerResult(CONSUMER_OTHER_1.id, 12_000, null, null),
+    };
+    private static final  EnergyConsumerResult[] RESULTS_4 = new EnergyConsumerResult[] {
+        createEnergyConsumerResult(CONSUMER_DISPLAY.id, 43, null, null),
+        createEnergyConsumerResult(
+                CONSUMER_OTHER_0.id, 290, new int[] {7, 47, 3, 2}, new long[] {6, 14, 18, 11}),
+        // No CONSUMER_OTHER_1
+        createEnergyConsumerResult(CONSUMER_OTHER_2.id, 165, new int[] {6, 47}, new long[] {10, 8}),
     };
 
-    @Before
-    public void setUp() {
-        mSnapshot = new MeasuredEnergySnapshot(NUMBER_SUBSYSTEMS, mOmniEnergyArray);
+    @Test
+    public void testUpdateAndGetDelta_empty() {
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
+        assertNull(snapshot.updateAndGetDelta(null));
+        assertNull(snapshot.updateAndGetDelta(new EnergyConsumerResult[0]));
     }
 
     @Test
     public void testUpdateAndGetDelta() {
-        SparseLongArray result;
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
 
-        // Increment DISPLAY by 15
-        incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 15);
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals(1, result.size());
-        assertEquals(15, result.get(SUBSYSTEM_DISPLAY));
+        // results0
+        MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0);
+        if (delta != null) { // null is fine here. If non-null, it better be uninteresting though.
+            assertEquals(UNAVAILABLE, delta.displayEnergyUJ);
+            assertNull(delta.otherTotalEnergyUJ);
+            assertNull(delta.otherUidEnergiesUJ);
+        }
 
-        // Increment DISPLAY by 7
-        // Increment CATAPULT by 5. But do NOT include (pull) it in the passed in energy array.
-        incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 7);
-        incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 5);
-        result = mSnapshot.updateAndGetDelta(mJustDisplayEnergyArray); // Just pull display.
-        assertEquals(1, result.size());
-        assertEquals(7, result.get(SUBSYSTEM_DISPLAY));
+        // results1
+        delta = snapshot.updateAndGetDelta(RESULTS_1);
+        assertNotNull(delta);
+        assertEquals(24 - 14, delta.displayEnergyUJ);
 
-        // Increment CATAPULT by 64 (in addition to the previous increase of 5)
-        incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 64);
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals(1, result.size());
-        assertEquals(5 + 64, result.get(SUBSYSTEM_CATAPULT));
+        assertNotNull(delta.otherTotalEnergyUJ);
+        assertEquals(90 - 90, delta.otherTotalEnergyUJ[0]);
+        assertEquals(12_000 - 0, delta.otherTotalEnergyUJ[1]);
+        assertEquals(0, delta.otherTotalEnergyUJ[2]); // First good pull. Treat delta as 0.
 
-        // Do nothing
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals("0 results should not appear at all", 0, result.size());
+        assertNotNull(delta.otherUidEnergiesUJ);
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[0]); // No change in uid energies
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[1]);
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[2]);
 
-        // Increment DISPLAY by 42
-        incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 42);
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals(1, result.size());
-        assertEquals(42, result.get(SUBSYSTEM_DISPLAY));
+        // results2
+        delta = snapshot.updateAndGetDelta(RESULTS_2);
+        assertNotNull(delta);
+        assertEquals(36 - 24, delta.displayEnergyUJ);
+        assertNull(delta.otherUidEnergiesUJ);
+        assertNull(delta.otherTotalEnergyUJ);
 
-        // Increment DISPLAY by 106 and CATAPULT by 13
-        incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 106);
-        incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 13);
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals(2, result.size());
-        assertEquals(106, result.get(SUBSYSTEM_DISPLAY));
-        assertEquals(13, result.get(SUBSYSTEM_CATAPULT));
+        // results3
+        delta = snapshot.updateAndGetDelta(RESULTS_3);
+        assertNotNull(delta);
+        assertEquals(UNAVAILABLE, delta.displayEnergyUJ);
+
+        assertNotNull(delta.otherTotalEnergyUJ);
+        assertEquals(190 - 90, delta.otherTotalEnergyUJ[0]);
+        assertEquals(12_000 - 12_000, delta.otherTotalEnergyUJ[1]);
+        assertEquals(13 - 12, delta.otherTotalEnergyUJ[2]);
+
+        assertNotNull(delta.otherUidEnergiesUJ);
+        assertEquals(3, delta.otherUidEnergiesUJ[0].size());
+        assertEquals(9 - 0, delta.otherUidEnergiesUJ[0].get(2));
+        assertEquals(18 - 13, delta.otherUidEnergiesUJ[0].get(3));
+        assertEquals(6 - 0, delta.otherUidEnergiesUJ[0].get(7));
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[1]);
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[2]);
+
+        // results4
+        delta = snapshot.updateAndGetDelta(RESULTS_4);
+        assertNotNull(delta);
+        assertEquals(43 - 36, delta.displayEnergyUJ);
+
+        assertNotNull(delta.otherTotalEnergyUJ);
+        assertEquals(290 - 190, delta.otherTotalEnergyUJ[0]);
+        assertEquals(0, delta.otherTotalEnergyUJ[1]); // Not present (e.g. missing data)
+        assertEquals(165 - 13, delta.otherTotalEnergyUJ[2]);
+
+        assertNotNull(delta.otherUidEnergiesUJ);
+        assertEquals(1, delta.otherUidEnergiesUJ[0].size());
+        assertEquals(11 - 9, delta.otherUidEnergiesUJ[0].get(2));
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[1]); // Not present
+        assertEquals(1, delta.otherUidEnergiesUJ[2].size());
+        assertEquals(8, delta.otherUidEnergiesUJ[2].get(47));
     }
 
-    private void incrementEnergyOfSubsystem(int subsystem, long energy) {
-        mCurrentSubsystemEnergyUJ[mSubsystemIndices[subsystem]] += energy;
+    /** Test updateAndGetDelta() when the results have consumers absent from idToConsumerMap. */
+    @Test
+    public void testUpdateAndGetDelta_some() {
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(SOME_ID_CONSUMER_MAP);
+
+        // results0
+        MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0);
+        if (delta != null) { // null is fine here. If non-null, it better be uninteresting though.
+            assertEquals(UNAVAILABLE, delta.displayEnergyUJ);
+            assertNull(delta.otherTotalEnergyUJ);
+            assertNull(delta.otherUidEnergiesUJ);
+        }
+
+        // results1
+        delta = snapshot.updateAndGetDelta(RESULTS_1);
+        assertNotNull(delta);
+        assertEquals(24 - 14, delta.displayEnergyUJ);
+        assertNull(delta.otherTotalEnergyUJ); // Although in the results, they're not in the idMap
+        assertNull(delta.otherUidEnergiesUJ);
     }
 
     @Test
-    public void testUpdateAndGetDelta_null() {
-        assertNull(mSnapshot.updateAndGetDelta(null));
+    public void testGetNumOtherOrdinals() {
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
+        assertEquals(3, snapshot.getNumOtherOrdinals());
     }
 
     @Test
-    public void testHasSubsystem() {
-        // Setup MeasuredEnergySnapshot which reported some of the subsystems.
-        final int[] subsystems = {SUBSYSTEM_DISPLAY, SUBSYSTEM_CATAPULT};
-        MeasuredEnergyArray measuredEnergyArray = new MeasuredEnergyArray() {
-            @Override
-            public int getSubsystem(int index) {
-                return subsystems[index];
-            }
+    public void testGetNumOtherOrdinals_none() {
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(SOME_ID_CONSUMER_MAP);
+        assertEquals(0, snapshot.getNumOtherOrdinals());
+    }
 
-            @Override
-            public long getEnergy(int index) {
-                return 0; // Irrelevant for this test.
-            }
+    private static EnergyConsumer createEnergyConsumer(int id, int ord, byte type, String name) {
+        final EnergyConsumer ec = new EnergyConsumer();
+        ec.id = id;
+        ec.ordinal = ord;
+        ec.type = type;
+        ec.name = name;
+        return ec;
+    }
 
-            @Override
-            public int size() {
-                return subsystems.length;
-            }
-        };
-        final MeasuredEnergySnapshot snapshot =
-                new MeasuredEnergySnapshot(NUMBER_SUBSYSTEMS, measuredEnergyArray);
+    private static SparseArray<EnergyConsumer> createIdToConsumerMap(EnergyConsumer ... ecs) {
+        final SparseArray<EnergyConsumer> map = new SparseArray<>();
+        for (EnergyConsumer ec : ecs) {
+            map.put(ec.id, ec);
+        }
+        return map;
+    }
 
-        assertTrue(snapshot.hasSubsystem(SUBSYSTEM_DISPLAY));
-        assertTrue(snapshot.hasSubsystem(SUBSYSTEM_CATAPULT));
-        assertFalse(snapshot.hasSubsystem(SUBSYSTEM_NEVER_USED));
+    private static EnergyConsumerResult createEnergyConsumerResult(
+            int id, long energyUWs, int[] uids, long[] uidEnergies) {
+        final EnergyConsumerResult ecr = new EnergyConsumerResult();
+        ecr.id = id;
+        ecr.energyUWs = energyUWs;
+        if (uids != null) {
+            ecr.attribution = new EnergyConsumerAttribution[uids.length];
+            for (int i = 0; i < uids.length; i++) {
+                ecr.attribution[i] = new EnergyConsumerAttribution();
+                ecr.attribution[i].uid = uids[i];
+                ecr.attribution[i].energyUWs = uidEnergies[i];
+            }
+        }
+        return ecr;
+    }
+
+    private void assertNullOrEmpty(SparseLongArray a) {
+        if (a != null) assertEquals("Array should be null or empty", 0, a.size());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 1a22661..a078a77 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -52,7 +52,8 @@
 public final class DeviceStateManagerServiceTest {
     private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(0, "DEFAULT");
     private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(1, "OTHER");
-    private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(999, "UNSUPPORTED");
+    // A device state that is not reported as being supported for the default test provider.
+    private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(255, "UNSUPPORTED");
 
     private TestDeviceStatePolicy mPolicy;
     private TestDeviceStateProvider mProvider;
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
new file mode 100644
index 0000000..b5c8053
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicestate;
+
+import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link DeviceState}.
+ * <p/>
+ * Run with <code>atest DeviceStateTest</code>.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public final class DeviceStateTest {
+    @Test
+    public void testConstruct() {
+        final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE /* identifier */,
+                "CLOSED" /* name */);
+        assertEquals(state.getIdentifier(), MINIMUM_DEVICE_STATE);
+        assertEquals(state.getName(), "CLOSED");
+    }
+
+    @Test
+    public void testConstruct_nullName() {
+        final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE /* identifier */,
+                null /* name */);
+        assertEquals(state.getIdentifier(), MAXIMUM_DEVICE_STATE);
+        assertNull(state.getName());
+    }
+
+    @Test
+    public void testConstruct_tooLargeIdentifier() {
+        assertThrows(IllegalArgumentException.class, () -> {
+            final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE + 1 /* identifier */,
+                    null /* name */);
+        });
+    }
+
+    @Test
+    public void testConstruct_tooSmallIdentifier() {
+        assertThrows(IllegalArgumentException.class, () -> {
+            final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE - 1 /* identifier */,
+                    null /* name */);
+        });
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
new file mode 100644
index 0000000..b8dfd56
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
+import static com.android.server.hdmi.Constants.ADDR_TV;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+
+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.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+/** Tests for {@link ActiveSourceAction} */
+@SmallTest
+@RunWith(JUnit4.class)
+public class PowerStatusMonitorActionTest {
+
+    private Context mContextSpy;
+    private HdmiControlService mHdmiControlService;
+    private FakeNativeWrapper mNativeWrapper;
+
+    private TestLooper mTestLooper = new TestLooper();
+    private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+    private int mPhysicalAddress;
+    private HdmiCecLocalDeviceTv mTvDevice;
+
+    @Mock
+    private IPowerManager mIPowerManagerMock;
+    @Mock
+    private IThermalService mIThermalServiceMock;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+        PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
+        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
+        when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+        when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+
+        HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
+
+        mHdmiControlService = new HdmiControlService(mContextSpy) {
+            @Override
+            AudioManager getAudioManager() {
+                return new AudioManager() {
+                    @Override
+                    public void setWiredDeviceConnectionState(
+                            int type, int state, String address, String name) {
+                        // Do nothing.
+                    }
+                };
+            }
+
+            @Override
+            void wakeUp() {
+            }
+
+            @Override
+            boolean isPowerStandby() {
+                return false;
+            }
+
+            @Override
+            protected PowerManager getPowerManager() {
+                return powerManager;
+            }
+
+            @Override
+            protected void writeStringSystemProperty(String key, String value) {
+                // do nothing
+            }
+
+            @Override
+            protected HdmiCecConfig getHdmiCecConfig() {
+                return hdmiCecConfig;
+            }
+        };
+
+        Looper looper = mTestLooper.getLooper();
+        mHdmiControlService.setIoLooper(looper);
+        mNativeWrapper = new FakeNativeWrapper();
+        HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
+                this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+        mHdmiControlService.setCecController(hdmiCecController);
+        mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+        mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+        mTvDevice = new HdmiCecLocalDeviceTv(mHdmiControlService);
+        mTvDevice.init();
+        mLocalDevices.add(mTvDevice);
+        mTestLooper.dispatchAll();
+        HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[2];
+        hdmiPortInfo[0] =
+                new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false);
+        hdmiPortInfo[1] =
+                new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false);
+        mNativeWrapper.setPortInfo(hdmiPortInfo);
+        mHdmiControlService.initService();
+        mPhysicalAddress = 0x0000;
+        mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+    }
+
+    @Test
+    public void sourceDevice_1_4_updatesPowerState() {
+        sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+
+        PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+        action.start();
+        assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_UNKNOWN);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                ADDR_TV,
+                ADDR_PLAYBACK_1);
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+        reportPowerStatus(ADDR_PLAYBACK_1, false, HdmiControlManager.POWER_STATUS_ON);
+        assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_ON);
+
+        mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(60));
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+        reportPowerStatus(ADDR_PLAYBACK_1, false, HdmiControlManager.POWER_STATUS_STANDBY);
+        assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_STANDBY);
+    }
+
+    private void assertPowerStatus(int logicalAddress, int powerStatus) {
+        HdmiDeviceInfo deviceInfo = mHdmiControlService.getHdmiCecNetwork().getCecDeviceInfo(
+                logicalAddress);
+        assertThat(deviceInfo).isNotNull();
+        assertThat(deviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus);
+    }
+
+    @Test
+    public void sourceDevice_2_0_doesNotUpdatePowerState() {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+        reportPowerStatus(ADDR_PLAYBACK_1, true, HdmiControlManager.POWER_STATUS_ON);
+        mTestLooper.dispatchAll();
+
+        PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+        action.start();
+
+        assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_ON);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                ADDR_TV,
+                ADDR_PLAYBACK_1);
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus);
+
+        mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(60));
+        mTestLooper.dispatchAll();
+
+        assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_ON);
+    }
+
+    @Test
+    public void mixedSourceDevices_localDevice_1_4_updatesAll() {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+        mTestLooper.dispatchAll();
+        sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+        sendMessageFromPlaybackDevice(ADDR_PLAYBACK_2, 0x2000);
+        reportPowerStatus(ADDR_PLAYBACK_2, true, HdmiControlManager.POWER_STATUS_ON);
+
+        assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_UNKNOWN);
+        assertPowerStatus(ADDR_PLAYBACK_2, HdmiControlManager.POWER_STATUS_ON);
+
+        PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+        action.start();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                ADDR_TV,
+                ADDR_PLAYBACK_1);
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+        HdmiCecMessage giveDevicePowerStatus2 = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                ADDR_TV,
+                ADDR_PLAYBACK_2);
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus2);
+    }
+
+    @Test
+    public void mixedSourceDevices_localDevice_2_0_onlyUpdates_1_4() {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        mTestLooper.dispatchAll();
+        sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+        sendMessageFromPlaybackDevice(ADDR_PLAYBACK_2, 0x2000);
+        reportPowerStatus(ADDR_PLAYBACK_2, true, HdmiControlManager.POWER_STATUS_ON);
+
+        PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+        action.start();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                ADDR_TV,
+                ADDR_PLAYBACK_1);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+        HdmiCecMessage giveDevicePowerStatus2 = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                ADDR_TV,
+                ADDR_PLAYBACK_2);
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus2);
+    }
+
+    private void sendMessageFromPlaybackDevice(int logicalAddress, int physicalAddress) {
+        HdmiCecMessage playbackDevice = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                logicalAddress, physicalAddress, HdmiDeviceInfo.DEVICE_PLAYBACK);
+        mNativeWrapper.onCecMessage(playbackDevice);
+        mTestLooper.dispatchAll();
+    }
+
+    private void reportPowerStatus(int logicalAddress, boolean broadcast, int powerStatus) {
+        int destination = broadcast ? ADDR_BROADCAST : ADDR_TV;
+        HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+                logicalAddress, destination,
+                powerStatus);
+        mNativeWrapper.onCecMessage(reportPowerStatus);
+        mTestLooper.dispatchAll();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
index 353ac4b..f9b25d9 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
@@ -39,6 +39,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
 
@@ -51,7 +52,7 @@
     private static final String TAG = "WorkerCountTrackerTest";
 
     private static final double[] EQUAL_PROBABILITY_CDF =
-            buildCdf(1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES,
+            buildWorkTypeCdf(1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES,
                     1.0 / NUM_WORK_TYPES);
 
     private Random mRandom;
@@ -64,18 +65,19 @@
     }
 
     @NonNull
-    private static double[] buildCdf(double pTop, double pEj, double pBg, double pBgUser) {
-        double[] cdf = new double[JobConcurrencyManager.NUM_WORK_TYPES];
+    private static double[] buildWorkTypeCdf(double pTop, double pEj, double pBg, double pBgUser) {
+        return buildCdf(pTop, pEj, pBg, pBgUser);
+    }
+
+    @NonNull
+    private static double[] buildCdf(double... probs) {
+        double[] cdf = new double[probs.length];
         double sum = 0;
 
-        sum += pTop;
-        cdf[0] = sum;
-        sum += pEj;
-        cdf[1] = sum;
-        sum += pBg;
-        cdf[2] = sum;
-        sum += pBgUser;
-        cdf[3] = sum;
+        for (int i = 0; i < probs.length; ++i) {
+            sum += probs[i];
+            cdf[i] = sum;
+        }
 
         if (Double.compare(1, sum) != 0) {
             throw new IllegalArgumentException("probabilities don't sum to one: " + sum);
@@ -83,25 +85,30 @@
         return cdf;
     }
 
-    @JobConcurrencyManager.WorkType
-    static int getRandomWorkType(double[] cdf, double rand) {
+    static int getRandomIndex(double[] cdf, double rand) {
         for (int i = cdf.length - 1; i >= 0; --i) {
             if (rand < cdf[i] && (i == 0 || rand > cdf[i - 1])) {
-                switch (i) {
-                    case 0:
-                        return WORK_TYPE_TOP;
-                    case 1:
-                        return WORK_TYPE_EJ;
-                    case 2:
-                        return WORK_TYPE_BG;
-                    case 3:
-                        return WORK_TYPE_BGUSER;
-                    default:
-                        throw new IllegalStateException("Unknown work type");
-                }
+                return i;
             }
         }
-        throw new IllegalStateException("Couldn't pick random work type");
+        throw new IllegalStateException("Couldn't pick random index");
+    }
+
+    @JobConcurrencyManager.WorkType
+    static int getRandomWorkType(double[] cdf, double rand) {
+        final int index = getRandomIndex(cdf, rand);
+        switch (index) {
+            case 0:
+                return WORK_TYPE_TOP;
+            case 1:
+                return WORK_TYPE_EJ;
+            case 2:
+                return WORK_TYPE_BG;
+            case 3:
+                return WORK_TYPE_BGUSER;
+            default:
+                throw new IllegalStateException("Unknown work type");
+        }
     }
 
     /**
@@ -110,25 +117,59 @@
     class Jobs {
         public final SparseIntArray running = new SparseIntArray();
         public final SparseIntArray pending = new SparseIntArray();
+        public final List<Integer> pendingMultiTypes = new ArrayList<>();
 
-        public void maybeEnqueueJobs(double probStart, double[] typeCdf) {
+        /**
+         * @param probStart   Probability of starting a job
+         * @param typeCdf     The CDF representing the probability of each work type
+         * @param numTypesCdf The CDF representing the probability of a job having X different
+         *                    work types. Each index i represents i+1 work types (ie. index 0 = 1
+         *                    work type, index 3 = 4 work types).
+         */
+        public void maybeEnqueueJobs(double probStart, double[] typeCdf, double[] numTypesCdf) {
+            assertThat(numTypesCdf.length).isAtMost(NUM_WORK_TYPES);
+            assertThat(numTypesCdf.length).isAtLeast(1);
+
             while (mRandom.nextDouble() < probStart) {
-                final int workType = getRandomWorkType(typeCdf, mRandom.nextDouble());
-                pending.put(workType, pending.get(workType) + 1);
+                final int numTypes = getRandomIndex(numTypesCdf, mRandom.nextDouble()) + 1;
+                int types = WORK_TYPE_NONE;
+                for (int i = 0; i < numTypes; ++i) {
+                    types |= getRandomWorkType(typeCdf, mRandom.nextDouble());
+                }
+                addPending(types, 1);
             }
         }
 
-        public void maybeFinishJobs(double probStop) {
-            for (int i = running.get(WORK_TYPE_BG); i > 0; i--) {
-                if (mRandom.nextDouble() < probStop) {
-                    running.put(WORK_TYPE_BG, running.get(WORK_TYPE_BG) - 1);
-                    mWorkCountTracker.onJobFinished(WORK_TYPE_BG);
+        void addPending(int allWorkTypes, int num) {
+            for (int n = 0; n < num; ++n) {
+                for (int i = 0; i < 32; ++i) {
+                    final int type = 1 << i;
+                    if ((allWorkTypes & type) != 0) {
+                        pending.put(type, pending.get(type) + 1);
+                    }
+                }
+                pendingMultiTypes.add(allWorkTypes);
+            }
+        }
+
+        void removePending(int allWorkTypes) {
+            for (int i = 0; i < 32; ++i) {
+                final int type = 1 << i;
+                if ((allWorkTypes & type) != 0) {
+                    pending.put(type, pending.get(type) - 1);
                 }
             }
-            for (int i = running.get(WORK_TYPE_TOP); i > 0; i--) {
-                if (mRandom.nextDouble() < probStop) {
-                    running.put(WORK_TYPE_TOP, running.get(WORK_TYPE_TOP) - 1);
-                    mWorkCountTracker.onJobFinished(WORK_TYPE_TOP);
+            pendingMultiTypes.remove(Integer.valueOf(allWorkTypes));
+        }
+
+        public void maybeFinishJobs(double probStop) {
+            for (int i = running.size() - 1; i >= 0; --i) {
+                final int workType = running.keyAt(i);
+                for (int c = running.valueAt(i); c > 0; --c) {
+                    if (mRandom.nextDouble() < probStop) {
+                        running.put(workType, running.get(workType) - 1);
+                        mWorkCountTracker.onJobFinished(workType);
+                    }
                 }
             }
         }
@@ -171,6 +212,15 @@
         return false;
     }
 
+    private int getPendingMultiType(Jobs jobs, @JobConcurrencyManager.WorkType int workType) {
+        for (int multiType : jobs.pendingMultiTypes) {
+            if ((multiType & workType) != 0) {
+                return multiType;
+            }
+        }
+        throw new IllegalStateException("No pending multi type with work type: " + workType);
+    }
+
     private void startPendingJobs(Jobs jobs) {
         while (hasStartablePendingJob(jobs)) {
             final int startingWorkType =
@@ -178,9 +228,10 @@
 
             if (jobs.pending.get(startingWorkType) > 0
                     && mWorkCountTracker.canJobStart(startingWorkType) != WORK_TYPE_NONE) {
-                jobs.pending.put(startingWorkType, jobs.pending.get(startingWorkType) - 1);
+                final int pendingMultiType = getPendingMultiType(jobs, startingWorkType);
+                jobs.removePending(pendingMultiType);
                 jobs.running.put(startingWorkType, jobs.running.get(startingWorkType) + 1);
-                mWorkCountTracker.stageJob(startingWorkType);
+                mWorkCountTracker.stageJob(startingWorkType, pendingMultiType);
                 mWorkCountTracker.onJobStarted(startingWorkType);
             }
         }
@@ -192,12 +243,17 @@
     private void checkRandom(Jobs jobs, int numTests, int totalMax,
             @NonNull List<Pair<Integer, Integer>> minLimits,
             @NonNull List<Pair<Integer, Integer>> maxLimits,
-            double probStart, double[] typeCdf, double probStop) {
+            double probStart, double[] typeCdf, double[] numTypesCdf, double probStop) {
+        int minExpected = 0;
+        for (Pair<Integer, Integer> minLimit : minLimits) {
+            minExpected = Math.min(minLimit.second, minExpected);
+        }
         for (int i = 0; i < numTests; i++) {
             jobs.maybeFinishJobs(probStop);
-            jobs.maybeEnqueueJobs(probStart, typeCdf);
+            jobs.maybeEnqueueJobs(probStart, typeCdf, numTypesCdf);
 
             recount(jobs, totalMax, minLimits, maxLimits);
+            final int numPending = jobs.pendingMultiTypes.size();
             startPendingJobs(jobs);
 
             int totalRunning = 0;
@@ -209,6 +265,7 @@
                 totalRunning += numRunning;
             }
             assertThat(totalRunning).isAtMost(totalMax);
+            assertThat(totalRunning).isAtLeast(Math.min(minExpected, numPending));
             for (Pair<Integer, Integer> maxLimit : maxLimits) {
                 assertWithMessage("Work type " + maxLimit.first + " is running too many jobs")
                         .that(jobs.running.get(maxLimit.first)).isAtMost(maxLimit.second);
@@ -233,7 +290,7 @@
         final double probStart = 0.1;
 
         checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
-                EQUAL_PROBABILITY_CDF, probStop);
+                EQUAL_PROBABILITY_CDF, EQUAL_PROBABILITY_CDF, probStop);
     }
 
     @Test
@@ -246,10 +303,12 @@
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final List<Pair<Integer, Integer>> minLimits = List.of();
         final double probStop = 0.5;
-        final double[] cdf = buildCdf(0.5, 0, 0.5, 0);
+        final double[] cdf = buildWorkTypeCdf(0.5, 0, 0.5, 0);
+        final double[] numTypesCdf = buildCdf(.5, .3, .15, .05);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -262,10 +321,12 @@
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.5;
-        final double[] cdf = buildCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3);
+        final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3);
+        final double[] numTypesCdf = buildCdf(.75, .2, .05);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -278,10 +339,12 @@
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final List<Pair<Integer, Integer>> minLimits = List.of();
         final double probStop = 0.5;
-        final double[] cdf = buildCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3);
+        final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3);
+        final double[] numTypesCdf = buildCdf(.05, .95);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -294,10 +357,12 @@
                 List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
         final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.5;
-        final double[] cdf = buildCdf(0.1, 0, 0.8, .1);
+        final double[] cdf = buildWorkTypeCdf(0.1, 0, 0.8, .1);
+        final double[] numTypesCdf = buildCdf(.5, .3, .15, .05);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -310,10 +375,12 @@
                 List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
         final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.5;
-        final double[] cdf = buildCdf(0.9, 0, 0.1, 0);
+        final double[] cdf = buildWorkTypeCdf(0.9, 0, 0.1, 0);
+        final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -326,10 +393,12 @@
                 List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
         final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.4;
-        final double[] cdf = buildCdf(0.1, 0, 0.1, .8);
+        final double[] cdf = buildWorkTypeCdf(0.1, 0, 0.1, .8);
+        final double[] numTypesCdf = buildCdf(0.5, 0.5);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -343,10 +412,12 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final double probStop = 0.4;
-        final double[] cdf = buildCdf(0.9, 0, 0.05, 0.05);
+        final double[] cdf = buildWorkTypeCdf(0.9, 0, 0.05, 0.05);
+        final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -360,10 +431,12 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final double probStop = 0.5;
-        final double[] cdf = buildCdf(0, 0, 0.5, 0.5);
+        final double[] cdf = buildWorkTypeCdf(0, 0, 0.5, 0.5);
+        final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -377,10 +450,12 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final double probStop = 0.5;
-        final double[] cdf = buildCdf(0, 0, 0.1, 0.9);
+        final double[] cdf = buildWorkTypeCdf(0, 0, 0.1, 0.9);
+        final double[] numTypesCdf = buildCdf(0.9, 0.1);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -394,10 +469,12 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final double probStop = 0.5;
-        final double[] cdf = buildCdf(0, 0, 0.9, 0.1);
+        final double[] cdf = buildWorkTypeCdf(0, 0, 0.9, 0.1);
+        final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -410,10 +487,12 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.4;
-        final double[] cdf = buildCdf(0.5, 0.5, 0, 0);
+        final double[] cdf = buildWorkTypeCdf(0.5, 0.5, 0, 0);
+        final double[] numTypesCdf = buildCdf(0.1, 0.7, 0.2);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -430,10 +509,11 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1));
         final double probStop = 0.01;
+        final double[] numTypesCdf = buildCdf(0, 0.05, 0.05, 0.9);
         final double probStart = 0.99;
 
         checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
-                EQUAL_PROBABILITY_CDF, probStop);
+                EQUAL_PROBABILITY_CDF, numTypesCdf, probStop);
     }
 
     @Test
@@ -446,10 +526,12 @@
                 List.of(Pair.create(WORK_TYPE_EJ, 5), Pair.create(WORK_TYPE_BG, 4));
         final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.4;
-        final double[] cdf = buildCdf(.1, 0.5, 0.35, 0.05);
+        final double[] cdf = buildWorkTypeCdf(.1, 0.5, 0.35, 0.05);
+        final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -464,10 +546,12 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.4;
-        final double[] cdf = buildCdf(0.01, 0.49, 0.1, 0.4);
+        final double[] cdf = buildWorkTypeCdf(0.01, 0.49, 0.1, 0.4);
+        final double[] numTypesCdf = buildCdf(0.7, 0.3);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     /** Used by the following tests */
@@ -483,7 +567,7 @@
             jobs.running.put(run.first, run.second);
         }
         for (Pair<Integer, Integer> pend : pending) {
-            jobs.pending.put(pend.first, pend.second);
+            jobs.addPending(pend.first, pend.second);
         }
 
         recount(jobs, totalMax, minLimits, maxLimits);
@@ -651,14 +735,32 @@
                         Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1)),
                 /* resPen */ List.of(
                         Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 2)));
+
+        // Test multi-types
+        checkSimple(6,
+                /* min */ List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(
+                        Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1)),
+                /* run */ List.of(),
+                /* pen */ List.of(
+                        // 2 of these as TOP, 1 as EJ
+                        Pair.create(WORK_TYPE_TOP | WORK_TYPE_EJ, 3),
+                        // 1 as EJ, 2 as BG
+                        Pair.create(WORK_TYPE_EJ | WORK_TYPE_BG, 3),
+                        Pair.create(WORK_TYPE_BG, 4),
+                        Pair.create(WORK_TYPE_BGUSER, 1)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2),
+                        Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2)),
+                /* resPen */ List.of(
+                        Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1)));
     }
 
     /** Tests that the counter updates properly when jobs are stopped. */
     @Test
     public void testJobLifecycleLoop() {
         final Jobs jobs = new Jobs();
-        jobs.pending.put(WORK_TYPE_TOP, 11);
-        jobs.pending.put(WORK_TYPE_BG, 10);
+        jobs.addPending(WORK_TYPE_TOP, 11);
+        jobs.addPending(WORK_TYPE_BG, 10);
 
         final int totalMax = 6;
         final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 1));
@@ -729,4 +831,149 @@
         assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
         assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(3);
     }
+
+    /** Tests that the counter updates properly when jobs are stopped. */
+    @Test
+    public void testJobLifecycleLoop_Multitype() {
+        final Jobs jobs = new Jobs();
+        jobs.addPending(WORK_TYPE_TOP, 6); // a
+        jobs.addPending(WORK_TYPE_TOP | WORK_TYPE_EJ, 5); // b
+        jobs.addPending(WORK_TYPE_BG, 10); // c
+
+        final int totalMax = 8;
+        final List<Pair<Integer, Integer>> minLimits =
+                List.of(Pair.create(WORK_TYPE_EJ, 1), Pair.create(WORK_TYPE_BG, 1));
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 5));
+
+        recount(jobs, totalMax, minLimits, maxLimits);
+
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(11);
+        assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(5);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(10);
+
+        startPendingJobs(jobs);
+
+        assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(6);
+        assertThat(jobs.running.get(WORK_TYPE_EJ)).isEqualTo(1);
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(4);
+        // Since starting happens in random order, all EJs could have run first.
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(9);
+
+        // Stop all jobs
+        jobs.maybeFinishJobs(1);
+
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_TOP);
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_EJ)).isEqualTo(WORK_TYPE_EJ);
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+        startPendingJobs(jobs);
+
+        assertThat(jobs.running.get(WORK_TYPE_TOP) + jobs.running.get(WORK_TYPE_EJ)).isEqualTo(4);
+        // Depending on the order jobs start, we may run all TOP/EJ combos as TOP and reserve a slot
+        // for EJ, which would reduce BG count to 3 instead of 4.
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isAtLeast(3);
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isAtMost(4);
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+        assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(0);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtLeast(5);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtMost(6);
+
+        // Stop only a bg job and make sure the counter only allows another bg job to start.
+        jobs.running.put(WORK_TYPE_BG, jobs.running.get(WORK_TYPE_BG) - 1);
+        mWorkCountTracker.onJobFinished(WORK_TYPE_BG);
+
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_NONE);
+        // Depending on the order jobs start, we may run all TOP/EJ combos as TOP and reserve a slot
+        // for EJ, which would reduce BG count to 3 instead of 4.
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+        startPendingJobs(jobs);
+
+        assertThat(jobs.running.get(WORK_TYPE_TOP) + jobs.running.get(WORK_TYPE_EJ)).isEqualTo(4);
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isAtLeast(3);
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isAtMost(4);
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+        assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(0);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtLeast(4);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtMost(5);
+    }
+
+    /** Tests that the counter updates properly when jobs are stopped. */
+    @Test
+    public void testJobLifecycleLoop_Multitype_RandomOrder() {
+        final Jobs jobs = new Jobs();
+        SparseIntArray multiToCount = new SparseIntArray();
+        multiToCount.put(WORK_TYPE_TOP, 6); // a
+        multiToCount.put(WORK_TYPE_TOP | WORK_TYPE_EJ, 5); // b
+        multiToCount.put(WORK_TYPE_EJ | WORK_TYPE_BG, 5); // c
+        multiToCount.put(WORK_TYPE_BG, 5); // d
+        while (multiToCount.size() > 0) {
+            final int index = mRandom.nextInt(multiToCount.size());
+            final int count = multiToCount.valueAt(index);
+            jobs.addPending(multiToCount.keyAt(index), 1);
+            if (count <= 1) {
+                multiToCount.removeAt(index);
+            } else {
+                multiToCount.put(multiToCount.keyAt(index), count - 1);
+            }
+        }
+
+        final int totalMax = 8;
+        final List<Pair<Integer, Integer>> minLimits =
+                List.of(Pair.create(WORK_TYPE_EJ, 1), Pair.create(WORK_TYPE_BG, 1));
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 5));
+
+        recount(jobs, totalMax, minLimits, maxLimits);
+
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(11);
+        assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(10);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(10);
+
+        startPendingJobs(jobs);
+
+        // Random order, but we should have 6 TOP, 1 EJ, and 1 BG running.
+        assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(6);
+        assertThat(jobs.running.get(WORK_TYPE_EJ)).isEqualTo(1);
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(4);
+        // Can't equate pending EJ since some could be running as TOP and BG
+        assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(9);
+
+        // Stop all jobs
+        jobs.maybeFinishJobs(1);
+
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_TOP);
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_EJ)).isEqualTo(WORK_TYPE_EJ);
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+        startPendingJobs(jobs);
+
+        // Random order, but we should have 4 TOP, 1 EJ, and 1 BG running.
+        assertThat(jobs.running.get(WORK_TYPE_TOP)).isAtLeast(1);
+        assertThat(jobs.running.get(WORK_TYPE_EJ)).isAtLeast(1);
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+        // At this point, all TOP should be running (or have already run).
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+        assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(5);
+
+        // Stop only a bg job and make sure the counter only allows another bg job to start.
+        jobs.running.put(WORK_TYPE_BG, jobs.running.get(WORK_TYPE_BG) - 1);
+        mWorkCountTracker.onJobFinished(WORK_TYPE_BG);
+
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_NONE);
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_EJ)).isEqualTo(WORK_TYPE_NONE);
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+        startPendingJobs(jobs);
+
+        assertThat(jobs.running.get(WORK_TYPE_TOP)).isAtLeast(1);
+        assertThat(jobs.running.get(WORK_TYPE_EJ)).isAtLeast(1);
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+        assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(4);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index a4ba4c8..a896f1b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -43,6 +43,7 @@
 import android.content.ContextWrapper;
 import android.content.pm.UserInfo;
 import android.hardware.rebootescrow.IRebootEscrow;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.os.UserManager;
@@ -155,6 +156,11 @@
         }
 
         @Override
+        void post(Handler handler, Runnable runnable) {
+            runnable.run();
+        }
+
+        @Override
         public UserManager getUserManager() {
             return mUserManager;
         }
@@ -369,7 +375,7 @@
 
     @Test
     public void loadRebootEscrowDataIfAvailable_NothingAvailable_Success() throws Exception {
-        mService.loadRebootEscrowDataIfAvailable();
+        mService.loadRebootEscrowDataIfAvailable(null);
     }
 
     @Test
@@ -401,7 +407,7 @@
         doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture());
         when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());
 
-        mService.loadRebootEscrowDataIfAvailable();
+        mService.loadRebootEscrowDataIfAvailable(null);
         verify(mRebootEscrow).retrieveKey();
         assertTrue(metricsSuccessCaptor.getValue());
         verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
@@ -435,7 +441,7 @@
 
         when(mServiceConnection.unwrap(any(), anyLong()))
                 .thenAnswer(invocation -> invocation.getArgument(0));
-        mService.loadRebootEscrowDataIfAvailable();
+        mService.loadRebootEscrowDataIfAvailable(null);
         verify(mServiceConnection).unwrap(any(), anyLong());
         assertTrue(metricsSuccessCaptor.getValue());
         verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
@@ -466,7 +472,7 @@
         when(mInjected.getBootCount()).thenReturn(10);
         when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]);
 
-        mService.loadRebootEscrowDataIfAvailable();
+        mService.loadRebootEscrowDataIfAvailable(null);
         verify(mRebootEscrow).retrieveKey();
         verify(mInjected, never()).reportMetric(anyBoolean());
     }
@@ -493,7 +499,7 @@
         when(mInjected.getBootCount()).thenReturn(10);
         when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]);
 
-        mService.loadRebootEscrowDataIfAvailable();
+        mService.loadRebootEscrowDataIfAvailable(null);
         verify(mInjected, never()).reportMetric(anyBoolean());
     }
 
@@ -527,7 +533,7 @@
         when(mInjected.getBootCount()).thenReturn(10);
         when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());
 
-        mService.loadRebootEscrowDataIfAvailable();
+        mService.loadRebootEscrowDataIfAvailable(null);
         verify(mInjected).reportMetric(eq(true));
     }
 
@@ -557,7 +563,7 @@
         ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
         doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture());
         when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> new byte[32]);
-        mService.loadRebootEscrowDataIfAvailable();
+        mService.loadRebootEscrowDataIfAvailable(null);
         verify(mRebootEscrow).retrieveKey();
         assertFalse(metricsSuccessCaptor.getValue());
     }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java
index bc1e025..28b737b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java
@@ -30,6 +30,7 @@
 
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
@@ -42,7 +43,6 @@
 import org.mockito.stubbing.Answer;
 
 import java.io.File;
-import java.io.IOException;
 
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;
@@ -130,7 +130,7 @@
     @Test
     public void getAndClearRebootEscrowKey_ServiceConnectionException_failure() throws Exception {
         when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption);
-        doThrow(IOException.class).when(mServiceConnection).unwrap(any(), anyLong());
+        doThrow(RemoteException.class).when(mServiceConnection).unwrap(any(), anyLong());
 
         assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport());
         mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey);
diff --git a/services/tests/servicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/StagingManagerTest.java
deleted file mode 100644
index 79935c2..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/StagingManagerTest.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import android.content.Context;
-import android.content.pm.PackageInstaller;
-import android.os.storage.StorageManager;
-import android.platform.test.annotations.Presubmit;
-
-import com.android.internal.os.BackgroundThread;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.File;
-import java.util.function.Predicate;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-
-@Presubmit
-@RunWith(JUnit4.class)
-public class StagingManagerTest {
-    @Rule
-    public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
-
-    private File mTmpDir;
-    private StagingManager mStagingManager;
-
-    @Before
-    public void setup() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        StorageManager storageManager = Mockito.mock(StorageManager.class);
-        Context context = Mockito.mock(Context.class);
-        when(storageManager.isCheckpointSupported()).thenReturn(true);
-        when(context.getSystemService(eq(Context.POWER_SERVICE))).thenReturn(null);
-        when(context.getSystemService(eq(Context.STORAGE_SERVICE))).thenReturn(storageManager);
-
-        mTmpDir = mTemporaryFolder.newFolder("StagingManagerTest");
-        mStagingManager = new StagingManager(context, null);
-    }
-
-    /**
-     * Tests that sessions committed later shouldn't cause earlier ones to fail the overlapping
-     * check.
-     */
-    @Test
-    public void checkNonOverlappingWithStagedSessions_laterSessionShouldNotFailEarlierOnes()
-            throws Exception {
-        // Create 2 sessions with overlapping packages
-        StagingManager.StagedSession session1 = createSession(111, "com.foo", 1);
-        StagingManager.StagedSession session2 = createSession(222, "com.foo", 2);
-
-        mStagingManager.createSession(session1);
-        mStagingManager.createSession(session2);
-        // Session1 should not fail in spite of the overlapping packages
-        mStagingManager.checkNonOverlappingWithStagedSessions(session1);
-        // Session2 should fail due to overlapping packages
-        assertThrows(PackageManagerException.class,
-                () -> mStagingManager.checkNonOverlappingWithStagedSessions(session2));
-    }
-
-    private StagingManager.StagedSession createSession(int sessionId, String packageName,
-            long committedMillis) {
-        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
-                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-        params.isStaged = true;
-
-        InstallSource installSource = InstallSource.create("testInstallInitiator",
-                "testInstallOriginator", "testInstaller", "testAttributionTag");
-
-        PackageInstallerSession session = new PackageInstallerSession(
-                /* callback */ null,
-                /* context */ null,
-                /* pm */ null,
-                /* sessionProvider */ null,
-                /* looper */ BackgroundThread.getHandler().getLooper(),
-                /* stagingManager */ null,
-                /* sessionId */ sessionId,
-                /* userId */ 456,
-                /* installerUid */ -1,
-                /* installSource */ installSource,
-                /* sessionParams */ params,
-                /* createdMillis */ 0L,
-                /* committedMillis */ committedMillis,
-                /* stageDir */ mTmpDir,
-                /* stageCid */ null,
-                /* files */ null,
-                /* checksums */ null,
-                /* prepared */ true,
-                /* committed */ true,
-                /* destroyed */ false,
-                /* sealed */ false,  // Setting to true would trigger some PM logic.
-                /* childSessionIds */ null,
-                /* parentSessionId */ -1,
-                /* isReady */ false,
-                /* isFailed */ false,
-                /* isApplied */false,
-                /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.STAGED_SESSION_NO_ERROR,
-                /* stagedSessionErrorMessage */ "no error");
-
-        StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
-        doReturn(packageName).when(stagedSession).getPackageName();
-        doAnswer(invocation -> {
-            Predicate<StagingManager.StagedSession> filter = invocation.getArgument(0);
-            return filter.test(stagedSession);
-        }).when(stagedSession).sessionContains(any());
-        return stagedSession;
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
index 22020ad..bc84e35 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
@@ -96,7 +96,7 @@
 
         int[] reasons = new int[] {
                 PackageManagerService.REASON_FIRST_BOOT,
-                PackageManagerService.REASON_BOOT,
+                PackageManagerService.REASON_POST_BOOT,
                 PackageManagerService.REASON_INSTALL,
                 PackageManagerService.REASON_BACKGROUND_DEXOPT,
                 PackageManagerService.REASON_AB_OTA,
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index b28994c..5574836 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -28,7 +28,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
 import android.app.timedetector.GnssTimeSuggestion;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index 6dcfb42..daa1b25 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -27,7 +27,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
 import android.app.timedetector.GnssTimeSuggestion;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/services/tests/servicestests/test-apps/ConnTestApp/OWNERS b/services/tests/servicestests/test-apps/ConnTestApp/OWNERS
new file mode 100644
index 0000000..aa87958
--- /dev/null
+++ b/services/tests/servicestests/test-apps/ConnTestApp/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/net/OWNERS
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index ec28baf..07475e9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -1686,6 +1686,11 @@
         }
 
         @Override
+        protected void ensureFilters(ServiceInfo si, int userId) {
+
+        }
+
+        @Override
         protected void loadDefaultsFromConfig() {
             mDefaultComponents.addAll(mDefaults);
         }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index afcf08ef..80a046a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -15,6 +15,11 @@
  */
 package com.android.server.notification;
 
+import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
+import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
+
+import static com.android.server.notification.NotificationManagerService.NotificationListeners.TAG_REQUESTED_LISTENERS;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -27,11 +32,14 @@
 import android.content.ComponentName;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.VersionedPackage;
+import android.os.Bundle;
 import android.service.notification.NotificationListenerFilter;
+import android.service.notification.NotificationListenerService;
 import android.util.ArraySet;
 import android.util.Pair;
+import android.util.Slog;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
@@ -47,8 +55,6 @@
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.util.ArrayList;
-import java.util.List;
 
 public class NotificationListenersTest extends UiServiceTestCase {
 
@@ -80,22 +86,21 @@
 
     @Test
     public void testReadExtraTag() throws Exception {
-        String xml = "<req_listeners>"
+        String xml = "<" + TAG_REQUESTED_LISTENERS+ ">"
                 + "<listener component=\"" + mCn1.flattenToString() + "\" user=\"0\">"
                 + "<allowed types=\"7\" />"
-                + "<disallowed pkgs=\"\" />"
                 + "</listener>"
                 + "<listener component=\"" + mCn2.flattenToString() + "\" user=\"10\">"
                 + "<allowed types=\"4\" />"
-                + "<disallowed pkgs=\"something\" />"
+                + "<disallowed pkg=\"pkg1\" uid=\"243\"/>"
                 + "</listener>"
-                + "</req_listeners>";
+                + "</" + TAG_REQUESTED_LISTENERS + ">";
 
         TypedXmlPullParser parser = Xml.newFastPullParser();
         parser.setInput(new BufferedInputStream(
                 new ByteArrayInputStream(xml.getBytes())), null);
         parser.nextTag();
-        mListeners.readExtraTag("req_listeners", parser);
+        mListeners.readExtraTag(TAG_REQUESTED_LISTENERS, parser);
 
         validateListenersFromXml();
     }
@@ -103,8 +108,9 @@
     @Test
     public void testWriteExtraTag() throws Exception {
         NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
         NotificationListenerFilter nlf2 =
-                new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
+                new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
         mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
         mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2);
 
@@ -134,16 +140,18 @@
 
         assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 10)).getTypes())
                 .isEqualTo(4);
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
         assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 10))
                 .getDisallowedPackages())
-                .contains("something");
+                .contains(a1);
     }
 
     @Test
     public void testOnUserRemoved() {
         NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
         NotificationListenerFilter nlf2 =
-                new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
+                new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
         mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
         mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2);
 
@@ -155,58 +163,68 @@
     }
 
     @Test
-    public void testOnUserUnlocked() {
-        // one exists already, say from xml
-        NotificationListenerFilter nlf =
-                new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
-        mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf);
-
-        // new service exists or backfilling on upgrade to S
+    public void testEnsureFilters_newServiceNoMetadata() {
         ServiceInfo si = new ServiceInfo();
-        si.permission = mListeners.getConfig().bindPermission;
+        si.packageName = "new2";
+        si.name = "comp2";
+
+        mListeners.ensureFilters(si, 0);
+
+        assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))).isNull();
+    }
+
+    @Test
+    public void testEnsureFilters_preExisting() {
+        // one exists already, say from xml
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+        NotificationListenerFilter nlf =
+                new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
+        mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf);
+        ServiceInfo siOld = new ServiceInfo();
+        siOld.packageName = mCn2.getPackageName();
+        siOld.name = mCn2.getClassName();
+
+        mListeners.ensureFilters(siOld, 0);
+
+        assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))).isEqualTo(nlf);
+    }
+
+    @Test
+    public void testEnsureFilters_newServiceWithMetadata() {
+        ServiceInfo si = new ServiceInfo();
         si.packageName = "new";
         si.name = "comp";
-        ResolveInfo ri = new ResolveInfo();
-        ri.serviceInfo = si;
+        si.metaData = new Bundle();
+        si.metaData.putString(NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES, "1,2");
 
-        // incorrect service
-        ServiceInfo si2 = new ServiceInfo();
-        ResolveInfo ri2 = new ResolveInfo();
-        ri2.serviceInfo = si2;
-        si2.packageName = "new2";
-        si2.name = "comp2";
-
-        List<ResolveInfo> ris = new ArrayList<>();
-        ris.add(ri);
-        ris.add(ri2);
-
-        when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(ris);
-
-        mListeners.onUserUnlocked(0);
-
-        assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0)).getTypes())
-                .isEqualTo(4);
-        assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))
-                .getDisallowedPackages())
-                .contains("something");
+        mListeners.ensureFilters(si, 0);
 
         assertThat(mListeners.getNotificationListenerFilter(
                 Pair.create(si.getComponentName(), 0)).getTypes())
-                .isEqualTo(15);
-        assertThat(mListeners.getNotificationListenerFilter(Pair.create(si.getComponentName(), 0))
-                .getDisallowedPackages())
-                .isEmpty();
+                .isEqualTo(FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ALERTING);
+    }
 
-        assertThat(mListeners.getNotificationListenerFilter(Pair.create(si2.getComponentName(), 0)))
-                .isNull();
+    @Test
+    public void testEnsureFilters_newServiceWithEmptyMetadata() {
+        ServiceInfo si = new ServiceInfo();
+        si.packageName = "new";
+        si.name = "comp";
+        si.metaData = new Bundle();
+        si.metaData.putString(NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES, "");
 
+        mListeners.ensureFilters(si, 0);
+
+        assertThat(mListeners.getNotificationListenerFilter(
+                Pair.create(si.getComponentName(), 0)).getTypes())
+                .isEqualTo(0);
     }
 
     @Test
     public void testOnPackageChanged() {
         NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
         NotificationListenerFilter nlf2 =
-                new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
+                new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
         mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
         mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2);
 
@@ -224,8 +242,9 @@
     @Test
     public void testOnPackageChanged_removing() {
         NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
         NotificationListenerFilter nlf2 =
-                new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
+                new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
         mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
         mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf2);
 
@@ -239,4 +258,21 @@
                 .isEqualTo(4);
     }
 
+    @Test
+    public void testOnPackageChanged_removingDisallowedPackage() {
+        NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+        NotificationListenerFilter nlf2 =
+                new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
+        mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
+        mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf2);
+
+        String[] pkgs = new String[] {"pkg1"};
+        int[] uids = new int[] {243};
+        mListeners.onPackagesChanged(true, pkgs, uids);
+
+        assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn1, 0))
+                .getDisallowedPackages()).isEmpty();
+    }
+
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index e8888f4..a640509 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -464,7 +464,7 @@
 
         // Setup managed services
         when(mNlf.isTypeAllowed(anyInt())).thenReturn(true);
-        when(mNlf.isPackageAllowed(anyString())).thenReturn(true);
+        when(mNlf.isPackageAllowed(any())).thenReturn(true);
         when(mNlf.isPackageAllowed(null)).thenReturn(true);
         when(mListeners.getNotificationListenerFilter(any())).thenReturn(mNlf);
         mListener = mListeners.new ManagedServiceInfo(
@@ -7307,7 +7307,7 @@
 
     @Test
     public void testIsVisibleToListener_disallowedPackage() {
-        when(mNlf.isPackageAllowed(null)).thenReturn(false);
+        when(mNlf.isPackageAllowed(any())).thenReturn(false);
 
         StatusBarNotification sbn = mock(StatusBarNotification.class);
         when(sbn.getUserId()).thenReturn(10);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index b4fd302..781cfec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1834,7 +1834,7 @@
         mWm.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /* updateInputWindows */);
     }
 
-    private void performLayout(DisplayContent dc) {
+    static void performLayout(DisplayContent dc) {
         dc.setLayoutNeeded();
         dc.performLayout(true /* initial */, false /* updateImeWindows */);
     }
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 3231f8b..8969695 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -67,11 +67,14 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.when;
 
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.view.Gravity;
 import android.view.InputWindowHandle;
 import android.view.InsetsState;
 import android.view.SurfaceControl;
@@ -559,6 +562,46 @@
         assertTrue(window.isVisibleByPolicy());
     }
 
+    @Test
+    public void testCompatOverrideScale() {
+        final float overrideScale = 2; // 0.5x on client side.
+        final CompatModePackages cmp = mWm.mAtmService.mCompatModePackages;
+        spyOn(cmp);
+        doReturn(overrideScale).when(cmp).getCompatScale(anyString(), anyInt());
+        final WindowState w = createWindow(null, TYPE_APPLICATION_OVERLAY, "win");
+        makeWindowVisible(w);
+        w.setRequestedSize(100, 200);
+        w.mAttrs.width = w.mAttrs.height = WindowManager.LayoutParams.WRAP_CONTENT;
+        w.mAttrs.gravity = Gravity.TOP | Gravity.LEFT;
+        DisplayContentTests.performLayout(mDisplayContent);
+
+        // Frame on screen = 100x200. Compat frame on client = 50x100.
+        final Rect unscaledCompatFrame = new Rect(w.getWindowFrames().mCompatFrame);
+        unscaledCompatFrame.scale(overrideScale);
+        assertEquals(w.getWindowFrames().mFrame, unscaledCompatFrame);
+
+        // Surface should apply the scale.
+        w.prepareSurfaces();
+        verify(w.getPendingTransaction()).setMatrix(w.getSurfaceControl(),
+                overrideScale, 0, 0, overrideScale);
+
+        // According to "dp * density / 160 = px", density is scaled and the size in dp is the same.
+        final CompatibilityInfo compatInfo = cmp.compatibilityInfoForPackageLocked(
+                mContext.getApplicationInfo());
+        final Configuration winConfig = w.getConfiguration();
+        final Configuration clientConfig = new Configuration(w.getConfiguration());
+        compatInfo.applyToConfiguration(clientConfig.densityDpi, clientConfig);
+
+        assertEquals(winConfig.screenWidthDp, clientConfig.screenWidthDp);
+        assertEquals(winConfig.screenHeightDp, clientConfig.screenHeightDp);
+        assertEquals(winConfig.smallestScreenWidthDp, clientConfig.smallestScreenWidthDp);
+        assertEquals(winConfig.densityDpi, (int) (clientConfig.densityDpi * overrideScale));
+
+        final Rect unscaledClientBounds = new Rect(clientConfig.windowConfiguration.getBounds());
+        unscaledClientBounds.scale(overrideScale);
+        assertEquals(w.getWindowConfiguration().getBounds(), unscaledClientBounds);
+    }
+
     @UseTestDisplay(addWindows = { W_ABOVE_ACTIVITY, W_NOTIFICATION_SHADE })
     @Test
     public void testRequestDrawIfNeeded() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index eb6c6ed..83b30a9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -337,6 +337,7 @@
 
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
         attrs.setTitle(name);
+        attrs.packageName = "test";
 
         final WindowState w = new WindowState(service, session, iWindow, token, parent,
                 OP_NONE, attrs, VISIBLE, ownerId, userId,
diff --git a/services/usb/OWNERS b/services/usb/OWNERS
index 8ee72b5..60172a3 100644
--- a/services/usb/OWNERS
+++ b/services/usb/OWNERS
@@ -1,6 +1,5 @@
 badhri@google.com
 elaurent@google.com
-moltmann@google.com
 albertccwang@google.com
 jameswei@google.com
 howardyen@google.com
\ No newline at end of file
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 3b06fd3..170ed3e 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -2024,7 +2024,7 @@
         boolean isHandover = request.getExtras() != null && request.getExtras().getBoolean(
                 TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, false);
         boolean addSelfManaged = request.getExtras() != null && request.getExtras().getBoolean(
-                PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, false);
+                PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true);
         Log.i(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, "
                         + "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b, "
                         + " addSelfManaged: %b", callManagerAccount, callId, request, isIncoming,
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 99f2e5e..c6757fb 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -646,7 +646,7 @@
             TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
             TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO,
             TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
-            TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE})
+            TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED})
     public @interface OverrideNetworkType {}
 
     /**
diff --git a/telephony/java/android/telephony/RadioInterfaceCapabilities.java b/telephony/java/android/telephony/RadioInterfaceCapabilities.java
deleted file mode 100644
index 7c7eb9f..0000000
--- a/telephony/java/android/telephony/RadioInterfaceCapabilities.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony;
-
-import android.util.ArraySet;
-
-/**
- * Contains the set of supported capabilities that the Radio Interface supports on this device.
- *
- * @hide
- */
-public class RadioInterfaceCapabilities {
-
-    private final ArraySet<String> mSupportedCapabilities;
-
-
-    public RadioInterfaceCapabilities() {
-        mSupportedCapabilities = new ArraySet<>();
-    }
-
-    /**
-     * Marks a capability as supported
-     *
-     * @param capabilityName the name of the capability
-     */
-    public void addSupportedCapability(
-            @TelephonyManager.RadioInterfaceCapability String capabilityName) {
-        mSupportedCapabilities.add(capabilityName);
-    }
-
-    /**
-     * Whether the capability is supported
-     *
-     * @param capabilityName the name of the capability
-     */
-    public boolean isSupported(String capabilityName) {
-        return mSupportedCapabilities.contains(capabilityName);
-    }
-}
diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java
index 1fcb504..5b5570b 100644
--- a/telephony/java/android/telephony/TelephonyDisplayInfo.java
+++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java
@@ -66,9 +66,26 @@
      * {@link TelephonyManager#NETWORK_TYPE_LTE} network and has E-UTRA-NR Dual Connectivity(EN-DC)
      * capability or is currently connected to the secondary
      * {@link TelephonyManager#NETWORK_TYPE_NR} cellular network on millimeter wave bands.
+     * @deprecated Use{@link #OVERRIDE_NETWORK_TYPE_NR_ADVANCED} instead.
      */
+    @Deprecated
     public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4;
 
+    /**
+     * Override network type when the device is connected NR cellular network and the data rate is
+     * higher than the generic 5G date rate.
+     * Including but not limited to
+     * <ul>
+     *   <li>The device is connected to the NR cellular network on millimeter wave bands. </li>
+     *   <li>The device is connected to the specific network which the carrier is using
+     *   proprietary means to provide a faster overall data connection than would be otherwise
+     *   possible. This may include using other bands unique to the carrier, or carrier
+     *   aggregation, for example.</li>
+     * </ul>
+     * One of the use case is that UX can show a different icon, for example, "5G+"
+     */
+    public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4;
+
     @NetworkType
     private final  int mNetworkType;
 
@@ -169,7 +186,7 @@
             case OVERRIDE_NETWORK_TYPE_LTE_CA: return "LTE_CA";
             case OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO: return "LTE_ADV_PRO";
             case OVERRIDE_NETWORK_TYPE_NR_NSA: return "NR_NSA";
-            case OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE: return "NR_NSA_MMWAVE";
+            case OVERRIDE_NETWORK_TYPE_NR_ADVANCED: return "NR_NSA_MMWAVE";
             default: return "UNKNOWN";
         }
     }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 16ffd9e..ee3a0ef 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -8491,6 +8491,11 @@
      * <p>Requires Permission:
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
      * app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     * <p>
+     * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+     * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then
+     * setAllowedNetworkTypesBitmap is used on the radio interface.  Otherwise,
+     * setPreferredNetworkTypesBitmap is used instead.
      *
      * @param subId the id of the subscription to set the preferred network type for.
      * @param networkType the preferred network type
@@ -8524,6 +8529,11 @@
      * <p>Requires Permission:
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
      * app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     * <p>
+     * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+     * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then
+     * setAllowedNetworkTypesBitmap is used on the radio interface.  Otherwise,
+     * setPreferredNetworkTypesBitmap is used instead.
      *
      * @param networkTypeBitmask The bitmask of preferred network types.
      * @return true on success; false on any failure.
@@ -8550,6 +8560,11 @@
      * Set the allowed network types of the device. This is for carrier or privileged apps to
      * enable/disable certain network types on the device. The user preferred network types should
      * be set through {@link #setPreferredNetworkTypeBitmask}.
+     * <p>
+     * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+     * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then
+     * setAllowedNetworkTypesBitmap is used on the radio interface.  Otherwise,
+     * setPreferredNetworkTypesBitmap is used instead.
      *
      * @param allowedNetworkTypes The bitmask of allowed network types.
      * @return true on success; false on any failure.
@@ -8624,6 +8639,11 @@
      * </ol>
      * This API will result in allowing an intersection of allowed network types for all reasons,
      * including the configuration done through other reasons.
+     * <p>
+     * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+     * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then
+     * setAllowedNetworkTypesBitmap is used on the radio interface.  Otherwise,
+     * setPreferredNetworkTypesBitmap is used instead.
      *
      * @param reason the reason the allowed network type change is taking place
      * @param allowedNetworkTypes The bitmask of allowed network types.
@@ -14861,10 +14881,24 @@
     public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE =
             "CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE";
 
+    /**
+     * Indicates whether {@link #setPreferredNetworkType}, {@link
+     * #setPreferredNetworkTypeBitmask}, {@link #setAllowedNetworkTypes} and
+     * {@link #setAllowedNetworkTypesForReason} rely on
+     * setAllowedNetworkTypesBitmap instead of setPreferredNetworkTypesBitmap on the radio
+     * interface.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED =
+            "CAPABILITY_ALLOWED_NETWORK_TYPES_USED";
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @StringDef(prefix = "CAPABILITY_", value = {
             CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE,
+            CAPABILITY_ALLOWED_NETWORK_TYPES_USED,
     })
     public @interface RadioInterfaceCapability {}
 
diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
similarity index 64%
rename from tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
rename to tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
index d1d6a26..0f920b3 100644
--- a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
@@ -16,8 +16,11 @@
 
 package com.android.framework.permission.tests;
 
+import android.content.Context;
 import android.os.Binder;
-import android.os.IVibratorService;
+import android.os.CombinedVibrationEffect;
+import android.os.IBinder;
+import android.os.IVibratorManagerService;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -32,27 +35,28 @@
  * Verify that Hardware apis cannot be called without required permissions.
  */
 @SmallTest
-public class VibratorServicePermissionTest extends TestCase {
+public class VibratorManagerServicePermissionTest extends TestCase {
 
-    private IVibratorService mVibratorService;
+    private IVibratorManagerService mVibratorService;
 
     @Override
     protected void setUp() throws Exception {
-        mVibratorService = IVibratorService.Stub.asInterface(
-                ServiceManager.getService("vibrator"));
+        mVibratorService = IVibratorManagerService.Stub.asInterface(
+                ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE));
     }
 
     /**
-     * Test that calling {@link android.os.IVibratorService#vibrate(long)} requires permissions.
+     * Test that calling {@link android.os.IVibratorManagerService#vibrate(int, String,
+     * CombinedVibrationEffect, VibrationAttributes, String, IBinder)} requires permissions.
      * <p>Tests permission:
-     *   {@link android.Manifest.permission#VIBRATE}
-     * @throws RemoteException
+     * {@link android.Manifest.permission#VIBRATE}
      */
     public void testVibrate() throws RemoteException {
         try {
-            final VibrationEffect effect =
-                    VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
-            final VibrationAttributes attrs = new VibrationAttributes.Builder()
+            CombinedVibrationEffect effect =
+                    CombinedVibrationEffect.createSynced(
+                            VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
+            VibrationAttributes attrs = new VibrationAttributes.Builder()
                     .setUsage(VibrationAttributes.USAGE_ALARM)
                     .build();
             mVibratorService.vibrate(Process.myUid(), null, effect, attrs,
@@ -64,10 +68,10 @@
     }
 
     /**
-     * Test that calling {@link android.os.IVibratorService#cancelVibrate()} requires permissions.
+     * Test that calling {@link android.os.IVibratorManagerService#cancelVibrate(IBinder)} requires
+     * permissions.
      * <p>Tests permission:
-     *   {@link android.Manifest.permission#VIBRATE}
-     * @throws RemoteException
+     * {@link android.Manifest.permission#VIBRATE}
      */
     public void testCancelVibrate() throws RemoteException {
         try {