Merge "Fix composed VibrationEffect timing out"
diff --git a/Android.bp b/Android.bp
index ef25ec2..30b38d3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -326,7 +326,6 @@
             "packages/modules/Connectivity/framework/aidl-export",
             "packages/modules/Media/apex/aidl/stable",
             "hardware/interfaces/graphics/common/aidl",
-            "frameworks/native/libs/permission/aidl",
         ],
     },
     dxflags: [
@@ -596,7 +595,6 @@
             "packages/modules/Connectivity/framework/aidl-export",
             "packages/modules/Media/apex/aidl/stable",
             "hardware/interfaces/graphics/common/aidl",
-            "frameworks/native/libs/permission/aidl",
         ],
     },
     // These are libs from framework-internal-utils that are required (i.e. being referenced)
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 448ee61..a652055 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -155,6 +155,7 @@
     public void tearDown() throws Exception {
         setSystemProperty("debug.usercontroller.user_switch_timeout_ms", mUserSwitchTimeoutMs);
         mBroadcastWaiter.close();
+        mUserSwitchWaiter.close();
         for (int userId : mUsersToRemove) {
             try {
                 mUm.removeUser(userId);
@@ -207,10 +208,10 @@
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
             final int userId = createUserNoFlags();
-            mRunner.resumeTiming();
-            Log.i(TAG, "Starting timer");
-
             runThenWaitForBroadcasts(userId, () -> {
+                mRunner.resumeTiming();
+                Log.i(TAG, "Starting timer");
+
                 mIam.startUserInBackground(userId);
             }, Intent.ACTION_USER_STARTED);
 
@@ -273,9 +274,7 @@
             mRunner.resumeTiming();
             Log.i(TAG, "Starting timer");
 
-            runThenWaitForBroadcasts(testUser, () -> {
-                mAm.switchUser(testUser);
-            }, Intent.ACTION_USER_UNLOCKED);
+            switchUser(testUser);
 
             mRunner.pauseTiming();
             Log.i(TAG, "Stopping timer");
@@ -362,10 +361,10 @@
             }, Intent.ACTION_MEDIA_MOUNTED);
 
             mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(startUser, () -> {
-                mRunner.resumeTiming();
-                Log.i(TAG, "Starting timer");
-
                 runThenWaitForBroadcasts(userId, () -> {
+                    mRunner.resumeTiming();
+                    Log.i(TAG, "Starting timer");
+
                     mAm.switchUser(startUser);
                 }, Intent.ACTION_USER_STOPPED);
 
@@ -425,7 +424,7 @@
             final int userId = createManagedProfile();
             // Start the profile initially, then stop it. Similar to setQuietModeEnabled.
             startUserInBackgroundAndWaitForUnlock(userId);
-            stopUser(userId, true);
+            stopUserAfterWaitingForBroadcastIdle(userId, true);
             mRunner.resumeTiming();
             Log.i(TAG, "Starting timer");
 
@@ -480,7 +479,7 @@
             installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
             startUserInBackgroundAndWaitForUnlock(userId);
             startApp(userId, DUMMY_PACKAGE_NAME);
-            stopUser(userId, true);
+            stopUserAfterWaitingForBroadcastIdle(userId, true);
             SystemClock.sleep(1_000); // 1 second cool-down before re-starting profile.
             mRunner.resumeTiming();
             Log.i(TAG, "Starting timer");
@@ -677,6 +676,19 @@
         return success[0];
     }
 
+    /**
+     * Waits for broadcast idle before stopping a user, to prevent timeouts on stop user.
+     * Stopping a user heavily depends on broadcast queue, and that gets crowded after user creation
+     * or user switches, which leads to a timeout on stopping user and cause the tests to be flaky.
+     * Do not call this method while timing is on. i.e. between mRunner.resumeTiming() and
+     * mRunner.pauseTiming(). Otherwise it would cause the test results to be spiky.
+     */
+    private void stopUserAfterWaitingForBroadcastIdle(int userId, boolean force)
+            throws RemoteException {
+        ShellHelper.runShellCommand("am wait-for-broadcast-idle");
+        stopUser(userId, force);
+    }
+
     private void stopUser(int userId, boolean force) throws RemoteException {
         final CountDownLatch latch = new CountDownLatch(1);
         mIam.stopUser(userId, force /* force */, new IStopUserCallback.Stub() {
@@ -712,7 +724,7 @@
         attestTrue("Didn't switch back to user, " + origUser, origUser == mAm.getCurrentUser());
 
         if (stopNewUser) {
-            stopUser(testUser, true);
+            stopUserAfterWaitingForBroadcastIdle(testUser, true);
             attestFalse("Failed to stop user " + testUser, mAm.isUserRunning(testUser));
         }
 
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserSwitchWaiter.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserSwitchWaiter.java
index 228d14c..8224597 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserSwitchWaiter.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserSwitchWaiter.java
@@ -17,61 +17,87 @@
 package android.multiuser;
 
 import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.app.IUserSwitchObserver;
 import android.app.UserSwitchObserver;
 import android.os.RemoteException;
 import android.util.Log;
 
 import com.android.internal.util.FunctionalUtils;
 
-import java.util.concurrent.CountDownLatch;
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
-public class UserSwitchWaiter {
+public class UserSwitchWaiter implements Closeable {
 
     private final String mTag;
     private final int mTimeoutInSecond;
+    private final IActivityManager mActivityManager;
+    private final IUserSwitchObserver mUserSwitchObserver = new UserSwitchObserver() {
+        @Override
+        public void onUserSwitchComplete(int newUserId) {
+            getSemaphoreSwitchComplete(newUserId).release();
+        }
 
-    public UserSwitchWaiter(String tag, int timeoutInSecond) {
+        @Override
+        public void onLockedBootComplete(int newUserId) {
+            getSemaphoreBootComplete(newUserId).release();
+        }
+    };
+
+    private final Map<Integer, Semaphore> mSemaphoresMapSwitchComplete = new ConcurrentHashMap<>();
+    private Semaphore getSemaphoreSwitchComplete(final int userId) {
+        return mSemaphoresMapSwitchComplete.computeIfAbsent(userId,
+                (Integer absentKey) -> new Semaphore(0));
+    }
+
+    private final Map<Integer, Semaphore> mSemaphoresMapBootComplete = new ConcurrentHashMap<>();
+    private Semaphore getSemaphoreBootComplete(final int userId) {
+        return mSemaphoresMapBootComplete.computeIfAbsent(userId,
+                (Integer absentKey) -> new Semaphore(0));
+    }
+
+    public UserSwitchWaiter(String tag, int timeoutInSecond) throws RemoteException {
         mTag = tag;
         mTimeoutInSecond = timeoutInSecond;
+        mActivityManager = ActivityManager.getService();
+
+        mActivityManager.registerUserSwitchObserver(mUserSwitchObserver, mTag);
+    }
+
+    @Override
+    public void close() throws IOException {
+        try {
+            mActivityManager.unregisterUserSwitchObserver(mUserSwitchObserver);
+        } catch (RemoteException e) {
+            Log.e(mTag, "Failed to unregister user switch observer", e);
+        }
     }
 
     public void runThenWaitUntilSwitchCompleted(int userId,
             FunctionalUtils.ThrowingRunnable runnable, Runnable onFail) throws RemoteException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        ActivityManager.getService().registerUserSwitchObserver(
-                new UserSwitchObserver() {
-                    @Override
-                    public void onUserSwitchComplete(int newUserId) throws RemoteException {
-                        if (userId == newUserId) {
-                            latch.countDown();
-                        }
-                    }
-                }, mTag);
+        final Semaphore semaphore = getSemaphoreSwitchComplete(userId);
+        semaphore.drainPermits();
         runnable.run();
-        waitForLatch(latch, onFail);
+        waitForSemaphore(semaphore, onFail);
     }
 
     public void runThenWaitUntilBootCompleted(int userId,
             FunctionalUtils.ThrowingRunnable runnable, Runnable onFail) throws RemoteException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        ActivityManager.getService().registerUserSwitchObserver(
-                new UserSwitchObserver() {
-                    @Override
-                    public void onLockedBootComplete(int newUserId) {
-                        if (userId == newUserId) {
-                            latch.countDown();
-                        }
-                    }
-                }, mTag);
+        final Semaphore semaphore = getSemaphoreBootComplete(userId);
+        semaphore.drainPermits();
         runnable.run();
-        waitForLatch(latch, onFail);
+        waitForSemaphore(semaphore, onFail);
     }
 
-    private void waitForLatch(CountDownLatch latch, Runnable onFail) {
+    private void waitForSemaphore(Semaphore semaphore, Runnable onFail) {
         boolean success = false;
         try {
-            success = latch.await(mTimeoutInSecond, TimeUnit.SECONDS);
+            success = semaphore.tryAcquire(mTimeoutInSecond, TimeUnit.SECONDS);
         } catch (InterruptedException e) {
             Log.e(mTag, "Thread interrupted unexpectedly.", e);
         }
diff --git a/apct-tests/perftests/surfaceflinger/AndroidManifest.xml b/apct-tests/perftests/surfaceflinger/AndroidManifest.xml
index c908d6a..26f2586 100644
--- a/apct-tests/perftests/surfaceflinger/AndroidManifest.xml
+++ b/apct-tests/perftests/surfaceflinger/AndroidManifest.xml
@@ -16,6 +16,10 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.perftests.surfaceflinger">
 
+    <!-- permission needed to write perfetto trace and read/write simpleperf report -->
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
     <application android:label="SurfaceFlingerPerfTests">
         <uses-library android:name="android.test.runner" />
         <activity android:name="android.surfaceflinger.SurfaceFlingerTestActivity"
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
index 59cd82e..3bbc5a3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
@@ -176,12 +176,13 @@
             Slog.d(TAG, "maybeReportNewChargingStateLocked: "
                     + powerConnected + "/" + stablePower + "/" + batteryNotLow);
         }
-        mFlexibilityController.setConstraintSatisfied(
-                JobStatus.CONSTRAINT_CHARGING, mService.isBatteryCharging());
-        mFlexibilityController
-            .setConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW, batteryNotLow);
-
         final long nowElapsed = sElapsedRealtimeClock.millis();
+
+        mFlexibilityController.setConstraintSatisfied(
+                JobStatus.CONSTRAINT_CHARGING, mService.isBatteryCharging(), nowElapsed);
+        mFlexibilityController.setConstraintSatisfied(
+                        JobStatus.CONSTRAINT_BATTERY_NOT_LOW, batteryNotLow, nowElapsed);
+
         for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
             final JobStatus ts = mTrackedTasks.valueAt(i);
             if (ts.hasChargingConstraint()) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 2e41dfd..9d36478 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.job.controllers;
 
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
@@ -31,6 +32,7 @@
 import android.annotation.Nullable;
 import android.app.job.JobInfo;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Looper;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
@@ -80,31 +82,26 @@
     private static final long NO_LIFECYCLE_END = Long.MAX_VALUE;
 
     /**
-     * Keeps track of what flexible constraints are satisfied at the moment.
-     * Is updated by the other controllers.
-     */
-    @VisibleForTesting
-    @GuardedBy("mLock")
-    int mSatisfiedFlexibleConstraints;
-
-    /** Hard cutoff to remove flexible constraints. */
-    private long mDeadlineProximityLimitMs =
-            FcConfig.DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS;
-
-    /**
      * The default deadline that all flexible constraints should be dropped by if a job lacks
      * a deadline.
      */
     private long mFallbackFlexibilityDeadlineMs =
             FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
 
-    @GuardedBy("mLock")
+    private long mRescheduledJobDeadline = FcConfig.DEFAULT_RESCHEDULED_JOB_DEADLINE_MS;
+    private long mMaxRescheduledDeadline = FcConfig.DEFAULT_MAX_RESCHEDULED_DEADLINE_MS;
+
     @VisibleForTesting
+    @GuardedBy("mLock")
     boolean mFlexibilityEnabled = FcConfig.DEFAULT_FLEXIBILITY_ENABLED;
 
     private long mMinTimeBetweenFlexibilityAlarmsMs =
             FcConfig.DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS;
 
+    /** Hard cutoff to remove flexible constraints. */
+    private long mDeadlineProximityLimitMs =
+            FcConfig.DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS;
+
     /**
      * The percent of a job's lifecycle to drop number of required constraints.
      * mPercentToDropConstraints[i] denotes that at x% of a Jobs lifecycle,
@@ -113,6 +110,17 @@
     private int[] mPercentToDropConstraints;
 
     @VisibleForTesting
+    boolean mDeviceSupportsFlexConstraints;
+
+    /**
+     * Keeps track of what flexible constraints are satisfied at the moment.
+     * Is updated by the other controllers.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    int mSatisfiedFlexibleConstraints;
+
+    @VisibleForTesting
     @GuardedBy("mLock")
     final FlexibilityTracker mFlexibilityTracker;
     @VisibleForTesting
@@ -120,7 +128,6 @@
     final FlexibilityAlarmQueue mFlexibilityAlarmQueue;
     @VisibleForTesting
     final FcConfig mFcConfig;
-
     @VisibleForTesting
     final PrefetchController mPrefetchController;
 
@@ -137,9 +144,9 @@
             new PrefetchController.PrefetchChangedListener() {
                 @Override
                 public void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId,
-                        String pkgName, long prevEstimatedLaunchTime, long newEstimatedLaunchTime) {
+                        String pkgName, long prevEstimatedLaunchTime,
+                        long newEstimatedLaunchTime, long nowElapsed) {
                     synchronized (mLock) {
-                        final long nowElapsed = sElapsedRealtimeClock.millis();
                         final long prefetchThreshold =
                                 mPrefetchController.getLaunchTimeThresholdMs();
                         boolean jobWasInPrefetchWindow  = prevEstimatedLaunchTime
@@ -158,8 +165,8 @@
                             if (!js.hasFlexibilityConstraint()) {
                                 continue;
                             }
-                            mFlexibilityTracker.resetJobNumDroppedConstraints(js);
-                            mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js);
+                            mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
+                            mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed);
                         }
                     }
                 }
@@ -168,6 +175,9 @@
     public FlexibilityController(
             JobSchedulerService service, PrefetchController prefetchController) {
         super(service);
+        mDeviceSupportsFlexConstraints = !mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUTOMOTIVE);
+        mFlexibilityEnabled &= mDeviceSupportsFlexConstraints;
         mFlexibilityTracker = new FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS);
         mFcConfig = new FcConfig();
         mFlexibilityAlarmQueue = new FlexibilityAlarmQueue(
@@ -187,11 +197,15 @@
     @GuardedBy("mLock")
     public void maybeStartTrackingJobLocked(JobStatus js, JobStatus lastJob) {
         if (js.hasFlexibilityConstraint()) {
+            final long nowElapsed = sElapsedRealtimeClock.millis();
+            if (!mDeviceSupportsFlexConstraints) {
+                js.setFlexibilityConstraintSatisfied(nowElapsed, true);
+                return;
+            }
+            js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
             mFlexibilityTracker.add(js);
             js.setTrackingController(JobStatus.TRACKING_FLEXIBILITY);
-            final long nowElapsed = sElapsedRealtimeClock.millis();
-            js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
-            mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js);
+            mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed);
         }
     }
 
@@ -239,7 +253,7 @@
      * Changes flexibility constraint satisfaction for affected jobs.
      */
     @VisibleForTesting
-    void setConstraintSatisfied(int constraint, boolean state) {
+    void setConstraintSatisfied(int constraint, boolean state, long nowElapsed) {
         synchronized (mLock) {
             final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0;
             if (old == state) {
@@ -255,8 +269,6 @@
             // The rest did not have a change in state and are still satisfied or unsatisfied.
             final int numConstraintsToUpdate = Math.max(curSatisfied, prevSatisfied);
 
-            final long nowElapsed = sElapsedRealtimeClock.millis();
-
             // In order to get the range of all potentially satisfied jobs, we start at the number
             // of satisfied system-wide constraints and iterate to the max number of potentially
             // satisfied constraints, determined by how many job-specific constraints exist.
@@ -323,16 +335,21 @@
             // There is no deadline and no estimated launch time.
             return NO_LIFECYCLE_END;
         }
+        if (js.getNumFailures() > 1) {
+            // Number of failures will not equal one as per restriction in JobStatus constructor.
+            return earliest + Math.min(
+                    (long) Math.scalb(mRescheduledJobDeadline, js.getNumFailures() - 2),
+                    mMaxRescheduledDeadline);
+        }
         return js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME
                 ? earliest + mFallbackFlexibilityDeadlineMs : js.getLatestRunTimeElapsed();
     }
 
     @VisibleForTesting
     @GuardedBy("mLock")
-    int getCurPercentOfLifecycleLocked(JobStatus js) {
+    int getCurPercentOfLifecycleLocked(JobStatus js, long nowElapsed) {
         final long earliest = getLifeCycleBeginningElapsedLocked(js);
         final long latest = getLifeCycleEndElapsedLocked(js, earliest);
-        final long nowElapsed = sElapsedRealtimeClock.millis();
         if (latest == NO_LIFECYCLE_END || earliest >= nowElapsed) {
             return 0;
         }
@@ -343,7 +360,6 @@
         return percentInTime;
     }
 
-    /** The elapsed time that marks when the next constraint should be dropped. */
     @VisibleForTesting
     @ElapsedRealtimeLong
     @GuardedBy("mLock")
@@ -354,7 +370,6 @@
     }
 
     /** The elapsed time that marks when the next constraint should be dropped. */
-    @VisibleForTesting
     @ElapsedRealtimeLong
     @GuardedBy("mLock")
     long getNextConstraintDropTimeElapsedLocked(JobStatus js, long earliest, long latest) {
@@ -409,13 +424,13 @@
                 final ArraySet<JobStatus> changedJobs = new ArraySet<>();
                 synchronized (mLock) {
                     final long nowElapsed = sElapsedRealtimeClock.millis();
-                    for (int j = 1; j <= mFlexibilityTracker.size(); j++) {
+                    for (int j = 0; j < mFlexibilityTracker.size(); j++) {
                         final ArraySet<JobStatus> jobs = mFlexibilityTracker
                                 .getJobsByNumRequiredConstraints(j);
                         for (int i = 0; i < jobs.size(); i++) {
                             JobStatus js = jobs.valueAt(i);
-                            mFlexibilityTracker.resetJobNumDroppedConstraints(js);
-                            mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js);
+                            mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
+                            mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed);
                             if (js.setFlexibilityConstraintSatisfied(
                                     nowElapsed, isFlexibilitySatisfiedLocked(js))) {
                                 changedJobs.add(js);
@@ -448,7 +463,7 @@
 
         FlexibilityTracker(int numFlexibleConstraints) {
             mTrackedJobs = new ArrayList<>();
-            for (int i = 0; i < numFlexibleConstraints; i++) {
+            for (int i = 0; i <= numFlexibleConstraints; i++) {
                 mTrackedJobs.add(new ArraySet<JobStatus>());
             }
         }
@@ -460,15 +475,15 @@
                 Slog.wtfStack(TAG, "Asked for a larger number of constraints than exists.");
                 return null;
             }
-            return mTrackedJobs.get(numRequired - 1);
+            return mTrackedJobs.get(numRequired);
         }
 
         /** adds a JobStatus object based on number of required flexible constraints. */
         public void add(JobStatus js) {
-            if (js.getNumRequiredFlexibleConstraints() <= 0) {
+            if (js.getNumRequiredFlexibleConstraints() < 0) {
                 return;
             }
-            mTrackedJobs.get(js.getNumRequiredFlexibleConstraints() - 1).add(js);
+            mTrackedJobs.get(js.getNumRequiredFlexibleConstraints()).add(js);
         }
 
         /** Removes a JobStatus object. */
@@ -476,11 +491,11 @@
             if (js.getNumRequiredFlexibleConstraints() == 0) {
                 return;
             }
-            mTrackedJobs.get(js.getNumRequiredFlexibleConstraints() - 1).remove(js);
+            mTrackedJobs.get(js.getNumRequiredFlexibleConstraints()).remove(js);
         }
 
-        public void resetJobNumDroppedConstraints(JobStatus js) {
-            final int curPercent = getCurPercentOfLifecycleLocked(js);
+        public void resetJobNumDroppedConstraints(JobStatus js, long nowElapsed) {
+            final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed);
             int toDrop = 0;
             final int jsMaxFlexibleConstraints = NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS
                     + (js.getPreferUnmetered() ? 1 : 0);
@@ -489,7 +504,8 @@
                     toDrop++;
                 }
             }
-            adjustJobsRequiredConstraints(js, js.getNumDroppedFlexibleConstraints() - toDrop);
+            adjustJobsRequiredConstraints(
+                    js, js.getNumDroppedFlexibleConstraints() - toDrop, nowElapsed);
         }
 
         /** Returns all tracked jobs. */
@@ -500,22 +516,15 @@
         /**
          * Adjusts number of required flexible constraints and sorts it into the tracker.
          * Returns false if the job status's number of flexible constraints is now 0.
-         * Jobs with 0 required flexible constraints are removed from the tracker.
          */
-        public boolean adjustJobsRequiredConstraints(JobStatus js, int n) {
-            if (n == 0) {
-                return false;
+        public boolean adjustJobsRequiredConstraints(JobStatus js, int adjustBy, long nowElapsed) {
+            if (adjustBy != 0) {
+                remove(js);
+                js.adjustNumRequiredFlexibleConstraints(adjustBy);
+                js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
+                add(js);
             }
-            remove(js);
-            js.adjustNumRequiredFlexibleConstraints(n);
-            final long nowElapsed = sElapsedRealtimeClock.millis();
-            js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
-            if (js.getNumRequiredFlexibleConstraints() <= 0) {
-                maybeStopTrackingJobLocked(js, null, false);
-                return false;
-            }
-            add(js);
-            return true;
+            return js.getNumRequiredFlexibleConstraints() > 0;
         }
 
         public int size() {
@@ -534,6 +543,8 @@
                     js.printUniqueId(pw);
                     pw.print(" from ");
                     UserHandle.formatUid(pw, js.getSourceUid());
+                    pw.print(" Num Required Constraints: ");
+                    pw.print(js.getNumRequiredFlexibleConstraints());
                     pw.println();
                 }
             }
@@ -553,21 +564,25 @@
             return js.getSourceUserId() == userId;
         }
 
-        public void scheduleDropNumConstraintsAlarm(JobStatus js) {
-            long nextTimeElapsed;
+        public void scheduleDropNumConstraintsAlarm(JobStatus js, long nowElapsed) {
             synchronized (mLock) {
                 final long earliest = getLifeCycleBeginningElapsedLocked(js);
                 final long latest = getLifeCycleEndElapsedLocked(js, earliest);
-                nextTimeElapsed = getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
+                final long nextTimeElapsed =
+                        getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
+
+                if (latest - nowElapsed < mDeadlineProximityLimitMs) {
+                    mFlexibilityTracker.adjustJobsRequiredConstraints(js,
+                            -js.getNumRequiredFlexibleConstraints(), nowElapsed);
+                    return;
+                }
                 if (nextTimeElapsed == NO_LIFECYCLE_END) {
                     // There is no known or estimated next time to drop a constraint.
                     removeAlarmForKey(js);
                     return;
                 }
-
                 if (latest - nextTimeElapsed < mDeadlineProximityLimitMs) {
-                    mFlexibilityTracker.adjustJobsRequiredConstraints(
-                            js, -js.getNumRequiredFlexibleConstraints());
+                    addAlarm(js, latest - mDeadlineProximityLimitMs);
                     return;
                 }
                 addAlarm(js, nextTimeElapsed);
@@ -578,24 +593,12 @@
         protected void processExpiredAlarms(@NonNull ArraySet<JobStatus> expired) {
             synchronized (mLock) {
                 ArraySet<JobStatus> changedJobs = new ArraySet<>();
+                final long nowElapsed = sElapsedRealtimeClock.millis();
                 for (int i = 0; i < expired.size(); i++) {
                     JobStatus js = expired.valueAt(i);
                     boolean wasFlexibilitySatisfied = js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE);
-
-                    final long earliest = getLifeCycleBeginningElapsedLocked(js);
-                    final long latest = getLifeCycleEndElapsedLocked(js, earliest);
-                    final long nowElapsed = sElapsedRealtimeClock.millis();
-
-                    if (latest - nowElapsed < mDeadlineProximityLimitMs) {
-                        mFlexibilityTracker.adjustJobsRequiredConstraints(js,
-                                -js.getNumRequiredFlexibleConstraints());
-                    } else {
-                        long nextTimeElapsed =
-                                getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
-                        if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1)
-                                && nextTimeElapsed != NO_LIFECYCLE_END) {
-                            mFlexibilityAlarmQueue.addAlarm(js, nextTimeElapsed);
-                        }
+                    if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1, nowElapsed)) {
+                        scheduleDropNumConstraintsAlarm(js, nowElapsed);
                     }
                     if (wasFlexibilitySatisfied != js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE)) {
                         changedJobs.add(js);
@@ -622,6 +625,10 @@
                 FC_CONFIG_PREFIX + "min_alarm_time_flexibility_ms";
         static final String KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
                 FC_CONFIG_PREFIX + "percents_to_drop_num_flexible_constraints";
+        static final String KEY_MAX_RESCHEDULED_DEADLINE_MS =
+                FC_CONFIG_PREFIX + "max_rescheduled_deadline_ms";
+        static final String KEY_RESCHEDULED_JOB_DEADLINE_MS =
+                FC_CONFIG_PREFIX + "rescheduled_job_deadline_ms";
 
         private static final boolean DEFAULT_FLEXIBILITY_ENABLED = false;
         @VisibleForTesting
@@ -631,6 +638,8 @@
         private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS;
         @VisibleForTesting
         final int[] DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS = {50, 60, 70, 80};
+        private static final long DEFAULT_RESCHEDULED_JOB_DEADLINE_MS = HOUR_IN_MILLIS;
+        private static final long DEFAULT_MAX_RESCHEDULED_DEADLINE_MS = 5 * DAY_IN_MILLIS;
 
         /**
          * If false the controller will not track new jobs
@@ -646,13 +655,18 @@
         /** The percentages of a jobs' lifecycle to drop the number of required constraints. */
         public int[] PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
                 DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+        /** Initial fallback flexible deadline for rescheduled jobs. */
+        public long RESCHEDULED_JOB_DEADLINE_MS = DEFAULT_RESCHEDULED_JOB_DEADLINE_MS;
+        /** The max deadline for rescheduled jobs. */
+        public long MAX_RESCHEDULED_DEADLINE_MS = DEFAULT_MAX_RESCHEDULED_DEADLINE_MS;
 
         @GuardedBy("mLock")
         public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
                 @NonNull String key) {
             switch (key) {
                 case KEY_FLEXIBILITY_ENABLED:
-                    FLEXIBILITY_ENABLED = properties.getBoolean(key, DEFAULT_FLEXIBILITY_ENABLED);
+                    FLEXIBILITY_ENABLED = properties.getBoolean(key, DEFAULT_FLEXIBILITY_ENABLED)
+                            && mDeviceSupportsFlexConstraints;
                     if (mFlexibilityEnabled != FLEXIBILITY_ENABLED) {
                         mFlexibilityEnabled = FLEXIBILITY_ENABLED;
                         mShouldReevaluateConstraints = true;
@@ -665,6 +679,22 @@
                         }
                     }
                     break;
+                case KEY_RESCHEDULED_JOB_DEADLINE_MS:
+                    RESCHEDULED_JOB_DEADLINE_MS =
+                            properties.getLong(key, DEFAULT_RESCHEDULED_JOB_DEADLINE_MS);
+                    if (mRescheduledJobDeadline != RESCHEDULED_JOB_DEADLINE_MS) {
+                        mRescheduledJobDeadline = RESCHEDULED_JOB_DEADLINE_MS;
+                        mShouldReevaluateConstraints = true;
+                    }
+                    break;
+                case KEY_MAX_RESCHEDULED_DEADLINE_MS:
+                    MAX_RESCHEDULED_DEADLINE_MS =
+                            properties.getLong(key, DEFAULT_MAX_RESCHEDULED_DEADLINE_MS);
+                    if (mMaxRescheduledDeadline != MAX_RESCHEDULED_DEADLINE_MS) {
+                        mMaxRescheduledDeadline = MAX_RESCHEDULED_DEADLINE_MS;
+                        mShouldReevaluateConstraints = true;
+                    }
+                    break;
                 case KEY_DEADLINE_PROXIMITY_LIMIT:
                     DEADLINE_PROXIMITY_LIMIT_MS =
                             properties.getLong(key, DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS);
@@ -736,6 +766,14 @@
             pw.increaseIndent();
 
             pw.print(KEY_FLEXIBILITY_ENABLED, FLEXIBILITY_ENABLED).println();
+            pw.print(KEY_DEADLINE_PROXIMITY_LIMIT, DEADLINE_PROXIMITY_LIMIT_MS).println();
+            pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE, FALLBACK_FLEXIBILITY_DEADLINE_MS).println();
+            pw.print(KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS,
+                    MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS).println();
+            pw.print(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS,
+                    PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS).println();
+            pw.print(KEY_RESCHEDULED_JOB_DEADLINE_MS, RESCHEDULED_JOB_DEADLINE_MS).println();
+            pw.print(KEY_MAX_RESCHEDULED_DEADLINE_MS, MAX_RESCHEDULED_DEADLINE_MS).println();
 
             pw.decreaseIndent();
         }
@@ -751,9 +789,15 @@
     @GuardedBy("mLock")
     public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
         pw.println("# Constraints Satisfied: " + Integer.bitCount(mSatisfiedFlexibleConstraints));
+        pw.print("Satisfied Flexible Constraints: ");
+        JobStatus.dumpConstraints(pw, mSatisfiedFlexibleConstraints);
+        pw.println();
         pw.println();
 
         mFlexibilityTracker.dump(pw, predicate);
+        pw.println();
+        mFlexibilityAlarmQueue.dump(pw);
+        pw.println();
         mFcConfig.dump(pw);
     }
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java
index d5750f8..dd06217 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java
@@ -95,9 +95,10 @@
      */
     @Override
     public void reportNewIdleState(boolean isIdle) {
-        mFlexibilityController.setConstraintSatisfied(JobStatus.CONSTRAINT_IDLE, isIdle);
         synchronized (mLock) {
             final long nowElapsed = sElapsedRealtimeClock.millis();
+            mFlexibilityController.setConstraintSatisfied(
+                    JobStatus.CONSTRAINT_IDLE, isIdle, nowElapsed);
             for (int i = mTrackedTasks.size()-1; i >= 0; i--) {
                 mTrackedTasks.valueAt(i).setIdleConstraintSatisfied(nowElapsed, isIdle);
             }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 4ce6b321..4320db0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -572,8 +572,11 @@
                 (latestRunTimeElapsedMillis - earliestRunTimeElapsedMillis)
                 >= MIN_WINDOW_FOR_FLEXIBILITY_MS;
 
+        // The first time a job is rescheduled it will not be subject to flexible constraints.
+        // Otherwise, every consecutive reschedule increases a jobs' flexibility deadline.
         if (!isRequestedExpeditedJob()
                 && satisfiesMinWindowException
+                && numFailures != 1
                 && lacksSomeFlexibleConstraints) {
             mNumRequiredFlexibleConstraints =
                     NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0);
@@ -1928,35 +1931,38 @@
         proto.end(token);
     }
 
-    void dumpConstraints(PrintWriter pw, int constraints) {
-        if ((constraints&CONSTRAINT_CHARGING) != 0) {
+    static void dumpConstraints(PrintWriter pw, int constraints) {
+        if ((constraints & CONSTRAINT_CHARGING) != 0) {
             pw.print(" CHARGING");
         }
-        if ((constraints& CONSTRAINT_BATTERY_NOT_LOW) != 0) {
+        if ((constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0) {
             pw.print(" BATTERY_NOT_LOW");
         }
-        if ((constraints& CONSTRAINT_STORAGE_NOT_LOW) != 0) {
+        if ((constraints & CONSTRAINT_STORAGE_NOT_LOW) != 0) {
             pw.print(" STORAGE_NOT_LOW");
         }
-        if ((constraints&CONSTRAINT_TIMING_DELAY) != 0) {
+        if ((constraints & CONSTRAINT_TIMING_DELAY) != 0) {
             pw.print(" TIMING_DELAY");
         }
-        if ((constraints&CONSTRAINT_DEADLINE) != 0) {
+        if ((constraints & CONSTRAINT_DEADLINE) != 0) {
             pw.print(" DEADLINE");
         }
-        if ((constraints&CONSTRAINT_IDLE) != 0) {
+        if ((constraints & CONSTRAINT_IDLE) != 0) {
             pw.print(" IDLE");
         }
-        if ((constraints&CONSTRAINT_CONNECTIVITY) != 0) {
+        if ((constraints & CONSTRAINT_CONNECTIVITY) != 0) {
             pw.print(" CONNECTIVITY");
         }
-        if ((constraints&CONSTRAINT_CONTENT_TRIGGER) != 0) {
+        if ((constraints & CONSTRAINT_FLEXIBLE) != 0) {
+            pw.print(" FLEXIBILITY");
+        }
+        if ((constraints & CONSTRAINT_CONTENT_TRIGGER) != 0) {
             pw.print(" CONTENT_TRIGGER");
         }
-        if ((constraints&CONSTRAINT_DEVICE_NOT_DOZING) != 0) {
+        if ((constraints & CONSTRAINT_DEVICE_NOT_DOZING) != 0) {
             pw.print(" DEVICE_NOT_DOZING");
         }
-        if ((constraints&CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
+        if ((constraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
             pw.print(" BACKGROUND_NOT_RESTRICTED");
         }
         if ((constraints & CONSTRAINT_PREFETCH) != 0) {
@@ -2242,6 +2248,14 @@
                     ((requiredConstraints | CONSTRAINT_WITHIN_QUOTA | CONSTRAINT_TARE_WEALTH)
                             & ~satisfiedConstraints));
             pw.println();
+            if (hasFlexibilityConstraint()) {
+                pw.print("Num Required Flexible constraints: ");
+                pw.print(getNumRequiredFlexibleConstraints());
+                pw.println();
+                pw.print("Num Dropped Flexible constraints: ");
+                pw.print(getNumDroppedFlexibleConstraints());
+                pw.println();
+            }
 
             pw.println("Constraint history:");
             pw.increaseIndent();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
index 0945b7e..e04cec3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
@@ -105,7 +105,7 @@
     public interface PrefetchChangedListener {
         /** Callback to inform listeners when estimated launch times change. */
         void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId, String pkgName,
-                long prevEstimatedLaunchTime, long newEstimatedLaunchTime);
+                long prevEstimatedLaunchTime, long newEstimatedLaunchTime, long nowElapsed);
     }
 
     @SuppressWarnings("FieldCanBeLocal")
@@ -308,8 +308,9 @@
                     final long nowElapsed = sElapsedRealtimeClock.millis();
                     updateThresholdAlarmLocked(userId, pkgName, now, nowElapsed);
                     for (int i = 0; i < mPrefetchChangedListeners.size(); i++) {
-                        mPrefetchChangedListeners.valueAt(i).onPrefetchCacheUpdated(jobs,
-                                userId, pkgName, prevEstimatedLaunchTime, newEstimatedLaunchTime);
+                        mPrefetchChangedListeners.valueAt(i).onPrefetchCacheUpdated(
+                                jobs, userId, pkgName, prevEstimatedLaunchTime,
+                                newEstimatedLaunchTime, nowElapsed);
                     }
                     if (maybeUpdateConstraintForPkgLocked(now, nowElapsed, userId, pkgName)) {
                         mStateChangedListener.onControllerStateChanged(jobs);
diff --git a/api/OWNERS b/api/OWNERS
index 4d8ed03..bf6216c 100644
--- a/api/OWNERS
+++ b/api/OWNERS
@@ -3,7 +3,7 @@
 # Modularization team
 file:platform/packages/modules/common:/OWNERS
 
-per-file Android.bp = file:platform/build/soong:/OWNERS
+per-file Android.bp = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION}
 
 # For metalava team to disable lint checks in platform
-per-file Android.bp = aurimas@google.com,emberrose@google.com,sjgilbert@google.com
\ No newline at end of file
+per-file Android.bp = aurimas@google.com,emberrose@google.com,sjgilbert@google.com
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index dae4570..814800b 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -615,10 +615,6 @@
     mWidth = limitedSize.width;
     mHeight = limitedSize.height;
 
-    SurfaceComposerClient::Transaction t;
-    t.setSize(mFlingerSurfaceControl, mWidth, mHeight);
-    t.apply();
-
     EGLConfig config = getEglConfig(mDisplay);
     EGLSurface surface = eglCreateWindowSurface(mDisplay, config, mFlingerSurface.get(), nullptr);
     if (eglMakeCurrent(mDisplay, surface, surface, mContext) == EGL_FALSE) {
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 073d987..083bbf01 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -235,9 +235,11 @@
 
   for (const auto& res : overlay.entries) {
     if (res.dataType == Res_value::TYPE_STRING) {
-      builder.SetResourceValue(res.resourceName, res.dataType, res.stringData.value());
+      builder.SetResourceValue(res.resourceName, res.dataType, res.stringData.value(),
+            res.configuration.value_or(std::string()));
     } else {
-      builder.SetResourceValue(res.resourceName, res.dataType, res.data);
+      builder.SetResourceValue(res.resourceName, res.dataType, res.data,
+            res.configuration.value_or(std::string()));
     }
   }
 
diff --git a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
index a6824da..c773e11 100644
--- a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
@@ -24,4 +24,5 @@
     int dataType;
     int data;
     @nullable @utf8InCpp String stringData;
+    @nullable @utf8InCpp String configuration;
 }
\ No newline at end of file
diff --git a/cmds/idmap2/include/idmap2/FabricatedOverlay.h b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
index 2fc4d43..05b0618 100644
--- a/cmds/idmap2/include/idmap2/FabricatedOverlay.h
+++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
@@ -39,10 +39,11 @@
     Builder& SetOverlayable(const std::string& name);
 
     Builder& SetResourceValue(const std::string& resource_name, uint8_t data_type,
-                              uint32_t data_value);
+                              uint32_t data_value, const std::string& configuration);
 
     Builder& SetResourceValue(const std::string& resource_name, uint8_t data_type,
-                              const std::string& data_string_value);
+                              const std::string& data_string_value,
+                              const std::string& configuration);
 
     WARN_UNUSED Result<FabricatedOverlay> Build();
 
@@ -52,6 +53,7 @@
       DataType data_type;
       DataValue data_value;
       std::string data_string_value;
+      std::string configuration;
     };
 
     std::string package_name_;
diff --git a/cmds/idmap2/include/idmap2/ResourceContainer.h b/cmds/idmap2/include/idmap2/ResourceContainer.h
index c3ba464..2452ff0 100644
--- a/cmds/idmap2/include/idmap2/ResourceContainer.h
+++ b/cmds/idmap2/include/idmap2/ResourceContainer.h
@@ -66,7 +66,7 @@
 
   struct Value {
     std::string resource_name;
-    std::variant<ResourceIdValue, TargetValue> value;
+    std::variant<ResourceIdValue, TargetValueWithConfig> value;
   };
 
   struct InlineStringPoolData {
diff --git a/cmds/idmap2/include/idmap2/ResourceMapping.h b/cmds/idmap2/include/idmap2/ResourceMapping.h
index 5a0a384..21862a36 100644
--- a/cmds/idmap2/include/idmap2/ResourceMapping.h
+++ b/cmds/idmap2/include/idmap2/ResourceMapping.h
@@ -69,7 +69,8 @@
   // If `allow_rewriting_` is true, then the overlay-to-target map will be populated if the target
   // resource id is mapped to an overlay resource id.
   Result<Unit> AddMapping(ResourceId target_resource,
-                          const std::variant<OverlayData::ResourceIdValue, TargetValue>& value);
+                          const std::variant<OverlayData::ResourceIdValue,
+                          TargetValueWithConfig>& value);
 
   TargetResourceMap target_map_;
   OverlayResourceMap overlay_map_;
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index 414aa06..8ec7496 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -43,6 +43,11 @@
   std::string data_string_value;
 };
 
+struct TargetValueWithConfig {
+  TargetValue value;
+  std::string config;
+};
+
 namespace utils {
 
 // Returns whether the Res_value::data_type represents a dynamic or regular resource reference.
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index 5bbe085..bde9b0b 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -70,19 +70,22 @@
 }
 
 FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
-    const std::string& resource_name, uint8_t data_type, uint32_t data_value) {
-  entries_.emplace_back(Entry{resource_name, data_type, data_value, ""});
+    const std::string& resource_name, uint8_t data_type, uint32_t data_value,
+    const std::string& configuration) {
+  entries_.emplace_back(Entry{resource_name, data_type, data_value, "", configuration});
   return *this;
 }
 
 FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
-    const std::string& resource_name, uint8_t data_type, const std::string& data_string_value) {
-  entries_.emplace_back(Entry{resource_name, data_type, 0, data_string_value});
+    const std::string& resource_name, uint8_t data_type, const std::string& data_string_value,
+    const std::string& configuration) {
+  entries_.emplace_back(Entry{resource_name, data_type, 0, data_string_value, configuration});
   return *this;
 }
 
 Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() {
-  using EntryMap = std::map<std::string, TargetValue>;
+  using ConfigMap = std::map<std::string, TargetValue>;
+  using EntryMap = std::map<std::string, ConfigMap>;
   using TypeMap = std::map<std::string, EntryMap>;
   using PackageMap = std::map<std::string, TypeMap>;
   PackageMap package_map;
@@ -123,11 +126,16 @@
 
     auto entry = type->second.find(entry_name.to_string());
     if (entry == type->second.end()) {
-      entry = type->second.insert(std::make_pair(entry_name.to_string(), TargetValue())).first;
+      entry = type->second.insert(std::make_pair(entry_name.to_string(), ConfigMap())).first;
     }
 
-    entry->second = TargetValue{
-        res_entry.data_type, res_entry.data_value, res_entry.data_string_value};
+    auto value = entry->second.find(res_entry.configuration);
+    if (value == entry->second.end()) {
+      value = entry->second.insert(std::make_pair(res_entry.configuration, TargetValue())).first;
+    }
+
+    value->second = TargetValue{res_entry.data_type, res_entry.data_value,
+        res_entry.data_string_value};
   }
 
   pb::FabricatedOverlay overlay_pb;
@@ -145,15 +153,18 @@
       type_pb->set_name(type.first);
 
       for (auto& entry : type.second) {
-        auto entry_pb = type_pb->add_entries();
-        entry_pb->set_name(entry.first);
-        pb::ResourceValue* value = entry_pb->mutable_res_value();
-        value->set_data_type(entry.second.data_type);
-        if (entry.second.data_type == Res_value::TYPE_STRING) {
-          auto ref = string_pool.MakeRef(entry.second.data_string_value);
-          value->set_data_value(ref.index());
-        } else {
-          value->set_data_value(entry.second.data_value);
+        for (const auto& value: entry.second) {
+          auto entry_pb = type_pb->add_entries();
+          entry_pb->set_name(entry.first);
+          entry_pb->set_configuration(value.first);
+          pb::ResourceValue* pb_value = entry_pb->mutable_res_value();
+          pb_value->set_data_type(value.second.data_type);
+          if (value.second.data_type == Res_value::TYPE_STRING) {
+            auto ref = string_pool.MakeRef(value.second.data_string_value);
+            pb_value->set_data_value(ref.index());
+          } else {
+            pb_value->set_data_value(value.second.data_value);
+          }
         }
       }
     }
@@ -330,8 +341,9 @@
                                        entry.name().c_str());
         const auto& res_value = entry.res_value();
         result.pairs.emplace_back(OverlayData::Value{
-            name, TargetValue{.data_type = static_cast<uint8_t>(res_value.data_type()),
-                              .data_value = res_value.data_value()}});
+            name, TargetValueWithConfig{.config = entry.configuration(), .value = TargetValue{
+                    .data_type = static_cast<uint8_t>(res_value.data_type()),
+                    .data_value = res_value.data_value()}}});
       }
     }
   }
diff --git a/cmds/idmap2/libidmap2/ResourceContainer.cpp b/cmds/idmap2/libidmap2/ResourceContainer.cpp
index a62472c..0e35904 100644
--- a/cmds/idmap2/libidmap2/ResourceContainer.cpp
+++ b/cmds/idmap2/libidmap2/ResourceContainer.cpp
@@ -226,8 +226,10 @@
           *target_resource, OverlayData::ResourceIdValue{overlay_resource->data, rewrite_id}});
     } else {
       overlay_data.pairs.emplace_back(
-          OverlayData::Value{*target_resource, TargetValue{.data_type = overlay_resource->dataType,
-                                                           .data_value = overlay_resource->data}});
+          OverlayData::Value{*target_resource, TargetValueWithConfig{
+              .config = std::string(),
+              .value = TargetValue{.data_type = overlay_resource->dataType,
+                                   .data_value = overlay_resource->data}}});
     }
   }
 
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index 3bbbf24..8ebe5aa4 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -160,7 +160,7 @@
 
 Result<Unit> ResourceMapping::AddMapping(
     ResourceId target_resource,
-    const std::variant<OverlayData::ResourceIdValue, TargetValue>& value) {
+    const std::variant<OverlayData::ResourceIdValue, TargetValueWithConfig>& value) {
   if (target_map_.find(target_resource) != target_map_.end()) {
     return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource);
   }
@@ -176,8 +176,8 @@
       overlay_map_.insert(std::make_pair(overlay_resource->overlay_id, target_resource));
     }
   } else {
-    auto overlay_value = std::get<TargetValue>(value);
-    target_map_.insert(std::make_pair(target_resource, overlay_value));
+    auto overlay_value = std::get<TargetValueWithConfig>(value);
+    target_map_.insert(std::make_pair(target_resource, overlay_value.value));
   }
 
   return Unit{};
diff --git a/cmds/idmap2/libidmap2/proto/fabricated_v1.proto b/cmds/idmap2/libidmap2/proto/fabricated_v1.proto
index a392b2b..c7a79b3 100644
--- a/cmds/idmap2/libidmap2/proto/fabricated_v1.proto
+++ b/cmds/idmap2/libidmap2/proto/fabricated_v1.proto
@@ -46,6 +46,7 @@
   oneof value {
     ResourceValue res_value = 2;
   }
+  string configuration = 3;
 }
 
 message ResourceValue {
diff --git a/cmds/idmap2/tests/FabricatedOverlayTests.cpp b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
index 91331ca..e804c87 100644
--- a/cmds/idmap2/tests/FabricatedOverlayTests.cpp
+++ b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
@@ -43,10 +43,17 @@
 TEST(FabricatedOverlayTests, SetResourceValue) {
   auto overlay =
       FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
-          .SetResourceValue("com.example.target:integer/int1", Res_value::TYPE_INT_DEC, 1U)
-          .SetResourceValue("com.example.target.split:integer/int2", Res_value::TYPE_INT_DEC, 2U)
-          .SetResourceValue("string/int3", Res_value::TYPE_REFERENCE, 0x7f010000)
-          .SetResourceValue("com.example.target:string/string1", Res_value::TYPE_STRING, "foobar")
+          .SetResourceValue(
+              "com.example.target:integer/int1", Res_value::TYPE_INT_DEC, 1U, "port")
+          .SetResourceValue(
+              "com.example.target.split:integer/int2", Res_value::TYPE_INT_DEC, 2U, "land")
+          .SetResourceValue(
+              "string/int3", Res_value::TYPE_REFERENCE, 0x7f010000, "xxhdpi-v7")
+          .SetResourceValue(
+              "com.example.target:string/string1",
+              Res_value::TYPE_STRING,
+              "foobar",
+              "en-rUS-normal-xxhdpi-v21")
           .Build();
   ASSERT_TRUE(overlay);
   auto container = FabricatedOverlayContainer::FromOverlay(std::move(*overlay));
@@ -66,44 +73,48 @@
 
   auto& it = pairs->pairs[0];
   ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
-  auto entry = std::get_if<TargetValue>(&it.value);
+  auto entry = std::get_if<TargetValueWithConfig>(&it.value);
   ASSERT_NE(nullptr, entry);
-  ASSERT_EQ(1U, entry->data_value);
-  ASSERT_EQ(Res_value::TYPE_INT_DEC, entry->data_type);
+  ASSERT_EQ(1U, entry->value.data_value);
+  ASSERT_EQ(Res_value::TYPE_INT_DEC, entry->value.data_type);
+  ASSERT_EQ("port", entry->config);
 
   it = pairs->pairs[1];
   ASSERT_EQ("com.example.target:string/int3", it.resource_name);
-  entry = std::get_if<TargetValue>(&it.value);
+  entry = std::get_if<TargetValueWithConfig>(&it.value);
   ASSERT_NE(nullptr, entry);
-  ASSERT_EQ(0x7f010000, entry->data_value);
-  ASSERT_EQ(Res_value::TYPE_REFERENCE, entry->data_type);
+  ASSERT_EQ(0x7f010000, entry->value.data_value);
+  ASSERT_EQ(Res_value::TYPE_REFERENCE, entry->value.data_type);
+  ASSERT_EQ("xxhdpi-v7", entry->config);
 
   it = pairs->pairs[2];
   ASSERT_EQ("com.example.target:string/string1", it.resource_name);
-  entry = std::get_if<TargetValue>(&it.value);
+  entry = std::get_if<TargetValueWithConfig>(&it.value);
   ASSERT_NE(nullptr, entry);
-  ASSERT_EQ(Res_value::TYPE_STRING, entry->data_type);
-  ASSERT_EQ(std::string("foobar"), string_pool.string8At(entry->data_value).value_or(""));
+  ASSERT_EQ(Res_value::TYPE_STRING, entry->value.data_type);
+  ASSERT_EQ(std::string("foobar"), string_pool.string8At(entry->value.data_value).value_or(""));
+  ASSERT_EQ("en-rUS-normal-xxhdpi-v21", entry->config);
 
   it = pairs->pairs[3];
   ASSERT_EQ("com.example.target.split:integer/int2", it.resource_name);
-  entry = std::get_if<TargetValue>(&it.value);
+  entry = std::get_if<TargetValueWithConfig>(&it.value);
   ASSERT_NE(nullptr, entry);
-  ASSERT_EQ(2U, entry->data_value);
-  ASSERT_EQ(Res_value::TYPE_INT_DEC, entry->data_type);
+  ASSERT_EQ(2U, entry->value.data_value);
+  ASSERT_EQ(Res_value::TYPE_INT_DEC, entry->value.data_type);
+  ASSERT_EQ("land", entry->config);
 }
 
 TEST(FabricatedOverlayTests, SetResourceValueBadArgs) {
   {
     auto builder =
         FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
-            .SetResourceValue("int1", Res_value::TYPE_INT_DEC, 1U);
+            .SetResourceValue("int1", Res_value::TYPE_INT_DEC, 1U, "");
     ASSERT_FALSE(builder.Build());
   }
   {
     auto builder =
         FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
-            .SetResourceValue("com.example.target:int2", Res_value::TYPE_INT_DEC, 1U);
+            .SetResourceValue("com.example.target:int2", Res_value::TYPE_INT_DEC, 1U, "");
     ASSERT_FALSE(builder.Build());
   }
 }
@@ -112,8 +123,9 @@
   auto overlay =
       FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
           .SetOverlayable("TestResources")
-          .SetResourceValue("com.example.target:integer/int1", Res_value::TYPE_INT_DEC, 1U)
-          .SetResourceValue("com.example.target:string/string1", Res_value::TYPE_STRING, "foobar")
+          .SetResourceValue("com.example.target:integer/int1", Res_value::TYPE_INT_DEC, 1U, "")
+          .SetResourceValue(
+              "com.example.target:string/string1", Res_value::TYPE_STRING, "foobar", "")
           .Build();
   ASSERT_TRUE(overlay);
   TemporaryFile tf;
@@ -142,17 +154,17 @@
 
   auto& it = pairs->pairs[0];
   ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
-  auto entry = std::get_if<TargetValue>(&it.value);
+  auto entry = std::get_if<TargetValueWithConfig>(&it.value);
   ASSERT_NE(nullptr, entry);
-  EXPECT_EQ(1U, entry->data_value);
-  EXPECT_EQ(Res_value::TYPE_INT_DEC, entry->data_type);
+  EXPECT_EQ(1U, entry->value.data_value);
+  EXPECT_EQ(Res_value::TYPE_INT_DEC, entry->value.data_type);
 
   it = pairs->pairs[1];
   ASSERT_EQ("com.example.target:string/string1", it.resource_name);
-  entry = std::get_if<TargetValue>(&it.value);
+  entry = std::get_if<TargetValueWithConfig>(&it.value);
   ASSERT_NE(nullptr, entry);
-  ASSERT_EQ(Res_value::TYPE_STRING, entry->data_type);
-  ASSERT_EQ(std::string("foobar"), string_pool.string8At(entry->data_value).value_or(""));
+  ASSERT_EQ(Res_value::TYPE_STRING, entry->value.data_type);
+  ASSERT_EQ(std::string("foobar"), string_pool.string8At(entry->value.data_value).value_or(""));
 }
 
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index a3799f9..ee9a424 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -261,9 +261,9 @@
 
   auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
                   .SetOverlayable("TestResources")
-                  .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U)
-                  .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000)
-                  .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar")
+                  .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "")
+                  .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "")
+                  .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "")
                   .Build();
 
   ASSERT_TRUE(frro);
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index c05abcf..ca9a444 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -194,9 +194,9 @@
 TEST(ResourceMappingTests, FabricatedOverlay) {
   auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
                   .SetOverlayable("TestResources")
-                  .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U)
-                  .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000)
-                  .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar")
+                  .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "")
+                  .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "")
+                  .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "")
                   .Build();
 
   ASSERT_TRUE(frro);
diff --git a/core/api/current.txt b/core/api/current.txt
index 9ebd118..317f8eb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9126,7 +9126,6 @@
     method @Nullable public String getAttributionTag();
     method @Nullable public android.content.AttributionSource getNext();
     method @Nullable public String getPackageName();
-    method public int getPid();
     method public int getUid();
     method public boolean isTrusted(@NonNull android.content.Context);
     method @NonNull public static android.content.AttributionSource myAttributionSource();
@@ -9141,7 +9140,6 @@
     method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);
     method @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
     method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String);
-    method @NonNull public android.content.AttributionSource.Builder setPid(int);
   }
 
   public abstract class BroadcastReceiver {
@@ -32007,7 +32005,6 @@
     method @Deprecated public static final boolean supportsProcesses();
     field public static final int BLUETOOTH_UID = 1002; // 0x3ea
     field public static final int FIRST_APPLICATION_UID = 10000; // 0x2710
-    field public static final int INVALID_PID = -1; // 0xffffffff
     field public static final int INVALID_UID = -1; // 0xffffffff
     field public static final int LAST_APPLICATION_UID = 19999; // 0x4e1f
     field public static final int PHONE_UID = 1001; // 0x3e9
@@ -32267,6 +32264,7 @@
     method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.QUERY_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.content.pm.UserProperties getUserProperties(@NonNull android.os.UserHandle);
     method public android.os.Bundle getUserRestrictions();
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
+    method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}) public java.util.List<android.os.UserHandle> getVisibleUsers();
     method public boolean hasUserRestriction(String);
     method public boolean isDemoUser();
     method public static boolean isHeadlessSystemUserMode();
@@ -51982,6 +51980,7 @@
     method public boolean isTextEntryKey();
     method public boolean isTextSelectable();
     method public boolean isVisibleToUser();
+    method public void makeQueryableFromAppProcess(@NonNull android.view.View);
     method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View);
     method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View, int);
     method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain();
@@ -52988,6 +52987,22 @@
     method public android.view.inputmethod.CursorAnchorInfo.Builder setSelectionRange(int, int);
   }
 
+  public final class DeleteGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.graphics.RectF getDeletionArea();
+    method public int getGranularity();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.DeleteGesture> CREATOR;
+  }
+
+  public static final class DeleteGesture.Builder {
+    ctor public DeleteGesture.Builder();
+    method @NonNull public android.view.inputmethod.DeleteGesture build();
+    method @NonNull public android.view.inputmethod.DeleteGesture.Builder setDeletionArea(@NonNull android.graphics.RectF);
+    method @NonNull public android.view.inputmethod.DeleteGesture.Builder setFallbackText(@Nullable String);
+    method @NonNull public android.view.inputmethod.DeleteGesture.Builder setGranularity(int);
+  }
+
   public final class EditorBoundsInfo implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public android.graphics.RectF getEditorBounds();
@@ -53082,6 +53097,12 @@
     field public int token;
   }
 
+  public abstract class HandwritingGesture {
+    method @Nullable public String getFallbackText();
+    field public static final int GRANULARITY_CHARACTER = 2; // 0x2
+    field public static final int GRANULARITY_WORD = 1; // 0x1
+  }
+
   public final class InlineSuggestion implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.view.inputmethod.InlineSuggestionInfo getInfo();
@@ -53172,6 +53193,7 @@
     method @Nullable public CharSequence getTextBeforeCursor(@IntRange(from=0) int, int);
     method public boolean performContextMenuAction(int);
     method public boolean performEditorAction(int);
+    method public default void performHandwritingGesture(@NonNull android.view.inputmethod.HandwritingGesture, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.IntConsumer);
     method public boolean performPrivateCommand(String, android.os.Bundle);
     method public default boolean performSpellCheck();
     method public boolean reportFullscreenMode(boolean);
@@ -53393,6 +53415,38 @@
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeNameResId(int);
   }
 
+  public final class InsertGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.graphics.PointF getInsertionPoint();
+    method @Nullable public String getTextToInsert();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.InsertGesture> CREATOR;
+  }
+
+  public static final class InsertGesture.Builder {
+    ctor public InsertGesture.Builder();
+    method @NonNull public android.view.inputmethod.InsertGesture build();
+    method @NonNull public android.view.inputmethod.InsertGesture.Builder setFallbackText(@Nullable String);
+    method @NonNull public android.view.inputmethod.InsertGesture.Builder setInsertionPoint(@NonNull android.graphics.PointF);
+    method @NonNull public android.view.inputmethod.InsertGesture.Builder setTextToInsert(@NonNull String);
+  }
+
+  public final class SelectGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getGranularity();
+    method @NonNull public android.graphics.RectF getSelectionArea();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.SelectGesture> CREATOR;
+  }
+
+  public static final class SelectGesture.Builder {
+    ctor public SelectGesture.Builder();
+    method @NonNull public android.view.inputmethod.SelectGesture build();
+    method @NonNull public android.view.inputmethod.SelectGesture.Builder setFallbackText(@Nullable String);
+    method @NonNull public android.view.inputmethod.SelectGesture.Builder setGranularity(int);
+    method @NonNull public android.view.inputmethod.SelectGesture.Builder setSelectionArea(@NonNull android.graphics.RectF);
+  }
+
   public final class SurroundingText implements android.os.Parcelable {
     ctor public SurroundingText(@NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0xffffffff) int);
     method public int describeContents();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 610cb94..e890005 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -172,6 +172,7 @@
     method @NonNull public static android.media.BluetoothProfileConnectionInfo createA2dpSinkInfo(int);
     method @NonNull public static android.media.BluetoothProfileConnectionInfo createHearingAidInfo(boolean);
     method @NonNull public static android.media.BluetoothProfileConnectionInfo createLeAudioInfo(boolean, boolean);
+    method @NonNull public static android.media.BluetoothProfileConnectionInfo createLeAudioOutputInfo(boolean, int);
     method public int describeContents();
     method public int getProfile();
     method public int getVolume();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index be84032..2b2f202 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -320,6 +320,7 @@
     field public static final String START_ACTIVITIES_FROM_BACKGROUND = "android.permission.START_ACTIVITIES_FROM_BACKGROUND";
     field public static final String START_CROSS_PROFILE_ACTIVITIES = "android.permission.START_CROSS_PROFILE_ACTIVITIES";
     field public static final String START_REVIEW_PERMISSION_DECISIONS = "android.permission.START_REVIEW_PERMISSION_DECISIONS";
+    field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS";
     field public static final String STATUS_BAR_SERVICE = "android.permission.STATUS_BAR_SERVICE";
     field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
     field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME";
@@ -507,7 +508,7 @@
 
   public class ActivityOptions {
     method public int getLaunchTaskId();
-    method @RequiresPermission("android.permission.START_TASKS_FROM_RECENTS") public void setLaunchTaskId(int);
+    method @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) public void setLaunchTaskId(int);
   }
 
   public class AlarmManager {
@@ -11479,6 +11480,7 @@
     method public void onPanelRevealed(int);
     method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
     method public final void unsnoozeNotification(@NonNull String);
+    field public static final String ACTION_NOTIFICATION_ASSISTANT_DETAIL_SETTINGS = "android.service.notification.action.NOTIFICATION_ASSISTANT_DETAIL_SETTINGS";
     field public static final String FEEDBACK_RATING = "feedback.rating";
     field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
     field public static final int SOURCE_FROM_APP = 0; // 0x0
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index fefdfd8..f45298a 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -42,7 +42,6 @@
     field public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS = "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS";
     field public static final String SET_GAME_SERVICE = "android.permission.SET_GAME_SERVICE";
     field public static final String SET_KEYBOARD_LAYOUT = "android.permission.SET_KEYBOARD_LAYOUT";
-    field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS";
     field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
     field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
     field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS";
@@ -3204,6 +3203,11 @@
     method public android.widget.ListView getMenuListView();
   }
 
+  public class RatingBar extends android.widget.AbsSeekBar {
+    field public static final String PLURALS_MAX = "max";
+    field public static final String PLURALS_RATING = "rating";
+  }
+
   public class Spinner extends android.widget.AbsSpinner implements android.content.DialogInterface.OnClickListener {
     method public boolean isPopupShowing();
   }
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 25ef6e8..c2b315f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -980,7 +980,8 @@
     boolean mEnterAnimationComplete;
 
     private boolean mIsInMultiWindowMode;
-    private boolean mIsInPictureInPictureMode;
+    /** @hide */
+    boolean mIsInPictureInPictureMode;
 
     private boolean mShouldDockBigOverlays;
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b383d7d..db76816 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4177,7 +4177,8 @@
     private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) {
         final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token);
         transaction.setLifecycleStateRequest(PauseActivityItem.obtain(r.activity.isFinishing(),
-                /* userLeaving */ true, r.activity.mConfigChangeFlags, /* dontReport */ false));
+                /* userLeaving */ true, r.activity.mConfigChangeFlags, /* dontReport */ false,
+                /* autoEnteringPip */ false));
         executeTransaction(transaction);
     }
 
@@ -4965,12 +4966,18 @@
 
     @Override
     public void handlePauseActivity(ActivityClientRecord r, boolean finished, boolean userLeaving,
-            int configChanges, PendingTransactionActions pendingActions, String reason) {
+            int configChanges, boolean autoEnteringPip, PendingTransactionActions pendingActions,
+            String reason) {
         if (userLeaving) {
             performUserLeavingActivity(r);
         }
 
         r.activity.mConfigChangeFlags |= configChanges;
+        if (autoEnteringPip) {
+            // Set mIsInPictureInPictureMode earlier in case of auto-enter-pip, see also
+            // {@link Activity#enterPictureInPictureMode(PictureInPictureParams)}.
+            r.activity.mIsInPictureInPictureMode = true;
+        }
         performPauseActivity(r, finished, reason, pendingActions);
 
         // Make sure any pending writes are now committed.
diff --git a/core/java/android/app/AppOpInfo.java b/core/java/android/app/AppOpInfo.java
new file mode 100644
index 0000000..979c910
--- /dev/null
+++ b/core/java/android/app/AppOpInfo.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static android.app.AppOpsManager.OP_NONE;
+
+import android.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * Information about a particular app op.
+ */
+class AppOpInfo {
+    /**
+     * A unique constant identifying this app op.
+     */
+    public final int code;
+
+    /**
+     * This maps each operation to the operation that serves as the
+     * switch to determine whether it is allowed.  Generally this is
+     * a 1:1 mapping, but for some things (like location) that have
+     * multiple low-level operations being tracked that should be
+     * presented to the user as one switch then this can be used to
+     * make them all controlled by the same single operation.
+     */
+    public final int switchCode;
+
+    /**
+     * This maps each operation to the public string constant for it.
+     */
+    public final String name;
+
+    /**
+     * This provides a simple name for each operation to be used
+     * in debug output.
+     */
+    public final String simpleName;
+
+    /**
+     * This optionally maps a permission to an operation.  If there
+     * is no permission associated with an operation, it is null.
+     */
+    public final String permission;
+
+    /**
+     * Specifies whether an Op should be restricted by a user restriction.
+     * Each Op should be filled with a restriction string from UserManager or
+     * null to specify it is not affected by any user restriction.
+     */
+    public final String restriction;
+
+    /**
+     * In which cases should an app be allowed to bypass the
+     * {@link AppOpsManager#setUserRestriction user restriction} for a certain app-op.
+     */
+    public final AppOpsManager.RestrictionBypass allowSystemRestrictionBypass;
+
+    /**
+     * This specifies the default mode for each operation.
+     */
+    public final int defaultMode;
+
+    /**
+     * This specifies whether each option is allowed to be reset
+     * when resetting all app preferences.  Disable reset for
+     * app ops that are under strong control of some part of the
+     * system (such as OP_WRITE_SMS, which should be allowed only
+     * for whichever app is selected as the current SMS app).
+     */
+    public final boolean disableReset;
+
+    /**
+     * This specifies whether each option is only allowed to be read
+     * by apps with manage appops permission.
+     */
+    public final boolean restrictRead;
+
+    AppOpInfo(int code,
+            int switchCode,
+            @NonNull String name,
+            @NonNull String simpleName,
+            String permission,
+            String restriction,
+            AppOpsManager.RestrictionBypass allowSystemRestrictionBypass,
+            int defaultMode,
+            boolean disableReset,
+            boolean restrictRead) {
+        if (code < OP_NONE) throw new IllegalArgumentException();
+        if (switchCode < OP_NONE) throw new IllegalArgumentException();
+        Objects.requireNonNull(name);
+        Objects.requireNonNull(simpleName);
+        this.code = code;
+        this.switchCode = switchCode;
+        this.name = name;
+        this.simpleName = simpleName;
+        this.permission = permission;
+        this.restriction = restriction;
+        this.allowSystemRestrictionBypass = allowSystemRestrictionBypass;
+        this.defaultMode = defaultMode;
+        this.disableReset = disableReset;
+        this.restrictRead = restrictRead;
+    }
+
+    static class Builder {
+        private int mCode;
+        private int mSwitchCode;
+        private String mName;
+        private String mSimpleName;
+        private String mPermission = null;
+        private String mRestriction = null;
+        private AppOpsManager.RestrictionBypass mAllowSystemRestrictionBypass = null;
+        private int mDefaultMode = AppOpsManager.MODE_DEFAULT;
+        private boolean mDisableReset = false;
+        private boolean mRestrictRead = false;
+
+        Builder(int code, @NonNull String name, @NonNull String simpleName) {
+            if (code < OP_NONE) throw new IllegalArgumentException();
+            Objects.requireNonNull(name);
+            Objects.requireNonNull(simpleName);
+            this.mCode = code;
+            this.mSwitchCode = code;
+            this.mName = name;
+            this.mSimpleName = simpleName;
+        }
+
+        public Builder setCode(int value) {
+            this.mCode = value;
+            return this;
+        }
+
+        public Builder setSwitchCode(int value) {
+            this.mSwitchCode = value;
+            return this;
+        }
+
+        public Builder setName(String value) {
+            this.mName = value;
+            return this;
+        }
+
+        public Builder setSimpleName(String value) {
+            this.mSimpleName = value;
+            return this;
+        }
+
+        public Builder setPermission(String value) {
+            this.mPermission = value;
+            return this;
+        }
+
+        public Builder setRestriction(String value) {
+            this.mRestriction = value;
+            return this;
+        }
+
+        public Builder setAllowSystemRestrictionBypass(
+                AppOpsManager.RestrictionBypass value) {
+            this.mAllowSystemRestrictionBypass = value;
+            return this;
+        }
+
+        public Builder setDefaultMode(int value) {
+            this.mDefaultMode = value;
+            return this;
+        }
+
+        public Builder setDisableReset(boolean value) {
+            this.mDisableReset = value;
+            return this;
+        }
+
+        public Builder setRestrictRead(boolean value) {
+            this.mRestrictRead = value;
+            return this;
+        }
+
+        public AppOpInfo build() {
+            return new AppOpInfo(mCode, mSwitchCode, mName, mSimpleName, mPermission, mRestriction,
+                mAllowSystemRestrictionBypass, mDefaultMode, mDisableReset, mRestrictRead);
+        }
+    }
+}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 2f282f7..2a5916d 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -867,8 +867,7 @@
     // when adding one of these:
     //  - increment _NUM_OP
     //  - define an OPSTR_* constant (marked as @SystemApi)
-    //  - add rows to sOpToSwitch, sOpToString, sOpNames, sOpPerms, sOpDefaultMode, sOpDisableReset,
-    //      sOpRestrictions, sOpAllowSystemRestrictionBypass
+    //  - add row to sAppOpInfos
     //  - add descriptive strings to Settings/res/values/arrays.xml
     //  - add the op to the appropriate template in AppOpsState.OpsTemplate (settings app)
 
@@ -1909,1163 +1908,379 @@
             OP_TURN_SCREEN_ON,
     };
 
-    /**
-     * This maps each operation to the operation that serves as the
-     * switch to determine whether it is allowed.  Generally this is
-     * a 1:1 mapping, but for some things (like location) that have
-     * multiple low-level operations being tracked that should be
-     * presented to the user as one switch then this can be used to
-     * make them all controlled by the same single operation.
-     */
-    private static int[] sOpToSwitch = new int[] {
-            OP_COARSE_LOCATION,                 // COARSE_LOCATION
-            OP_FINE_LOCATION,                   // FINE_LOCATION
-            OP_COARSE_LOCATION,                 // GPS
-            OP_VIBRATE,                         // VIBRATE
-            OP_READ_CONTACTS,                   // READ_CONTACTS
-            OP_WRITE_CONTACTS,                  // WRITE_CONTACTS
-            OP_READ_CALL_LOG,                   // READ_CALL_LOG
-            OP_WRITE_CALL_LOG,                  // WRITE_CALL_LOG
-            OP_READ_CALENDAR,                   // READ_CALENDAR
-            OP_WRITE_CALENDAR,                  // WRITE_CALENDAR
-            OP_COARSE_LOCATION,                 // WIFI_SCAN
-            OP_POST_NOTIFICATION,               // POST_NOTIFICATION
-            OP_COARSE_LOCATION,                 // NEIGHBORING_CELLS
-            OP_CALL_PHONE,                      // CALL_PHONE
-            OP_READ_SMS,                        // READ_SMS
-            OP_WRITE_SMS,                       // WRITE_SMS
-            OP_RECEIVE_SMS,                     // RECEIVE_SMS
-            OP_RECEIVE_SMS,                     // RECEIVE_EMERGECY_SMS
-            OP_RECEIVE_MMS,                     // RECEIVE_MMS
-            OP_RECEIVE_WAP_PUSH,                // RECEIVE_WAP_PUSH
-            OP_SEND_SMS,                        // SEND_SMS
-            OP_READ_SMS,                        // READ_ICC_SMS
-            OP_WRITE_SMS,                       // WRITE_ICC_SMS
-            OP_WRITE_SETTINGS,                  // WRITE_SETTINGS
-            OP_SYSTEM_ALERT_WINDOW,             // SYSTEM_ALERT_WINDOW
-            OP_ACCESS_NOTIFICATIONS,            // ACCESS_NOTIFICATIONS
-            OP_CAMERA,                          // CAMERA
-            OP_RECORD_AUDIO,                    // RECORD_AUDIO
-            OP_PLAY_AUDIO,                      // PLAY_AUDIO
-            OP_READ_CLIPBOARD,                  // READ_CLIPBOARD
-            OP_WRITE_CLIPBOARD,                 // WRITE_CLIPBOARD
-            OP_TAKE_MEDIA_BUTTONS,              // TAKE_MEDIA_BUTTONS
-            OP_TAKE_AUDIO_FOCUS,                // TAKE_AUDIO_FOCUS
-            OP_AUDIO_MASTER_VOLUME,             // AUDIO_MASTER_VOLUME
-            OP_AUDIO_VOICE_VOLUME,              // AUDIO_VOICE_VOLUME
-            OP_AUDIO_RING_VOLUME,               // AUDIO_RING_VOLUME
-            OP_AUDIO_MEDIA_VOLUME,              // AUDIO_MEDIA_VOLUME
-            OP_AUDIO_ALARM_VOLUME,              // AUDIO_ALARM_VOLUME
-            OP_AUDIO_NOTIFICATION_VOLUME,       // AUDIO_NOTIFICATION_VOLUME
-            OP_AUDIO_BLUETOOTH_VOLUME,          // AUDIO_BLUETOOTH_VOLUME
-            OP_WAKE_LOCK,                       // WAKE_LOCK
-            OP_COARSE_LOCATION,                 // MONITOR_LOCATION
-            OP_COARSE_LOCATION,                 // MONITOR_HIGH_POWER_LOCATION
-            OP_GET_USAGE_STATS,                 // GET_USAGE_STATS
-            OP_MUTE_MICROPHONE,                 // MUTE_MICROPHONE
-            OP_TOAST_WINDOW,                    // TOAST_WINDOW
-            OP_PROJECT_MEDIA,                   // PROJECT_MEDIA
-            OP_ACTIVATE_VPN,                    // ACTIVATE_VPN
-            OP_WRITE_WALLPAPER,                 // WRITE_WALLPAPER
-            OP_ASSIST_STRUCTURE,                // ASSIST_STRUCTURE
-            OP_ASSIST_SCREENSHOT,               // ASSIST_SCREENSHOT
-            OP_READ_PHONE_STATE,                // READ_PHONE_STATE
-            OP_ADD_VOICEMAIL,                   // ADD_VOICEMAIL
-            OP_USE_SIP,                         // USE_SIP
-            OP_PROCESS_OUTGOING_CALLS,          // PROCESS_OUTGOING_CALLS
-            OP_USE_FINGERPRINT,                 // USE_FINGERPRINT
-            OP_BODY_SENSORS,                    // BODY_SENSORS
-            OP_READ_CELL_BROADCASTS,            // READ_CELL_BROADCASTS
-            OP_MOCK_LOCATION,                   // MOCK_LOCATION
-            OP_READ_EXTERNAL_STORAGE,           // READ_EXTERNAL_STORAGE
-            OP_WRITE_EXTERNAL_STORAGE,          // WRITE_EXTERNAL_STORAGE
-            OP_TURN_SCREEN_ON,                  // TURN_SCREEN_ON
-            OP_GET_ACCOUNTS,                    // GET_ACCOUNTS
-            OP_RUN_IN_BACKGROUND,               // RUN_IN_BACKGROUND
-            OP_AUDIO_ACCESSIBILITY_VOLUME,      // AUDIO_ACCESSIBILITY_VOLUME
-            OP_READ_PHONE_NUMBERS,              // READ_PHONE_NUMBERS
-            OP_REQUEST_INSTALL_PACKAGES,        // REQUEST_INSTALL_PACKAGES
-            OP_PICTURE_IN_PICTURE,              // ENTER_PICTURE_IN_PICTURE_ON_HIDE
-            OP_INSTANT_APP_START_FOREGROUND,    // INSTANT_APP_START_FOREGROUND
-            OP_ANSWER_PHONE_CALLS,              // ANSWER_PHONE_CALLS
-            OP_RUN_ANY_IN_BACKGROUND,           // OP_RUN_ANY_IN_BACKGROUND
-            OP_CHANGE_WIFI_STATE,               // OP_CHANGE_WIFI_STATE
-            OP_REQUEST_DELETE_PACKAGES,         // OP_REQUEST_DELETE_PACKAGES
-            OP_BIND_ACCESSIBILITY_SERVICE,      // OP_BIND_ACCESSIBILITY_SERVICE
-            OP_ACCEPT_HANDOVER,                 // ACCEPT_HANDOVER
-            OP_MANAGE_IPSEC_TUNNELS,            // MANAGE_IPSEC_HANDOVERS
-            OP_START_FOREGROUND,                // START_FOREGROUND
-            OP_BLUETOOTH_SCAN,                  // BLUETOOTH_SCAN
-            OP_USE_BIOMETRIC,                   // BIOMETRIC
-            OP_ACTIVITY_RECOGNITION,            // ACTIVITY_RECOGNITION
-            OP_SMS_FINANCIAL_TRANSACTIONS,      // SMS_FINANCIAL_TRANSACTIONS
-            OP_READ_MEDIA_AUDIO,                // READ_MEDIA_AUDIO
-            OP_WRITE_MEDIA_AUDIO,               // WRITE_MEDIA_AUDIO
-            OP_READ_MEDIA_VIDEO,                // READ_MEDIA_VIDEO
-            OP_WRITE_MEDIA_VIDEO,               // WRITE_MEDIA_VIDEO
-            OP_READ_MEDIA_IMAGES,               // READ_MEDIA_IMAGES
-            OP_WRITE_MEDIA_IMAGES,              // WRITE_MEDIA_IMAGES
-            OP_LEGACY_STORAGE,                  // LEGACY_STORAGE
-            OP_ACCESS_ACCESSIBILITY,            // ACCESS_ACCESSIBILITY
-            OP_READ_DEVICE_IDENTIFIERS,         // READ_DEVICE_IDENTIFIERS
-            OP_ACCESS_MEDIA_LOCATION,           // ACCESS_MEDIA_LOCATION
-            OP_QUERY_ALL_PACKAGES,              // QUERY_ALL_PACKAGES
-            OP_MANAGE_EXTERNAL_STORAGE,         // MANAGE_EXTERNAL_STORAGE
-            OP_INTERACT_ACROSS_PROFILES,        //INTERACT_ACROSS_PROFILES
-            OP_ACTIVATE_PLATFORM_VPN,           // ACTIVATE_PLATFORM_VPN
-            OP_LOADER_USAGE_STATS,              // LOADER_USAGE_STATS
-            OP_DEPRECATED_1,                    // deprecated
-            OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, //AUTO_REVOKE_PERMISSIONS_IF_UNUSED
-            OP_AUTO_REVOKE_MANAGED_BY_INSTALLER, //OP_AUTO_REVOKE_MANAGED_BY_INSTALLER
-            OP_NO_ISOLATED_STORAGE,             // NO_ISOLATED_STORAGE
-            OP_PHONE_CALL_MICROPHONE,           // OP_PHONE_CALL_MICROPHONE
-            OP_PHONE_CALL_CAMERA,               // OP_PHONE_CALL_CAMERA
-            OP_RECORD_AUDIO_HOTWORD,            // RECORD_AUDIO_HOTWORD
-            OP_MANAGE_ONGOING_CALLS,            // MANAGE_ONGOING_CALLS
-            OP_MANAGE_CREDENTIALS,              // MANAGE_CREDENTIALS
-            OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
-            OP_RECORD_AUDIO_OUTPUT,             // RECORD_AUDIO_OUTPUT
-            OP_SCHEDULE_EXACT_ALARM,            // SCHEDULE_EXACT_ALARM
-            OP_FINE_LOCATION,                   // OP_FINE_LOCATION_SOURCE
-            OP_COARSE_LOCATION,                 // OP_COARSE_LOCATION_SOURCE
-            OP_MANAGE_MEDIA,                    // MANAGE_MEDIA
-            OP_BLUETOOTH_CONNECT,               // OP_BLUETOOTH_CONNECT
-            OP_UWB_RANGING,                     // OP_UWB_RANGING
-            OP_ACTIVITY_RECOGNITION,            // OP_ACTIVITY_RECOGNITION_SOURCE
-            OP_BLUETOOTH_ADVERTISE,             // OP_BLUETOOTH_ADVERTISE
-            OP_RECORD_INCOMING_PHONE_AUDIO,     // OP_RECORD_INCOMING_PHONE_AUDIO
-            OP_NEARBY_WIFI_DEVICES,             // OP_NEARBY_WIFI_DEVICES
-            OP_ESTABLISH_VPN_SERVICE,           // OP_ESTABLISH_VPN_SERVICE
-            OP_ESTABLISH_VPN_MANAGER,           // OP_ESTABLISH_VPN_MANAGER
-            OP_ACCESS_RESTRICTED_SETTINGS,      // OP_ACCESS_RESTRICTED_SETTINGS
-            OP_RECEIVE_AMBIENT_TRIGGER_AUDIO,      // RECEIVE_SOUNDTRIGGER_AUDIO
-    };
-
-    /**
-     * This maps each operation to the public string constant for it.
-     */
-    private static String[] sOpToString = new String[]{
-            OPSTR_COARSE_LOCATION,
-            OPSTR_FINE_LOCATION,
-            OPSTR_GPS,
-            OPSTR_VIBRATE,
-            OPSTR_READ_CONTACTS,
-            OPSTR_WRITE_CONTACTS,
-            OPSTR_READ_CALL_LOG,
-            OPSTR_WRITE_CALL_LOG,
-            OPSTR_READ_CALENDAR,
-            OPSTR_WRITE_CALENDAR,
-            OPSTR_WIFI_SCAN,
-            OPSTR_POST_NOTIFICATION,
-            OPSTR_NEIGHBORING_CELLS,
-            OPSTR_CALL_PHONE,
-            OPSTR_READ_SMS,
-            OPSTR_WRITE_SMS,
-            OPSTR_RECEIVE_SMS,
-            OPSTR_RECEIVE_EMERGENCY_BROADCAST,
-            OPSTR_RECEIVE_MMS,
-            OPSTR_RECEIVE_WAP_PUSH,
-            OPSTR_SEND_SMS,
-            OPSTR_READ_ICC_SMS,
-            OPSTR_WRITE_ICC_SMS,
-            OPSTR_WRITE_SETTINGS,
-            OPSTR_SYSTEM_ALERT_WINDOW,
-            OPSTR_ACCESS_NOTIFICATIONS,
-            OPSTR_CAMERA,
-            OPSTR_RECORD_AUDIO,
-            OPSTR_PLAY_AUDIO,
-            OPSTR_READ_CLIPBOARD,
-            OPSTR_WRITE_CLIPBOARD,
-            OPSTR_TAKE_MEDIA_BUTTONS,
-            OPSTR_TAKE_AUDIO_FOCUS,
-            OPSTR_AUDIO_MASTER_VOLUME,
-            OPSTR_AUDIO_VOICE_VOLUME,
-            OPSTR_AUDIO_RING_VOLUME,
-            OPSTR_AUDIO_MEDIA_VOLUME,
-            OPSTR_AUDIO_ALARM_VOLUME,
-            OPSTR_AUDIO_NOTIFICATION_VOLUME,
-            OPSTR_AUDIO_BLUETOOTH_VOLUME,
-            OPSTR_WAKE_LOCK,
-            OPSTR_MONITOR_LOCATION,
-            OPSTR_MONITOR_HIGH_POWER_LOCATION,
-            OPSTR_GET_USAGE_STATS,
-            OPSTR_MUTE_MICROPHONE,
-            OPSTR_TOAST_WINDOW,
-            OPSTR_PROJECT_MEDIA,
-            OPSTR_ACTIVATE_VPN,
-            OPSTR_WRITE_WALLPAPER,
-            OPSTR_ASSIST_STRUCTURE,
-            OPSTR_ASSIST_SCREENSHOT,
-            OPSTR_READ_PHONE_STATE,
-            OPSTR_ADD_VOICEMAIL,
-            OPSTR_USE_SIP,
-            OPSTR_PROCESS_OUTGOING_CALLS,
-            OPSTR_USE_FINGERPRINT,
-            OPSTR_BODY_SENSORS,
-            OPSTR_READ_CELL_BROADCASTS,
-            OPSTR_MOCK_LOCATION,
-            OPSTR_READ_EXTERNAL_STORAGE,
-            OPSTR_WRITE_EXTERNAL_STORAGE,
-            OPSTR_TURN_SCREEN_ON,
-            OPSTR_GET_ACCOUNTS,
-            OPSTR_RUN_IN_BACKGROUND,
-            OPSTR_AUDIO_ACCESSIBILITY_VOLUME,
-            OPSTR_READ_PHONE_NUMBERS,
-            OPSTR_REQUEST_INSTALL_PACKAGES,
-            OPSTR_PICTURE_IN_PICTURE,
-            OPSTR_INSTANT_APP_START_FOREGROUND,
-            OPSTR_ANSWER_PHONE_CALLS,
-            OPSTR_RUN_ANY_IN_BACKGROUND,
-            OPSTR_CHANGE_WIFI_STATE,
-            OPSTR_REQUEST_DELETE_PACKAGES,
-            OPSTR_BIND_ACCESSIBILITY_SERVICE,
-            OPSTR_ACCEPT_HANDOVER,
-            OPSTR_MANAGE_IPSEC_TUNNELS,
-            OPSTR_START_FOREGROUND,
-            OPSTR_BLUETOOTH_SCAN,
-            OPSTR_USE_BIOMETRIC,
-            OPSTR_ACTIVITY_RECOGNITION,
-            OPSTR_SMS_FINANCIAL_TRANSACTIONS,
-            OPSTR_READ_MEDIA_AUDIO,
-            OPSTR_WRITE_MEDIA_AUDIO,
-            OPSTR_READ_MEDIA_VIDEO,
-            OPSTR_WRITE_MEDIA_VIDEO,
-            OPSTR_READ_MEDIA_IMAGES,
-            OPSTR_WRITE_MEDIA_IMAGES,
-            OPSTR_LEGACY_STORAGE,
-            OPSTR_ACCESS_ACCESSIBILITY,
-            OPSTR_READ_DEVICE_IDENTIFIERS,
-            OPSTR_ACCESS_MEDIA_LOCATION,
-            OPSTR_QUERY_ALL_PACKAGES,
-            OPSTR_MANAGE_EXTERNAL_STORAGE,
-            OPSTR_INTERACT_ACROSS_PROFILES,
-            OPSTR_ACTIVATE_PLATFORM_VPN,
-            OPSTR_LOADER_USAGE_STATS,
-            "", // deprecated
-            OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
-            OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER,
-            OPSTR_NO_ISOLATED_STORAGE,
-            OPSTR_PHONE_CALL_MICROPHONE,
-            OPSTR_PHONE_CALL_CAMERA,
-            OPSTR_RECORD_AUDIO_HOTWORD,
-            OPSTR_MANAGE_ONGOING_CALLS,
-            OPSTR_MANAGE_CREDENTIALS,
-            OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
-            OPSTR_RECORD_AUDIO_OUTPUT,
-            OPSTR_SCHEDULE_EXACT_ALARM,
-            OPSTR_FINE_LOCATION_SOURCE,
-            OPSTR_COARSE_LOCATION_SOURCE,
-            OPSTR_MANAGE_MEDIA,
-            OPSTR_BLUETOOTH_CONNECT,
-            OPSTR_UWB_RANGING,
-            OPSTR_ACTIVITY_RECOGNITION_SOURCE,
-            OPSTR_BLUETOOTH_ADVERTISE,
-            OPSTR_RECORD_INCOMING_PHONE_AUDIO,
-            OPSTR_NEARBY_WIFI_DEVICES,
-            OPSTR_ESTABLISH_VPN_SERVICE,
-            OPSTR_ESTABLISH_VPN_MANAGER,
-            OPSTR_ACCESS_RESTRICTED_SETTINGS,
-            OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO,
-    };
-
-    /**
-     * This provides a simple name for each operation to be used
-     * in debug output.
-     */
-    private static String[] sOpNames = new String[] {
-            "COARSE_LOCATION",
-            "FINE_LOCATION",
-            "GPS",
-            "VIBRATE",
-            "READ_CONTACTS",
-            "WRITE_CONTACTS",
-            "READ_CALL_LOG",
-            "WRITE_CALL_LOG",
-            "READ_CALENDAR",
-            "WRITE_CALENDAR",
-            "WIFI_SCAN",
-            "POST_NOTIFICATION",
-            "NEIGHBORING_CELLS",
-            "CALL_PHONE",
-            "READ_SMS",
-            "WRITE_SMS",
-            "RECEIVE_SMS",
-            "RECEIVE_EMERGECY_SMS",
-            "RECEIVE_MMS",
-            "RECEIVE_WAP_PUSH",
-            "SEND_SMS",
-            "READ_ICC_SMS",
-            "WRITE_ICC_SMS",
-            "WRITE_SETTINGS",
-            "SYSTEM_ALERT_WINDOW",
-            "ACCESS_NOTIFICATIONS",
-            "CAMERA",
-            "RECORD_AUDIO",
-            "PLAY_AUDIO",
-            "READ_CLIPBOARD",
-            "WRITE_CLIPBOARD",
-            "TAKE_MEDIA_BUTTONS",
-            "TAKE_AUDIO_FOCUS",
-            "AUDIO_MASTER_VOLUME",
-            "AUDIO_VOICE_VOLUME",
-            "AUDIO_RING_VOLUME",
-            "AUDIO_MEDIA_VOLUME",
-            "AUDIO_ALARM_VOLUME",
-            "AUDIO_NOTIFICATION_VOLUME",
-            "AUDIO_BLUETOOTH_VOLUME",
-            "WAKE_LOCK",
-            "MONITOR_LOCATION",
-            "MONITOR_HIGH_POWER_LOCATION",
-            "GET_USAGE_STATS",
-            "MUTE_MICROPHONE",
-            "TOAST_WINDOW",
-            "PROJECT_MEDIA",
-            "ACTIVATE_VPN",
-            "WRITE_WALLPAPER",
-            "ASSIST_STRUCTURE",
-            "ASSIST_SCREENSHOT",
-            "READ_PHONE_STATE",
-            "ADD_VOICEMAIL",
-            "USE_SIP",
-            "PROCESS_OUTGOING_CALLS",
-            "USE_FINGERPRINT",
-            "BODY_SENSORS",
-            "READ_CELL_BROADCASTS",
-            "MOCK_LOCATION",
-            "READ_EXTERNAL_STORAGE",
-            "WRITE_EXTERNAL_STORAGE",
-            "TURN_ON_SCREEN",
-            "GET_ACCOUNTS",
-            "RUN_IN_BACKGROUND",
-            "AUDIO_ACCESSIBILITY_VOLUME",
-            "READ_PHONE_NUMBERS",
-            "REQUEST_INSTALL_PACKAGES",
-            "PICTURE_IN_PICTURE",
-            "INSTANT_APP_START_FOREGROUND",
-            "ANSWER_PHONE_CALLS",
-            "RUN_ANY_IN_BACKGROUND",
-            "CHANGE_WIFI_STATE",
-            "REQUEST_DELETE_PACKAGES",
-            "BIND_ACCESSIBILITY_SERVICE",
-            "ACCEPT_HANDOVER",
-            "MANAGE_IPSEC_TUNNELS",
-            "START_FOREGROUND",
-            "BLUETOOTH_SCAN",
-            "USE_BIOMETRIC",
-            "ACTIVITY_RECOGNITION",
-            "SMS_FINANCIAL_TRANSACTIONS",
-            "READ_MEDIA_AUDIO",
-            "WRITE_MEDIA_AUDIO",
-            "READ_MEDIA_VIDEO",
-            "WRITE_MEDIA_VIDEO",
-            "READ_MEDIA_IMAGES",
-            "WRITE_MEDIA_IMAGES",
-            "LEGACY_STORAGE",
-            "ACCESS_ACCESSIBILITY",
-            "READ_DEVICE_IDENTIFIERS",
-            "ACCESS_MEDIA_LOCATION",
-            "QUERY_ALL_PACKAGES",
-            "MANAGE_EXTERNAL_STORAGE",
-            "INTERACT_ACROSS_PROFILES",
-            "ACTIVATE_PLATFORM_VPN",
-            "LOADER_USAGE_STATS",
-            "deprecated",
-            "AUTO_REVOKE_PERMISSIONS_IF_UNUSED",
-            "AUTO_REVOKE_MANAGED_BY_INSTALLER",
-            "NO_ISOLATED_STORAGE",
-            "PHONE_CALL_MICROPHONE",
-            "PHONE_CALL_CAMERA",
-            "RECORD_AUDIO_HOTWORD",
-            "MANAGE_ONGOING_CALLS",
-            "MANAGE_CREDENTIALS",
-            "USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER",
-            "RECORD_AUDIO_OUTPUT",
-            "SCHEDULE_EXACT_ALARM",
-            "FINE_LOCATION_SOURCE",
-            "COARSE_LOCATION_SOURCE",
-            "MANAGE_MEDIA",
-            "BLUETOOTH_CONNECT",
-            "UWB_RANGING",
-            "ACTIVITY_RECOGNITION_SOURCE",
-            "BLUETOOTH_ADVERTISE",
-            "RECORD_INCOMING_PHONE_AUDIO",
-            "NEARBY_WIFI_DEVICES",
-            "ESTABLISH_VPN_SERVICE",
-            "ESTABLISH_VPN_MANAGER",
-            "ACCESS_RESTRICTED_SETTINGS",
-            "RECEIVE_SOUNDTRIGGER_AUDIO",
-    };
-
-    /**
-     * This optionally maps a permission to an operation.  If there
-     * is no permission associated with an operation, it is null.
-     */
-    @UnsupportedAppUsage
-    private static String[] sOpPerms = new String[] {
-            android.Manifest.permission.ACCESS_COARSE_LOCATION,
-            android.Manifest.permission.ACCESS_FINE_LOCATION,
-            null,
-            android.Manifest.permission.VIBRATE,
-            android.Manifest.permission.READ_CONTACTS,
-            android.Manifest.permission.WRITE_CONTACTS,
-            android.Manifest.permission.READ_CALL_LOG,
-            android.Manifest.permission.WRITE_CALL_LOG,
-            android.Manifest.permission.READ_CALENDAR,
-            android.Manifest.permission.WRITE_CALENDAR,
-            android.Manifest.permission.ACCESS_WIFI_STATE,
-            android.Manifest.permission.POST_NOTIFICATIONS,
-            null, // neighboring cells shares the coarse location perm
-            android.Manifest.permission.CALL_PHONE,
-            android.Manifest.permission.READ_SMS,
-            null, // no permission required for writing sms
-            android.Manifest.permission.RECEIVE_SMS,
-            android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
-            android.Manifest.permission.RECEIVE_MMS,
-            android.Manifest.permission.RECEIVE_WAP_PUSH,
-            android.Manifest.permission.SEND_SMS,
-            android.Manifest.permission.READ_SMS,
-            null, // no permission required for writing icc sms
-            android.Manifest.permission.WRITE_SETTINGS,
-            android.Manifest.permission.SYSTEM_ALERT_WINDOW,
-            android.Manifest.permission.ACCESS_NOTIFICATIONS,
-            android.Manifest.permission.CAMERA,
-            android.Manifest.permission.RECORD_AUDIO,
-            null, // no permission for playing audio
-            null, // no permission for reading clipboard
-            null, // no permission for writing clipboard
-            null, // no permission for taking media buttons
-            null, // no permission for taking audio focus
-            null, // no permission for changing global volume
-            null, // no permission for changing voice volume
-            null, // no permission for changing ring volume
-            null, // no permission for changing media volume
-            null, // no permission for changing alarm volume
-            null, // no permission for changing notification volume
-            null, // no permission for changing bluetooth volume
-            android.Manifest.permission.WAKE_LOCK,
-            null, // no permission for generic location monitoring
-            null, // no permission for high power location monitoring
-            android.Manifest.permission.PACKAGE_USAGE_STATS,
-            null, // no permission for muting/unmuting microphone
-            null, // no permission for displaying toasts
-            null, // no permission for projecting media
-            null, // no permission for activating vpn
-            null, // no permission for supporting wallpaper
-            null, // no permission for receiving assist structure
-            null, // no permission for receiving assist screenshot
-            Manifest.permission.READ_PHONE_STATE,
-            Manifest.permission.ADD_VOICEMAIL,
-            Manifest.permission.USE_SIP,
-            Manifest.permission.PROCESS_OUTGOING_CALLS,
-            Manifest.permission.USE_FINGERPRINT,
-            Manifest.permission.BODY_SENSORS,
-            Manifest.permission.READ_CELL_BROADCASTS,
-            null,
-            Manifest.permission.READ_EXTERNAL_STORAGE,
-            Manifest.permission.WRITE_EXTERNAL_STORAGE,
-            Manifest.permission.TURN_SCREEN_ON,
-            Manifest.permission.GET_ACCOUNTS,
-            null, // no permission for running in background
-            null, // no permission for changing accessibility volume
-            Manifest.permission.READ_PHONE_NUMBERS,
-            Manifest.permission.REQUEST_INSTALL_PACKAGES,
-            null, // no permission for entering picture-in-picture on hide
-            Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
-            Manifest.permission.ANSWER_PHONE_CALLS,
-            null, // no permission for OP_RUN_ANY_IN_BACKGROUND
-            Manifest.permission.CHANGE_WIFI_STATE,
-            Manifest.permission.REQUEST_DELETE_PACKAGES,
-            Manifest.permission.BIND_ACCESSIBILITY_SERVICE,
-            Manifest.permission.ACCEPT_HANDOVER,
-            Manifest.permission.MANAGE_IPSEC_TUNNELS,
-            Manifest.permission.FOREGROUND_SERVICE,
-            Manifest.permission.BLUETOOTH_SCAN,
-            Manifest.permission.USE_BIOMETRIC,
-            Manifest.permission.ACTIVITY_RECOGNITION,
-            Manifest.permission.SMS_FINANCIAL_TRANSACTIONS,
-            Manifest.permission.READ_MEDIA_AUDIO,
-            null, // no permission for OP_WRITE_MEDIA_AUDIO
-            Manifest.permission.READ_MEDIA_VIDEO,
-            null, // no permission for OP_WRITE_MEDIA_VIDEO
-            Manifest.permission.READ_MEDIA_IMAGES,
-            null, // no permission for OP_WRITE_MEDIA_IMAGES
-            null, // no permission for OP_LEGACY_STORAGE
-            null, // no permission for OP_ACCESS_ACCESSIBILITY
-            null, // no direct permission for OP_READ_DEVICE_IDENTIFIERS
-            Manifest.permission.ACCESS_MEDIA_LOCATION,
-            null, // no permission for OP_QUERY_ALL_PACKAGES
-            Manifest.permission.MANAGE_EXTERNAL_STORAGE,
-            android.Manifest.permission.INTERACT_ACROSS_PROFILES,
-            null, // no permission for OP_ACTIVATE_PLATFORM_VPN
-            android.Manifest.permission.LOADER_USAGE_STATS,
-            null, // deprecated operation
-            null, // no permission for OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
-            null, // no permission for OP_AUTO_REVOKE_MANAGED_BY_INSTALLER
-            null, // no permission for OP_NO_ISOLATED_STORAGE
-            null, // no permission for OP_PHONE_CALL_MICROPHONE
-            null, // no permission for OP_PHONE_CALL_CAMERA
-            null, // no permission for OP_RECORD_AUDIO_HOTWORD
-            Manifest.permission.MANAGE_ONGOING_CALLS,
-            null, // no permission for OP_MANAGE_CREDENTIALS
-            Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
-            null, // no permission for OP_RECORD_AUDIO_OUTPUT
-            Manifest.permission.SCHEDULE_EXACT_ALARM,
-            null, // no permission for OP_ACCESS_FINE_LOCATION_SOURCE,
-            null, // no permission for OP_ACCESS_COARSE_LOCATION_SOURCE,
-            Manifest.permission.MANAGE_MEDIA,
-            Manifest.permission.BLUETOOTH_CONNECT,
-            Manifest.permission.UWB_RANGING,
-            null, // no permission for OP_ACTIVITY_RECOGNITION_SOURCE,
-            Manifest.permission.BLUETOOTH_ADVERTISE,
-            null, // no permission for OP_RECORD_INCOMING_PHONE_AUDIO,
-            Manifest.permission.NEARBY_WIFI_DEVICES,
-            null, // no permission for OP_ESTABLISH_VPN_SERVICE
-            null, // no permission for OP_ESTABLISH_VPN_MANAGER
-            null, // no permission for OP_ACCESS_RESTRICTED_SETTINGS,
-            null, // no permission for OP_RECEIVE_SOUNDTRIGGER_AUDIO
-    };
-
-    /**
-     * Specifies whether an Op should be restricted by a user restriction.
-     * Each Op should be filled with a restriction string from UserManager or
-     * null to specify it is not affected by any user restriction.
-     */
-    private static String[] sOpRestrictions = new String[] {
-            UserManager.DISALLOW_SHARE_LOCATION, //COARSE_LOCATION
-            UserManager.DISALLOW_SHARE_LOCATION, //FINE_LOCATION
-            UserManager.DISALLOW_SHARE_LOCATION, //GPS
-            null, //VIBRATE
-            null, //READ_CONTACTS
-            null, //WRITE_CONTACTS
-            UserManager.DISALLOW_OUTGOING_CALLS, //READ_CALL_LOG
-            UserManager.DISALLOW_OUTGOING_CALLS, //WRITE_CALL_LOG
-            null, //READ_CALENDAR
-            null, //WRITE_CALENDAR
-            UserManager.DISALLOW_SHARE_LOCATION, //WIFI_SCAN
-            null, //POST_NOTIFICATION
-            null, //NEIGHBORING_CELLS
-            null, //CALL_PHONE
-            UserManager.DISALLOW_SMS, //READ_SMS
-            UserManager.DISALLOW_SMS, //WRITE_SMS
-            UserManager.DISALLOW_SMS, //RECEIVE_SMS
-            null, //RECEIVE_EMERGENCY_SMS
-            UserManager.DISALLOW_SMS, //RECEIVE_MMS
-            null, //RECEIVE_WAP_PUSH
-            UserManager.DISALLOW_SMS, //SEND_SMS
-            UserManager.DISALLOW_SMS, //READ_ICC_SMS
-            UserManager.DISALLOW_SMS, //WRITE_ICC_SMS
-            null, //WRITE_SETTINGS
-            UserManager.DISALLOW_CREATE_WINDOWS, //SYSTEM_ALERT_WINDOW
-            null, //ACCESS_NOTIFICATIONS
-            UserManager.DISALLOW_CAMERA, //CAMERA
-            UserManager.DISALLOW_RECORD_AUDIO, //RECORD_AUDIO
-            null, //PLAY_AUDIO
-            null, //READ_CLIPBOARD
-            null, //WRITE_CLIPBOARD
-            null, //TAKE_MEDIA_BUTTONS
-            null, //TAKE_AUDIO_FOCUS
-            UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_MASTER_VOLUME
-            UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_VOICE_VOLUME
-            UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_RING_VOLUME
-            UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_MEDIA_VOLUME
-            UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_ALARM_VOLUME
-            UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_NOTIFICATION_VOLUME
-            UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_BLUETOOTH_VOLUME
-            null, //WAKE_LOCK
-            UserManager.DISALLOW_SHARE_LOCATION, //MONITOR_LOCATION
-            UserManager.DISALLOW_SHARE_LOCATION, //MONITOR_HIGH_POWER_LOCATION
-            null, //GET_USAGE_STATS
-            UserManager.DISALLOW_UNMUTE_MICROPHONE, // MUTE_MICROPHONE
-            UserManager.DISALLOW_CREATE_WINDOWS, // TOAST_WINDOW
-            null, //PROJECT_MEDIA
-            null, // ACTIVATE_VPN
-            UserManager.DISALLOW_WALLPAPER, // WRITE_WALLPAPER
-            null, // ASSIST_STRUCTURE
-            null, // ASSIST_SCREENSHOT
-            null, // READ_PHONE_STATE
-            null, // ADD_VOICEMAIL
-            null, // USE_SIP
-            null, // PROCESS_OUTGOING_CALLS
-            null, // USE_FINGERPRINT
-            null, // BODY_SENSORS
-            null, // READ_CELL_BROADCASTS
-            null, // MOCK_LOCATION
-            null, // READ_EXTERNAL_STORAGE
-            null, // WRITE_EXTERNAL_STORAGE
-            null, // TURN_SCREEN_ON
-            null, // GET_ACCOUNTS
-            null, // RUN_IN_BACKGROUND
-            UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_ACCESSIBILITY_VOLUME
-            null, // READ_PHONE_NUMBERS
-            null, // REQUEST_INSTALL_PACKAGES
-            null, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
-            null, // INSTANT_APP_START_FOREGROUND
-            null, // ANSWER_PHONE_CALLS
-            null, // OP_RUN_ANY_IN_BACKGROUND
-            null, // OP_CHANGE_WIFI_STATE
-            null, // REQUEST_DELETE_PACKAGES
-            null, // OP_BIND_ACCESSIBILITY_SERVICE
-            null, // ACCEPT_HANDOVER
-            null, // MANAGE_IPSEC_TUNNELS
-            null, // START_FOREGROUND
-            null, // maybe should be UserManager.DISALLOW_SHARE_LOCATION, //BLUETOOTH_SCAN
-            null, // USE_BIOMETRIC
-            null, // ACTIVITY_RECOGNITION
-            UserManager.DISALLOW_SMS, // SMS_FINANCIAL_TRANSACTIONS
-            null, // READ_MEDIA_AUDIO
-            null, // WRITE_MEDIA_AUDIO
-            null, // READ_MEDIA_VIDEO
-            null, // WRITE_MEDIA_VIDEO
-            null, // READ_MEDIA_IMAGES
-            null, // WRITE_MEDIA_IMAGES
-            null, // LEGACY_STORAGE
-            null, // ACCESS_ACCESSIBILITY
-            null, // READ_DEVICE_IDENTIFIERS
-            null, // ACCESS_MEDIA_LOCATION
-            null, // QUERY_ALL_PACKAGES
-            null, // MANAGE_EXTERNAL_STORAGE
-            null, // INTERACT_ACROSS_PROFILES
-            null, // ACTIVATE_PLATFORM_VPN
-            null, // LOADER_USAGE_STATS
-            null, // deprecated operation
-            null, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
-            null, // AUTO_REVOKE_MANAGED_BY_INSTALLER
-            null, // NO_ISOLATED_STORAGE
-            null, // PHONE_CALL_MICROPHONE
-            null, // PHONE_CALL_MICROPHONE
-            null, // RECORD_AUDIO_HOTWORD
-            null, // MANAGE_ONGOING_CALLS
-            null, // MANAGE_CREDENTIALS
-            null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
-            null, // RECORD_AUDIO_OUTPUT
-            null, // SCHEDULE_EXACT_ALARM
-            null, // ACCESS_FINE_LOCATION_SOURCE
-            null, // ACCESS_COARSE_LOCATION_SOURCE
-            null, // MANAGE_MEDIA
-            null, // BLUETOOTH_CONNECT
-            null, // UWB_RANGING
-            null, // ACTIVITY_RECOGNITION_SOURCE
-            null, // BLUETOOTH_ADVERTISE
-            null, // RECORD_INCOMING_PHONE_AUDIO
-            null, // NEARBY_WIFI_DEVICES
-            null, // ESTABLISH_VPN_SERVICE
-            null, // ESTABLISH_VPN_MANAGER
-            null, // ACCESS_RESTRICTED_SETTINGS
-            null, // RECEIVE_SOUNDTRIGGER_AUDIO
-    };
-
-    /**
-     * In which cases should an app be allowed to bypass the {@link #setUserRestriction user
-     * restriction} for a certain app-op.
-     */
-    private static RestrictionBypass[] sOpAllowSystemRestrictionBypass = new RestrictionBypass[] {
-            new RestrictionBypass(true, false, false), //COARSE_LOCATION
-            new RestrictionBypass(true, false, false), //FINE_LOCATION
-            null, //GPS
-            null, //VIBRATE
-            null, //READ_CONTACTS
-            null, //WRITE_CONTACTS
-            null, //READ_CALL_LOG
-            null, //WRITE_CALL_LOG
-            null, //READ_CALENDAR
-            null, //WRITE_CALENDAR
-            new RestrictionBypass(false, true, false), //WIFI_SCAN
-            null, //POST_NOTIFICATION
-            null, //NEIGHBORING_CELLS
-            null, //CALL_PHONE
-            null, //READ_SMS
-            null, //WRITE_SMS
-            null, //RECEIVE_SMS
-            null, //RECEIVE_EMERGECY_SMS
-            null, //RECEIVE_MMS
-            null, //RECEIVE_WAP_PUSH
-            null, //SEND_SMS
-            null, //READ_ICC_SMS
-            null, //WRITE_ICC_SMS
-            null, //WRITE_SETTINGS
-            new RestrictionBypass(false, true, false), //SYSTEM_ALERT_WINDOW
-            null, //ACCESS_NOTIFICATIONS
-            null, //CAMERA
-            new RestrictionBypass(false, false, true), //RECORD_AUDIO
-            null, //PLAY_AUDIO
-            null, //READ_CLIPBOARD
-            null, //WRITE_CLIPBOARD
-            null, //TAKE_MEDIA_BUTTONS
-            null, //TAKE_AUDIO_FOCUS
-            null, //AUDIO_MASTER_VOLUME
-            null, //AUDIO_VOICE_VOLUME
-            null, //AUDIO_RING_VOLUME
-            null, //AUDIO_MEDIA_VOLUME
-            null, //AUDIO_ALARM_VOLUME
-            null, //AUDIO_NOTIFICATION_VOLUME
-            null, //AUDIO_BLUETOOTH_VOLUME
-            null, //WAKE_LOCK
-            null, //MONITOR_LOCATION
-            null, //MONITOR_HIGH_POWER_LOCATION
-            null, //GET_USAGE_STATS
-            null, //MUTE_MICROPHONE
-            new RestrictionBypass(false, true, false), //TOAST_WINDOW
-            null, //PROJECT_MEDIA
-            null, //ACTIVATE_VPN
-            null, //WALLPAPER
-            null, //ASSIST_STRUCTURE
-            null, //ASSIST_SCREENSHOT
-            null, //READ_PHONE_STATE
-            null, //ADD_VOICEMAIL
-            null, // USE_SIP
-            null, // PROCESS_OUTGOING_CALLS
-            null, // USE_FINGERPRINT
-            null, // BODY_SENSORS
-            null, // READ_CELL_BROADCASTS
-            null, // MOCK_LOCATION
-            null, // READ_EXTERNAL_STORAGE
-            null, // WRITE_EXTERNAL_STORAGE
-            null, // TURN_SCREEN_ON
-            null, // GET_ACCOUNTS
-            null, // RUN_IN_BACKGROUND
-            null, // AUDIO_ACCESSIBILITY_VOLUME
-            null, // READ_PHONE_NUMBERS
-            null, // REQUEST_INSTALL_PACKAGES
-            null, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
-            null, // INSTANT_APP_START_FOREGROUND
-            null, // ANSWER_PHONE_CALLS
-            null, // OP_RUN_ANY_IN_BACKGROUND
-            null, // OP_CHANGE_WIFI_STATE
-            null, // OP_REQUEST_DELETE_PACKAGES
-            null, // OP_BIND_ACCESSIBILITY_SERVICE
-            null, // ACCEPT_HANDOVER
-            null, // MANAGE_IPSEC_HANDOVERS
-            null, // START_FOREGROUND
-            new RestrictionBypass(false, true, false), // BLUETOOTH_SCAN
-            null, // USE_BIOMETRIC
-            null, // ACTIVITY_RECOGNITION
-            null, // SMS_FINANCIAL_TRANSACTIONS
-            null, // READ_MEDIA_AUDIO
-            null, // WRITE_MEDIA_AUDIO
-            null, // READ_MEDIA_VIDEO
-            null, // WRITE_MEDIA_VIDEO
-            null, // READ_MEDIA_IMAGES
-            null, // WRITE_MEDIA_IMAGES
-            null, // LEGACY_STORAGE
-            null, // ACCESS_ACCESSIBILITY
-            null, // READ_DEVICE_IDENTIFIERS
-            null, // ACCESS_MEDIA_LOCATION
-            null, // QUERY_ALL_PACKAGES
-            null, // MANAGE_EXTERNAL_STORAGE
-            null, // INTERACT_ACROSS_PROFILES
-            null, // ACTIVATE_PLATFORM_VPN
-            null, // LOADER_USAGE_STATS
-            null, // deprecated operation
-            null, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
-            null, // AUTO_REVOKE_MANAGED_BY_INSTALLER
-            null, // NO_ISOLATED_STORAGE
-            null, // PHONE_CALL_MICROPHONE
-            null, // PHONE_CALL_CAMERA
-            null, // RECORD_AUDIO_HOTWORD
-            null, // MANAGE_ONGOING_CALLS
-            null, // MANAGE_CREDENTIALS
-            null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
-            null, // RECORD_AUDIO_OUTPUT
-            null, // SCHEDULE_EXACT_ALARM
-            null, // ACCESS_FINE_LOCATION_SOURCE
-            null, // ACCESS_COARSE_LOCATION_SOURCE
-            null, // MANAGE_MEDIA
-            null, // BLUETOOTH_CONNECT
-            null, // UWB_RANGING
-            null, // ACTIVITY_RECOGNITION_SOURCE
-            null, // BLUETOOTH_ADVERTISE
-            null, // RECORD_INCOMING_PHONE_AUDIO
-            null, // NEARBY_WIFI_DEVICES
-            null, // ESTABLISH_VPN_SERVICE
-            null, // ESTABLISH_VPN_MANAGER
-            null, // ACCESS_RESTRICTED_SETTINGS
-            null, // RECEIVE_SOUNDTRIGGER_AUDIO
-    };
-
-    /**
-     * This specifies the default mode for each operation.
-     */
-    private static int[] sOpDefaultMode = new int[] {
-            AppOpsManager.MODE_ALLOWED, // COARSE_LOCATION
-            AppOpsManager.MODE_ALLOWED, // FINE_LOCATION
-            AppOpsManager.MODE_ALLOWED, // GPS
-            AppOpsManager.MODE_ALLOWED, // VIBRATE
-            AppOpsManager.MODE_ALLOWED, // READ_CONTACTS
-            AppOpsManager.MODE_ALLOWED, // WRITE_CONTACTS
-            AppOpsManager.MODE_ALLOWED, // READ_CALL_LOG
-            AppOpsManager.MODE_ALLOWED, // WRITE_CALL_LOG
-            AppOpsManager.MODE_ALLOWED, // READ_CALENDAR
-            AppOpsManager.MODE_ALLOWED, // WRITE_CALENDAR
-            AppOpsManager.MODE_ALLOWED, // WIFI_SCAN
-            AppOpsManager.MODE_ALLOWED, // POST_NOTIFICATION
-            AppOpsManager.MODE_ALLOWED, // NEIGHBORING_CELLS
-            AppOpsManager.MODE_ALLOWED, // CALL_PHONE
-            AppOpsManager.MODE_ALLOWED, // READ_SMS
-            AppOpsManager.MODE_IGNORED, // WRITE_SMS
-            AppOpsManager.MODE_ALLOWED, // RECEIVE_SMS
-            AppOpsManager.MODE_ALLOWED, // RECEIVE_EMERGENCY_BROADCAST
-            AppOpsManager.MODE_ALLOWED, // RECEIVE_MMS
-            AppOpsManager.MODE_ALLOWED, // RECEIVE_WAP_PUSH
-            AppOpsManager.MODE_ALLOWED, // SEND_SMS
-            AppOpsManager.MODE_ALLOWED, // READ_ICC_SMS
-            AppOpsManager.MODE_ALLOWED, // WRITE_ICC_SMS
-            AppOpsManager.MODE_DEFAULT, // WRITE_SETTINGS
-            getSystemAlertWindowDefault(), // SYSTEM_ALERT_WINDOW
-            AppOpsManager.MODE_ALLOWED, // ACCESS_NOTIFICATIONS
-            AppOpsManager.MODE_ALLOWED, // CAMERA
-            AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO
-            AppOpsManager.MODE_ALLOWED, // PLAY_AUDIO
-            AppOpsManager.MODE_ALLOWED, // READ_CLIPBOARD
-            AppOpsManager.MODE_ALLOWED, // WRITE_CLIPBOARD
-            AppOpsManager.MODE_ALLOWED, // TAKE_MEDIA_BUTTONS
-            AppOpsManager.MODE_ALLOWED, // TAKE_AUDIO_FOCUS
-            AppOpsManager.MODE_ALLOWED, // AUDIO_MASTER_VOLUME
-            AppOpsManager.MODE_ALLOWED, // AUDIO_VOICE_VOLUME
-            AppOpsManager.MODE_ALLOWED, // AUDIO_RING_VOLUME
-            AppOpsManager.MODE_ALLOWED, // AUDIO_MEDIA_VOLUME
-            AppOpsManager.MODE_ALLOWED, // AUDIO_ALARM_VOLUME
-            AppOpsManager.MODE_ALLOWED, // AUDIO_NOTIFICATION_VOLUME
-            AppOpsManager.MODE_ALLOWED, // AUDIO_BLUETOOTH_VOLUME
-            AppOpsManager.MODE_ALLOWED, // WAKE_LOCK
-            AppOpsManager.MODE_ALLOWED, // MONITOR_LOCATION
-            AppOpsManager.MODE_ALLOWED, // MONITOR_HIGH_POWER_LOCATION
-            AppOpsManager.MODE_DEFAULT, // GET_USAGE_STATS
-            AppOpsManager.MODE_ALLOWED, // MUTE_MICROPHONE
-            AppOpsManager.MODE_ALLOWED, // TOAST_WINDOW
-            AppOpsManager.MODE_IGNORED, // PROJECT_MEDIA
-            AppOpsManager.MODE_IGNORED, // ACTIVATE_VPN
-            AppOpsManager.MODE_ALLOWED, // WRITE_WALLPAPER
-            AppOpsManager.MODE_ALLOWED, // ASSIST_STRUCTURE
-            AppOpsManager.MODE_ALLOWED, // ASSIST_SCREENSHOT
-            AppOpsManager.MODE_ALLOWED, // READ_PHONE_STATE
-            AppOpsManager.MODE_ALLOWED, // ADD_VOICEMAIL
-            AppOpsManager.MODE_ALLOWED, // USE_SIP
-            AppOpsManager.MODE_ALLOWED, // PROCESS_OUTGOING_CALLS
-            AppOpsManager.MODE_ALLOWED, // USE_FINGERPRINT
-            AppOpsManager.MODE_ALLOWED, // BODY_SENSORS
-            AppOpsManager.MODE_ALLOWED, // READ_CELL_BROADCASTS
-            AppOpsManager.MODE_ERRORED, // MOCK_LOCATION
-            AppOpsManager.MODE_ALLOWED, // READ_EXTERNAL_STORAGE
-            AppOpsManager.MODE_ALLOWED, // WRITE_EXTERNAL_STORAGE
-            AppOpsManager.MODE_ERRORED, // TURN_SCREEN_ON
-            AppOpsManager.MODE_ALLOWED, // GET_ACCOUNTS
-            AppOpsManager.MODE_ALLOWED, // RUN_IN_BACKGROUND
-            AppOpsManager.MODE_ALLOWED, // AUDIO_ACCESSIBILITY_VOLUME
-            AppOpsManager.MODE_ALLOWED, // READ_PHONE_NUMBERS
-            AppOpsManager.MODE_DEFAULT, // REQUEST_INSTALL_PACKAGES
-            AppOpsManager.MODE_ALLOWED, // PICTURE_IN_PICTURE
-            AppOpsManager.MODE_DEFAULT, // INSTANT_APP_START_FOREGROUND
-            AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS
-            AppOpsManager.MODE_ALLOWED, // RUN_ANY_IN_BACKGROUND
-            AppOpsManager.MODE_ALLOWED, // CHANGE_WIFI_STATE
-            AppOpsManager.MODE_ALLOWED, // REQUEST_DELETE_PACKAGES
-            AppOpsManager.MODE_ALLOWED, // BIND_ACCESSIBILITY_SERVICE
-            AppOpsManager.MODE_ALLOWED, // ACCEPT_HANDOVER
-            AppOpsManager.MODE_ERRORED, // MANAGE_IPSEC_TUNNELS
-            AppOpsManager.MODE_ALLOWED, // START_FOREGROUND
-            AppOpsManager.MODE_ALLOWED, // BLUETOOTH_SCAN
-            AppOpsManager.MODE_ALLOWED, // USE_BIOMETRIC
-            AppOpsManager.MODE_ALLOWED, // ACTIVITY_RECOGNITION
-            AppOpsManager.MODE_DEFAULT, // SMS_FINANCIAL_TRANSACTIONS
-            AppOpsManager.MODE_ALLOWED, // READ_MEDIA_AUDIO
-            AppOpsManager.MODE_ERRORED, // WRITE_MEDIA_AUDIO
-            AppOpsManager.MODE_ALLOWED, // READ_MEDIA_VIDEO
-            AppOpsManager.MODE_ERRORED, // WRITE_MEDIA_VIDEO
-            AppOpsManager.MODE_ALLOWED, // READ_MEDIA_IMAGES
-            AppOpsManager.MODE_ERRORED, // WRITE_MEDIA_IMAGES
-            AppOpsManager.MODE_DEFAULT, // LEGACY_STORAGE
-            AppOpsManager.MODE_ALLOWED, // ACCESS_ACCESSIBILITY
-            AppOpsManager.MODE_ERRORED, // READ_DEVICE_IDENTIFIERS
-            AppOpsManager.MODE_ALLOWED, // ALLOW_MEDIA_LOCATION
-            AppOpsManager.MODE_DEFAULT, // QUERY_ALL_PACKAGES
-            AppOpsManager.MODE_DEFAULT, // MANAGE_EXTERNAL_STORAGE
-            AppOpsManager.MODE_DEFAULT, // INTERACT_ACROSS_PROFILES
-            AppOpsManager.MODE_IGNORED, // ACTIVATE_PLATFORM_VPN
-            AppOpsManager.MODE_DEFAULT, // LOADER_USAGE_STATS
-            AppOpsManager.MODE_IGNORED, // deprecated operation
-            AppOpsManager.MODE_DEFAULT, // OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
-            AppOpsManager.MODE_ALLOWED, // OP_AUTO_REVOKE_MANAGED_BY_INSTALLER
-            AppOpsManager.MODE_ERRORED, // OP_NO_ISOLATED_STORAGE
-            AppOpsManager.MODE_ALLOWED, // PHONE_CALL_MICROPHONE
-            AppOpsManager.MODE_ALLOWED, // PHONE_CALL_CAMERA
-            AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO_HOTWORD
-            AppOpsManager.MODE_DEFAULT, // MANAGE_ONGOING_CALLS
-            AppOpsManager.MODE_DEFAULT, // MANAGE_CREDENTIALS
-            AppOpsManager.MODE_DEFAULT, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
-            AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO_OUTPUT
-            AppOpsManager.MODE_DEFAULT, // SCHEDULE_EXACT_ALARM
-            AppOpsManager.MODE_ALLOWED, // ACCESS_FINE_LOCATION_SOURCE
-            AppOpsManager.MODE_ALLOWED, // ACCESS_COARSE_LOCATION_SOURCE
-            AppOpsManager.MODE_DEFAULT, // MANAGE_MEDIA
-            AppOpsManager.MODE_ALLOWED, // BLUETOOTH_CONNECT
-            AppOpsManager.MODE_ALLOWED, // UWB_RANGING
-            AppOpsManager.MODE_ALLOWED, // ACTIVITY_RECOGNITION_SOURCE
-            AppOpsManager.MODE_ALLOWED, // BLUETOOTH_ADVERTISE
-            AppOpsManager.MODE_ALLOWED, // RECORD_INCOMING_PHONE_AUDIO
-            AppOpsManager.MODE_ALLOWED, // NEARBY_WIFI_DEVICES
-            AppOpsManager.MODE_ALLOWED, // ESTABLISH_VPN_SERVICE
-            AppOpsManager.MODE_ALLOWED, // ESTABLISH_VPN_MANAGER
-            AppOpsManager.MODE_ALLOWED, // ACCESS_RESTRICTED_SETTINGS,
-            AppOpsManager.MODE_ALLOWED, // RECEIVE_SOUNDTRIGGER_AUDIO
-    };
-
-    /**
-     * This specifies whether each option is allowed to be reset
-     * when resetting all app preferences.  Disable reset for
-     * app ops that are under strong control of some part of the
-     * system (such as OP_WRITE_SMS, which should be allowed only
-     * for whichever app is selected as the current SMS app).
-     */
-    private static boolean[] sOpDisableReset = new boolean[] {
-            false, // COARSE_LOCATION
-            false, // FINE_LOCATION
-            false, // GPS
-            false, // VIBRATE
-            false, // READ_CONTACTS
-            false, // WRITE_CONTACTS
-            false, // READ_CALL_LOG
-            false, // WRITE_CALL_LOG
-            false, // READ_CALENDAR
-            false, // WRITE_CALENDAR
-            false, // WIFI_SCAN
-            false, // POST_NOTIFICATION
-            false, // NEIGHBORING_CELLS
-            false, // CALL_PHONE
-            true, // READ_SMS
-            true, // WRITE_SMS
-            true, // RECEIVE_SMS
-            false, // RECEIVE_EMERGENCY_BROADCAST
-            false, // RECEIVE_MMS
-            true, // RECEIVE_WAP_PUSH
-            true, // SEND_SMS
-            false, // READ_ICC_SMS
-            false, // WRITE_ICC_SMS
-            false, // WRITE_SETTINGS
-            false, // SYSTEM_ALERT_WINDOW
-            false, // ACCESS_NOTIFICATIONS
-            false, // CAMERA
-            false, // RECORD_AUDIO
-            false, // PLAY_AUDIO
-            false, // READ_CLIPBOARD
-            false, // WRITE_CLIPBOARD
-            false, // TAKE_MEDIA_BUTTONS
-            false, // TAKE_AUDIO_FOCUS
-            false, // AUDIO_MASTER_VOLUME
-            false, // AUDIO_VOICE_VOLUME
-            false, // AUDIO_RING_VOLUME
-            false, // AUDIO_MEDIA_VOLUME
-            false, // AUDIO_ALARM_VOLUME
-            false, // AUDIO_NOTIFICATION_VOLUME
-            false, // AUDIO_BLUETOOTH_VOLUME
-            false, // WAKE_LOCK
-            false, // MONITOR_LOCATION
-            false, // MONITOR_HIGH_POWER_LOCATION
-            false, // GET_USAGE_STATS
-            false, // MUTE_MICROPHONE
-            false, // TOAST_WINDOW
-            false, // PROJECT_MEDIA
-            false, // ACTIVATE_VPN
-            false, // WRITE_WALLPAPER
-            false, // ASSIST_STRUCTURE
-            false, // ASSIST_SCREENSHOT
-            false, // READ_PHONE_STATE
-            false, // ADD_VOICEMAIL
-            false, // USE_SIP
-            false, // PROCESS_OUTGOING_CALLS
-            false, // USE_FINGERPRINT
-            false, // BODY_SENSORS
-            true, // READ_CELL_BROADCASTS
-            false, // MOCK_LOCATION
-            false, // READ_EXTERNAL_STORAGE
-            false, // WRITE_EXTERNAL_STORAGE
-            false, // TURN_SCREEN_ON
-            false, // GET_ACCOUNTS
-            false, // RUN_IN_BACKGROUND
-            false, // AUDIO_ACCESSIBILITY_VOLUME
-            false, // READ_PHONE_NUMBERS
-            false, // REQUEST_INSTALL_PACKAGES
-            false, // PICTURE_IN_PICTURE
-            false, // INSTANT_APP_START_FOREGROUND
-            false, // ANSWER_PHONE_CALLS
-            false, // RUN_ANY_IN_BACKGROUND
-            false, // CHANGE_WIFI_STATE
-            false, // REQUEST_DELETE_PACKAGES
-            false, // BIND_ACCESSIBILITY_SERVICE
-            false, // ACCEPT_HANDOVER
-            false, // MANAGE_IPSEC_TUNNELS
-            false, // START_FOREGROUND
-            false, // BLUETOOTH_SCAN
-            false, // USE_BIOMETRIC
-            false, // ACTIVITY_RECOGNITION
-            false, // SMS_FINANCIAL_TRANSACTIONS
-            false, // READ_MEDIA_AUDIO
-            false, // WRITE_MEDIA_AUDIO
-            false, // READ_MEDIA_VIDEO
-            true,  // WRITE_MEDIA_VIDEO
-            false, // READ_MEDIA_IMAGES
-            true,  // WRITE_MEDIA_IMAGES
-            true,  // LEGACY_STORAGE
-            false, // ACCESS_ACCESSIBILITY
-            false, // READ_DEVICE_IDENTIFIERS
-            false, // ACCESS_MEDIA_LOCATION
-            false, // QUERY_ALL_PACKAGES
-            false, // MANAGE_EXTERNAL_STORAGE
-            false, // INTERACT_ACROSS_PROFILES
-            false, // ACTIVATE_PLATFORM_VPN
-            false, // LOADER_USAGE_STATS
-            false, // deprecated operation
-            false, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
-            false, // AUTO_REVOKE_MANAGED_BY_INSTALLER
-            true, // NO_ISOLATED_STORAGE
-            false, // PHONE_CALL_MICROPHONE
-            false, // PHONE_CALL_CAMERA
-            false, // RECORD_AUDIO_HOTWORD
-            true, // MANAGE_ONGOING_CALLS
-            false, // MANAGE_CREDENTIALS
-            true, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
-            false, // RECORD_AUDIO_OUTPUT
-            false, // SCHEDULE_EXACT_ALARM
-            false, // ACCESS_FINE_LOCATION_SOURCE
-            false, // ACCESS_COARSE_LOCATION_SOURCE
-            false, // MANAGE_MEDIA
-            false, // BLUETOOTH_CONNECT
-            false, // UWB_RANGING
-            false, // ACTIVITY_RECOGNITION_SOURCE
-            false, // BLUETOOTH_ADVERTISE
-            false, // RECORD_INCOMING_PHONE_AUDIO
-            false, // NEARBY_WIFI_DEVICES
-            false, // OP_ESTABLISH_VPN_SERVICE
-            false, // OP_ESTABLISH_VPN_MANAGER
-            true, // ACCESS_RESTRICTED_SETTINGS
-            false, // RECEIVE_SOUNDTRIGGER_AUDIO
-    };
-
-    /**
-     * This specifies whether each option is only allowed to be read
-     * by apps with manage appops permission.
-     */
-    private static boolean[] sOpRestrictRead = new boolean[] {
-            false, // COARSE_LOCATION
-            false, // FINE_LOCATION
-            false, // GPS
-            false, // VIBRATE
-            false, // READ_CONTACTS
-            false, // WRITE_CONTACTS
-            false, // READ_CALL_LOG
-            false, // WRITE_CALL_LOG
-            false, // READ_CALENDAR
-            false, // WRITE_CALENDAR
-            false, // WIFI_SCAN
-            false, // POST_NOTIFICATION
-            false, // NEIGHBORING_CELLS
-            false, // CALL_PHONE
-            false, // READ_SMS
-            false, // WRITE_SMS
-            false, // RECEIVE_SMS
-            false, // RECEIVE_EMERGENCY_BROADCAST
-            false, // RECEIVE_MMS
-            false, // RECEIVE_WAP_PUSH
-            false, // SEND_SMS
-            false, // READ_ICC_SMS
-            false, // WRITE_ICC_SMS
-            false, // WRITE_SETTINGS
-            false, // SYSTEM_ALERT_WINDOW
-            false, // ACCESS_NOTIFICATIONS
-            false, // CAMERA
-            false, // RECORD_AUDIO
-            false, // PLAY_AUDIO
-            false, // READ_CLIPBOARD
-            false, // WRITE_CLIPBOARD
-            false, // TAKE_MEDIA_BUTTONS
-            false, // TAKE_AUDIO_FOCUS
-            false, // AUDIO_MASTER_VOLUME
-            false, // AUDIO_VOICE_VOLUME
-            false, // AUDIO_RING_VOLUME
-            false, // AUDIO_MEDIA_VOLUME
-            false, // AUDIO_ALARM_VOLUME
-            false, // AUDIO_NOTIFICATION_VOLUME
-            false, // AUDIO_BLUETOOTH_VOLUME
-            false, // WAKE_LOCK
-            false, // MONITOR_LOCATION
-            false, // MONITOR_HIGH_POWER_LOCATION
-            false, // GET_USAGE_STATS
-            false, // MUTE_MICROPHONE
-            false, // TOAST_WINDOW
-            false, // PROJECT_MEDIA
-            false, // ACTIVATE_VPN
-            false, // WRITE_WALLPAPER
-            false, // ASSIST_STRUCTURE
-            false, // ASSIST_SCREENSHOT
-            false, // READ_PHONE_STATE
-            false, // ADD_VOICEMAIL
-            false, // USE_SIP
-            false, // PROCESS_OUTGOING_CALLS
-            false, // USE_FINGERPRINT
-            false, // BODY_SENSORS
-            false, // READ_CELL_BROADCASTS
-            false, // MOCK_LOCATION
-            false, // READ_EXTERNAL_STORAGE
-            false, // WRITE_EXTERNAL_STORAGE
-            false, // TURN_SCREEN_ON
-            false, // GET_ACCOUNTS
-            false, // RUN_IN_BACKGROUND
-            false, // AUDIO_ACCESSIBILITY_VOLUME
-            false, // READ_PHONE_NUMBERS
-            false, // REQUEST_INSTALL_PACKAGES
-            false, // PICTURE_IN_PICTURE
-            false, // INSTANT_APP_START_FOREGROUND
-            false, // ANSWER_PHONE_CALLS
-            false, // RUN_ANY_IN_BACKGROUND
-            false, // CHANGE_WIFI_STATE
-            false, // REQUEST_DELETE_PACKAGES
-            false, // BIND_ACCESSIBILITY_SERVICE
-            false, // ACCEPT_HANDOVER
-            false, // MANAGE_IPSEC_TUNNELS
-            false, // START_FOREGROUND
-            false, // BLUETOOTH_SCAN
-            false, // USE_BIOMETRIC
-            false, // ACTIVITY_RECOGNITION
-            false, // SMS_FINANCIAL_TRANSACTIONS
-            false, // READ_MEDIA_AUDIO
-            false, // WRITE_MEDIA_AUDIO
-            false, // READ_MEDIA_VIDEO
-            false,  // WRITE_MEDIA_VIDEO
-            false, // READ_MEDIA_IMAGES
-            false,  // WRITE_MEDIA_IMAGES
-            false,  // LEGACY_STORAGE
-            false, // ACCESS_ACCESSIBILITY
-            false, // READ_DEVICE_IDENTIFIERS
-            false, // ACCESS_MEDIA_LOCATION
-            false, // QUERY_ALL_PACKAGES
-            false, // MANAGE_EXTERNAL_STORAGE
-            false, // INTERACT_ACROSS_PROFILES
-            false, // ACTIVATE_PLATFORM_VPN
-            false, // LOADER_USAGE_STATS
-            false, // deprecated operation
-            false, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
-            false, // AUTO_REVOKE_MANAGED_BY_INSTALLER
-            false, // NO_ISOLATED_STORAGE
-            false, // PHONE_CALL_MICROPHONE
-            false, // PHONE_CALL_CAMERA
-            false, // RECORD_AUDIO_HOTWORD
-            false, // MANAGE_ONGOING_CALLS
-            false, // MANAGE_CREDENTIALS
-            false, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
-            false, // RECORD_AUDIO_OUTPUT
-            false, // SCHEDULE_EXACT_ALARM
-            false, // ACCESS_FINE_LOCATION_SOURCE
-            false, // ACCESS_COARSE_LOCATION_SOURCE
-            false, // MANAGE_MEDIA
-            false, // BLUETOOTH_CONNECT
-            false, // UWB_RANGING
-            false, // ACTIVITY_RECOGNITION_SOURCE
-            false, // BLUETOOTH_ADVERTISE
-            false, // RECORD_INCOMING_PHONE_AUDIO
-            false, // NEARBY_WIFI_DEVICES
-            false, // OP_ESTABLISH_VPN_SERVICE
-            false, // OP_ESTABLISH_VPN_MANAGER
-            true, // ACCESS_RESTRICTED_SETTINGS
-            false, // RECEIVE_SOUNDTRIGGER_AUDIO
+    static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
+        new AppOpInfo.Builder(OP_COARSE_LOCATION, OPSTR_COARSE_LOCATION, "COARSE_LOCATION")
+            .setPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+            .setRestriction(UserManager.DISALLOW_SHARE_LOCATION)
+            .setAllowSystemRestrictionBypass(new RestrictionBypass(true, false, false))
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_FINE_LOCATION, OPSTR_FINE_LOCATION, "FINE_LOCATION")
+            .setPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+            .setRestriction(UserManager.DISALLOW_SHARE_LOCATION)
+            .setAllowSystemRestrictionBypass(new RestrictionBypass(true, false, false))
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_GPS, OPSTR_GPS, "GPS")
+            .setSwitchCode(OP_COARSE_LOCATION)
+            .setRestriction(UserManager.DISALLOW_SHARE_LOCATION)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_VIBRATE, OPSTR_VIBRATE, "VIBRATE")
+            .setSwitchCode(OP_VIBRATE).setPermission(android.Manifest.permission.VIBRATE)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_READ_CONTACTS, OPSTR_READ_CONTACTS, "READ_CONTACTS")
+            .setPermission(android.Manifest.permission.READ_CONTACTS)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_WRITE_CONTACTS, OPSTR_WRITE_CONTACTS, "WRITE_CONTACTS")
+            .setPermission(android.Manifest.permission.WRITE_CONTACTS)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_READ_CALL_LOG, OPSTR_READ_CALL_LOG, "READ_CALL_LOG")
+            .setPermission(android.Manifest.permission.READ_CALL_LOG)
+            .setRestriction(UserManager.DISALLOW_OUTGOING_CALLS)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_WRITE_CALL_LOG, OPSTR_WRITE_CALL_LOG, "WRITE_CALL_LOG")
+            .setPermission(android.Manifest.permission.WRITE_CALL_LOG)
+            .setRestriction(UserManager.DISALLOW_OUTGOING_CALLS)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_READ_CALENDAR, OPSTR_READ_CALENDAR, "READ_CALENDAR")
+            .setPermission(android.Manifest.permission.READ_CALENDAR)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_WRITE_CALENDAR, OPSTR_WRITE_CALENDAR, "WRITE_CALENDAR")
+            .setPermission(android.Manifest.permission.WRITE_CALENDAR)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_WIFI_SCAN, OPSTR_WIFI_SCAN, "WIFI_SCAN")
+            .setSwitchCode(OP_COARSE_LOCATION)
+            .setPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
+            .setRestriction(UserManager.DISALLOW_SHARE_LOCATION)
+            .setAllowSystemRestrictionBypass(new RestrictionBypass(false, true, false))
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_POST_NOTIFICATION, OPSTR_POST_NOTIFICATION, "POST_NOTIFICATION")
+            .setPermission(android.Manifest.permission.POST_NOTIFICATIONS)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_NEIGHBORING_CELLS, OPSTR_NEIGHBORING_CELLS, "NEIGHBORING_CELLS")
+            .setSwitchCode(OP_COARSE_LOCATION).setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_CALL_PHONE, OPSTR_CALL_PHONE, "CALL_PHONE")
+            .setSwitchCode(OP_CALL_PHONE).setPermission(android.Manifest.permission.CALL_PHONE)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_READ_SMS, OPSTR_READ_SMS, "READ_SMS")
+            .setPermission(android.Manifest.permission.READ_SMS)
+            .setRestriction(UserManager.DISALLOW_SMS).setDefaultMode(AppOpsManager.MODE_ALLOWED)
+            .setDisableReset(true).build(),
+        new AppOpInfo.Builder(OP_WRITE_SMS, OPSTR_WRITE_SMS, "WRITE_SMS")
+            .setRestriction(UserManager.DISALLOW_SMS)
+            .setDefaultMode(AppOpsManager.MODE_IGNORED).setDisableReset(true).build(),
+        new AppOpInfo.Builder(OP_RECEIVE_SMS, OPSTR_RECEIVE_SMS, "RECEIVE_SMS")
+            .setPermission(android.Manifest.permission.RECEIVE_SMS)
+            .setRestriction(UserManager.DISALLOW_SMS)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).setDisableReset(true).build(),
+        new AppOpInfo.Builder(OP_RECEIVE_EMERGECY_SMS, OPSTR_RECEIVE_EMERGENCY_BROADCAST,
+                "RECEIVE_EMERGENCY_BROADCAST").setSwitchCode(OP_RECEIVE_SMS)
+            .setPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_RECEIVE_MMS, OPSTR_RECEIVE_MMS, "RECEIVE_MMS")
+            .setPermission(android.Manifest.permission.RECEIVE_MMS)
+            .setRestriction(UserManager.DISALLOW_SMS).setDefaultMode(AppOpsManager.MODE_ALLOWED)
+            .build(),
+        new AppOpInfo.Builder(OP_RECEIVE_WAP_PUSH, OPSTR_RECEIVE_WAP_PUSH, "RECEIVE_WAP_PUSH")
+            .setPermission(android.Manifest.permission.RECEIVE_WAP_PUSH)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).setDisableReset(true).build(),
+        new AppOpInfo.Builder(OP_SEND_SMS, OPSTR_SEND_SMS, "SEND_SMS")
+            .setPermission(android.Manifest.permission.SEND_SMS)
+            .setRestriction(UserManager.DISALLOW_SMS).setDefaultMode(AppOpsManager.MODE_ALLOWED)
+            .setDisableReset(true).build(),
+        new AppOpInfo.Builder(OP_READ_ICC_SMS, OPSTR_READ_ICC_SMS, "READ_ICC_SMS")
+            .setSwitchCode(OP_READ_SMS).setPermission(android.Manifest.permission.READ_SMS)
+            .setRestriction(UserManager.DISALLOW_SMS).setDefaultMode(AppOpsManager.MODE_ALLOWED)
+            .build(),
+        new AppOpInfo.Builder(OP_WRITE_ICC_SMS, OPSTR_WRITE_ICC_SMS, "WRITE_ICC_SMS")
+            .setSwitchCode(OP_WRITE_SMS).setRestriction(UserManager.DISALLOW_SMS)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_WRITE_SETTINGS, OPSTR_WRITE_SETTINGS, "WRITE_SETTINGS")
+            .setPermission(android.Manifest.permission.WRITE_SETTINGS).build(),
+        new AppOpInfo.Builder(OP_SYSTEM_ALERT_WINDOW, OPSTR_SYSTEM_ALERT_WINDOW,
+                "SYSTEM_ALERT_WINDOW")
+            .setPermission(android.Manifest.permission.SYSTEM_ALERT_WINDOW)
+            .setRestriction(UserManager.DISALLOW_CREATE_WINDOWS)
+            .setAllowSystemRestrictionBypass(new RestrictionBypass(false, true, false))
+            .setDefaultMode(getSystemAlertWindowDefault()).build(),
+        new AppOpInfo.Builder(OP_ACCESS_NOTIFICATIONS, OPSTR_ACCESS_NOTIFICATIONS,
+                "ACCESS_NOTIFICATIONS")
+            .setPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_CAMERA, OPSTR_CAMERA, "CAMERA")
+            .setPermission(android.Manifest.permission.CAMERA)
+            .setRestriction(UserManager.DISALLOW_CAMERA)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_RECORD_AUDIO, OPSTR_RECORD_AUDIO, "RECORD_AUDIO")
+            .setPermission(android.Manifest.permission.RECORD_AUDIO)
+            .setRestriction(UserManager.DISALLOW_RECORD_AUDIO)
+            .setAllowSystemRestrictionBypass(new RestrictionBypass(false, false, true))
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_PLAY_AUDIO, OPSTR_PLAY_AUDIO, "PLAY_AUDIO")
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_READ_CLIPBOARD, OPSTR_READ_CLIPBOARD, "READ_CLIPBOARD")
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_WRITE_CLIPBOARD, OPSTR_WRITE_CLIPBOARD, "WRITE_CLIPBOARD")
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_TAKE_MEDIA_BUTTONS, OPSTR_TAKE_MEDIA_BUTTONS, "TAKE_MEDIA_BUTTONS")
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED)
+            .build(),
+        new AppOpInfo.Builder(OP_TAKE_AUDIO_FOCUS, OPSTR_TAKE_AUDIO_FOCUS, "TAKE_AUDIO_FOCUS")
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_AUDIO_MASTER_VOLUME, OPSTR_AUDIO_MASTER_VOLUME,
+                "AUDIO_MASTER_VOLUME").setSwitchCode(OP_AUDIO_MASTER_VOLUME)
+            .setRestriction(UserManager.DISALLOW_ADJUST_VOLUME)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_AUDIO_VOICE_VOLUME, OPSTR_AUDIO_VOICE_VOLUME, "AUDIO_VOICE_VOLUME")
+            .setRestriction(UserManager.DISALLOW_ADJUST_VOLUME)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_AUDIO_RING_VOLUME, OPSTR_AUDIO_RING_VOLUME, "AUDIO_RING_VOLUME")
+            .setRestriction(UserManager.DISALLOW_ADJUST_VOLUME)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_AUDIO_MEDIA_VOLUME, OPSTR_AUDIO_MEDIA_VOLUME, "AUDIO_MEDIA_VOLUME")
+            .setRestriction(UserManager.DISALLOW_ADJUST_VOLUME)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_AUDIO_ALARM_VOLUME, OPSTR_AUDIO_ALARM_VOLUME, "AUDIO_ALARM_VOLUME")
+            .setRestriction(UserManager.DISALLOW_ADJUST_VOLUME)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_AUDIO_NOTIFICATION_VOLUME, OPSTR_AUDIO_NOTIFICATION_VOLUME,
+                "AUDIO_NOTIFICATION_VOLUME").setSwitchCode(OP_AUDIO_NOTIFICATION_VOLUME)
+            .setRestriction(UserManager.DISALLOW_ADJUST_VOLUME)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_AUDIO_BLUETOOTH_VOLUME, OPSTR_AUDIO_BLUETOOTH_VOLUME,
+                "AUDIO_BLUETOOTH_VOLUME").setRestriction(UserManager.DISALLOW_ADJUST_VOLUME)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_WAKE_LOCK, OPSTR_WAKE_LOCK, "WAKE_LOCK")
+            .setPermission(android.Manifest.permission.WAKE_LOCK)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_MONITOR_LOCATION, OPSTR_MONITOR_LOCATION, "MONITOR_LOCATION")
+            .setSwitchCode(OP_COARSE_LOCATION)
+            .setRestriction(UserManager.DISALLOW_SHARE_LOCATION)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_MONITOR_HIGH_POWER_LOCATION, OPSTR_MONITOR_HIGH_POWER_LOCATION,
+                "MONITOR_HIGH_POWER_LOCATION").setSwitchCode(OP_COARSE_LOCATION)
+            .setRestriction(UserManager.DISALLOW_SHARE_LOCATION)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_GET_USAGE_STATS, OPSTR_GET_USAGE_STATS, "GET_USAGE_STATS")
+            .setPermission(android.Manifest.permission.PACKAGE_USAGE_STATS).build(),
+        new AppOpInfo.Builder(OP_MUTE_MICROPHONE, OPSTR_MUTE_MICROPHONE, "MUTE_MICROPHONE")
+            .setRestriction(UserManager.DISALLOW_UNMUTE_MICROPHONE)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_TOAST_WINDOW, OPSTR_TOAST_WINDOW, "TOAST_WINDOW")
+            .setRestriction(UserManager.DISALLOW_CREATE_WINDOWS)
+            .setAllowSystemRestrictionBypass(new RestrictionBypass(false, true, false))
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_PROJECT_MEDIA, OPSTR_PROJECT_MEDIA, "PROJECT_MEDIA")
+            .setDefaultMode(AppOpsManager.MODE_IGNORED).build(),
+        new AppOpInfo.Builder(OP_ACTIVATE_VPN, OPSTR_ACTIVATE_VPN, "ACTIVATE_VPN")
+            .setDefaultMode(AppOpsManager.MODE_IGNORED).build(),
+        new AppOpInfo.Builder(OP_WRITE_WALLPAPER, OPSTR_WRITE_WALLPAPER, "WRITE_WALLPAPER")
+            .setRestriction(UserManager.DISALLOW_WALLPAPER)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_ASSIST_STRUCTURE, OPSTR_ASSIST_STRUCTURE, "ASSIST_STRUCTURE")
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_ASSIST_SCREENSHOT, OPSTR_ASSIST_SCREENSHOT, "ASSIST_SCREENSHOT")
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED)
+            .build(),
+        new AppOpInfo.Builder(OP_READ_PHONE_STATE, OPSTR_READ_PHONE_STATE, "READ_PHONE_STATE")
+            .setPermission(Manifest.permission.READ_PHONE_STATE)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_ADD_VOICEMAIL, OPSTR_ADD_VOICEMAIL, "ADD_VOICEMAIL")
+            .setPermission(Manifest.permission.ADD_VOICEMAIL)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_USE_SIP, OPSTR_USE_SIP, "USE_SIP")
+            .setPermission(Manifest.permission.USE_SIP)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_PROCESS_OUTGOING_CALLS, OPSTR_PROCESS_OUTGOING_CALLS,
+                "PROCESS_OUTGOING_CALLS").setSwitchCode(OP_PROCESS_OUTGOING_CALLS)
+            .setPermission(Manifest.permission.PROCESS_OUTGOING_CALLS)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_USE_FINGERPRINT, OPSTR_USE_FINGERPRINT, "USE_FINGERPRINT")
+            .setPermission(Manifest.permission.USE_FINGERPRINT)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_BODY_SENSORS, OPSTR_BODY_SENSORS, "BODY_SENSORS")
+            .setPermission(Manifest.permission.BODY_SENSORS)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_READ_CELL_BROADCASTS, OPSTR_READ_CELL_BROADCASTS,
+                "READ_CELL_BROADCASTS").setPermission(Manifest.permission.READ_CELL_BROADCASTS)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).setDisableReset(true).build(),
+        new AppOpInfo.Builder(OP_MOCK_LOCATION, OPSTR_MOCK_LOCATION, "MOCK_LOCATION")
+            .setDefaultMode(AppOpsManager.MODE_ERRORED).build(),
+        new AppOpInfo.Builder(OP_READ_EXTERNAL_STORAGE, OPSTR_READ_EXTERNAL_STORAGE,
+                "READ_EXTERNAL_STORAGE").setPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_WRITE_EXTERNAL_STORAGE, OPSTR_WRITE_EXTERNAL_STORAGE,
+                "WRITE_EXTERNAL_STORAGE").setPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_TURN_SCREEN_ON, OPSTR_TURN_SCREEN_ON, "TURN_SCREEN_ON")
+            .setPermission(Manifest.permission.TURN_SCREEN_ON)
+            .setDefaultMode(AppOpsManager.MODE_ERRORED).build(),
+        new AppOpInfo.Builder(OP_GET_ACCOUNTS, OPSTR_GET_ACCOUNTS, "GET_ACCOUNTS")
+            .setPermission(Manifest.permission.GET_ACCOUNTS)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_RUN_IN_BACKGROUND, OPSTR_RUN_IN_BACKGROUND, "RUN_IN_BACKGROUND")
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED)
+            .build(),
+        new AppOpInfo.Builder(OP_AUDIO_ACCESSIBILITY_VOLUME, OPSTR_AUDIO_ACCESSIBILITY_VOLUME,
+                "AUDIO_ACCESSIBILITY_VOLUME")
+            .setRestriction(UserManager.DISALLOW_ADJUST_VOLUME)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_READ_PHONE_NUMBERS, OPSTR_READ_PHONE_NUMBERS, "READ_PHONE_NUMBERS")
+            .setPermission(Manifest.permission.READ_PHONE_NUMBERS)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_REQUEST_INSTALL_PACKAGES, OPSTR_REQUEST_INSTALL_PACKAGES,
+                "REQUEST_INSTALL_PACKAGES").setSwitchCode(OP_REQUEST_INSTALL_PACKAGES)
+            .setPermission(Manifest.permission.REQUEST_INSTALL_PACKAGES).build(),
+        new AppOpInfo.Builder(OP_PICTURE_IN_PICTURE, OPSTR_PICTURE_IN_PICTURE, "PICTURE_IN_PICTURE")
+            .setSwitchCode(OP_PICTURE_IN_PICTURE).setDefaultMode(AppOpsManager.MODE_ALLOWED)
+            .build(),
+        new AppOpInfo.Builder(OP_INSTANT_APP_START_FOREGROUND, OPSTR_INSTANT_APP_START_FOREGROUND,
+                "INSTANT_APP_START_FOREGROUND")
+            .setPermission(Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE).build(),
+        new AppOpInfo.Builder(OP_ANSWER_PHONE_CALLS, OPSTR_ANSWER_PHONE_CALLS, "ANSWER_PHONE_CALLS")
+            .setSwitchCode(OP_ANSWER_PHONE_CALLS)
+            .setPermission(Manifest.permission.ANSWER_PHONE_CALLS)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_RUN_ANY_IN_BACKGROUND, OPSTR_RUN_ANY_IN_BACKGROUND,
+                "RUN_ANY_IN_BACKGROUND")
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_CHANGE_WIFI_STATE, OPSTR_CHANGE_WIFI_STATE, "CHANGE_WIFI_STATE")
+            .setSwitchCode(OP_CHANGE_WIFI_STATE)
+            .setPermission(Manifest.permission.CHANGE_WIFI_STATE)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_REQUEST_DELETE_PACKAGES, OPSTR_REQUEST_DELETE_PACKAGES,
+                "REQUEST_DELETE_PACKAGES")
+            .setPermission(Manifest.permission.REQUEST_DELETE_PACKAGES)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_BIND_ACCESSIBILITY_SERVICE, OPSTR_BIND_ACCESSIBILITY_SERVICE,
+                "BIND_ACCESSIBILITY_SERVICE")
+            .setPermission(Manifest.permission.BIND_ACCESSIBILITY_SERVICE)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_ACCEPT_HANDOVER, OPSTR_ACCEPT_HANDOVER, "ACCEPT_HANDOVER")
+            .setSwitchCode(OP_ACCEPT_HANDOVER)
+            .setPermission(Manifest.permission.ACCEPT_HANDOVER)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_MANAGE_IPSEC_TUNNELS, OPSTR_MANAGE_IPSEC_TUNNELS,
+                "MANAGE_IPSEC_TUNNELS")
+            .setPermission(Manifest.permission.MANAGE_IPSEC_TUNNELS)
+            .setDefaultMode(AppOpsManager.MODE_ERRORED).build(),
+        new AppOpInfo.Builder(OP_START_FOREGROUND, OPSTR_START_FOREGROUND, "START_FOREGROUND")
+            .setPermission(Manifest.permission.FOREGROUND_SERVICE)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_BLUETOOTH_SCAN, OPSTR_BLUETOOTH_SCAN, "BLUETOOTH_SCAN")
+            .setPermission(Manifest.permission.BLUETOOTH_SCAN)
+            .setAllowSystemRestrictionBypass(new RestrictionBypass(false, true, false))
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_USE_BIOMETRIC, OPSTR_USE_BIOMETRIC, "USE_BIOMETRIC")
+            .setPermission(Manifest.permission.USE_BIOMETRIC)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_ACTIVITY_RECOGNITION, OPSTR_ACTIVITY_RECOGNITION,
+                "ACTIVITY_RECOGNITION")
+            .setPermission(Manifest.permission.ACTIVITY_RECOGNITION)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_SMS_FINANCIAL_TRANSACTIONS, OPSTR_SMS_FINANCIAL_TRANSACTIONS,
+                "SMS_FINANCIAL_TRANSACTIONS")
+            .setPermission(Manifest.permission.SMS_FINANCIAL_TRANSACTIONS)
+            .setRestriction(UserManager.DISALLOW_SMS).build(),
+        new AppOpInfo.Builder(OP_READ_MEDIA_AUDIO, OPSTR_READ_MEDIA_AUDIO, "READ_MEDIA_AUDIO")
+            .setPermission(Manifest.permission.READ_MEDIA_AUDIO)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_WRITE_MEDIA_AUDIO, OPSTR_WRITE_MEDIA_AUDIO, "WRITE_MEDIA_AUDIO")
+            .setDefaultMode(AppOpsManager.MODE_ERRORED).build(),
+        new AppOpInfo.Builder(OP_READ_MEDIA_VIDEO, OPSTR_READ_MEDIA_VIDEO, "READ_MEDIA_VIDEO")
+            .setPermission(Manifest.permission.READ_MEDIA_VIDEO)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_WRITE_MEDIA_VIDEO, OPSTR_WRITE_MEDIA_VIDEO, "WRITE_MEDIA_VIDEO")
+            .setDefaultMode(AppOpsManager.MODE_ERRORED).setDisableReset(true).build(),
+        new AppOpInfo.Builder(OP_READ_MEDIA_IMAGES, OPSTR_READ_MEDIA_IMAGES, "READ_MEDIA_IMAGES")
+            .setPermission(Manifest.permission.READ_MEDIA_IMAGES)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_WRITE_MEDIA_IMAGES, OPSTR_WRITE_MEDIA_IMAGES, "WRITE_MEDIA_IMAGES")
+            .setDefaultMode(AppOpsManager.MODE_ERRORED).setDisableReset(true).build(),
+        new AppOpInfo.Builder(OP_LEGACY_STORAGE, OPSTR_LEGACY_STORAGE, "LEGACY_STORAGE")
+            .setDisableReset(true).build(),
+        new AppOpInfo.Builder(OP_ACCESS_ACCESSIBILITY, OPSTR_ACCESS_ACCESSIBILITY,
+                "ACCESS_ACCESSIBILITY").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_READ_DEVICE_IDENTIFIERS, OPSTR_READ_DEVICE_IDENTIFIERS,
+                "READ_DEVICE_IDENTIFIERS").setDefaultMode(AppOpsManager.MODE_ERRORED).build(),
+        new AppOpInfo.Builder(OP_ACCESS_MEDIA_LOCATION, OPSTR_ACCESS_MEDIA_LOCATION,
+                "ACCESS_MEDIA_LOCATION").setPermission(Manifest.permission.ACCESS_MEDIA_LOCATION)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_QUERY_ALL_PACKAGES, OPSTR_QUERY_ALL_PACKAGES, "QUERY_ALL_PACKAGES")
+            .build(),
+        new AppOpInfo.Builder(OP_MANAGE_EXTERNAL_STORAGE, OPSTR_MANAGE_EXTERNAL_STORAGE,
+                "MANAGE_EXTERNAL_STORAGE")
+            .setPermission(Manifest.permission.MANAGE_EXTERNAL_STORAGE).build(),
+        new AppOpInfo.Builder(OP_INTERACT_ACROSS_PROFILES, OPSTR_INTERACT_ACROSS_PROFILES,
+                "INTERACT_ACROSS_PROFILES")
+            .setPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES).build(),
+        new AppOpInfo.Builder(OP_ACTIVATE_PLATFORM_VPN, OPSTR_ACTIVATE_PLATFORM_VPN,
+                "ACTIVATE_PLATFORM_VPN").setDefaultMode(AppOpsManager.MODE_IGNORED).build(),
+        new AppOpInfo.Builder(OP_LOADER_USAGE_STATS, OPSTR_LOADER_USAGE_STATS, "LOADER_USAGE_STATS")
+            .setPermission(android.Manifest.permission.LOADER_USAGE_STATS).build(),
+        new AppOpInfo.Builder(OP_NONE, "", "").setDefaultMode(AppOpsManager.MODE_IGNORED).build(),
+        new AppOpInfo.Builder(OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
+                OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, "AUTO_REVOKE_PERMISSIONS_IF_UNUSED")
+            .build(),
+        new AppOpInfo.Builder(OP_AUTO_REVOKE_MANAGED_BY_INSTALLER,
+                OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER, "AUTO_REVOKE_MANAGED_BY_INSTALLER")
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_NO_ISOLATED_STORAGE, OPSTR_NO_ISOLATED_STORAGE,
+                "NO_ISOLATED_STORAGE").setDefaultMode(AppOpsManager.MODE_ERRORED)
+            .setDisableReset(true).build(),
+        new AppOpInfo.Builder(OP_PHONE_CALL_MICROPHONE, OPSTR_PHONE_CALL_MICROPHONE,
+                "PHONE_CALL_MICROPHONE").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_PHONE_CALL_CAMERA, OPSTR_PHONE_CALL_CAMERA, "PHONE_CALL_CAMERA")
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_RECORD_AUDIO_HOTWORD, OPSTR_RECORD_AUDIO_HOTWORD,
+                "RECORD_AUDIO_HOTWORD").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_MANAGE_ONGOING_CALLS, OPSTR_MANAGE_ONGOING_CALLS,
+                "MANAGE_ONGOING_CALLS").setPermission(Manifest.permission.MANAGE_ONGOING_CALLS)
+            .setDisableReset(true).build(),
+        new AppOpInfo.Builder(OP_MANAGE_CREDENTIALS, OPSTR_MANAGE_CREDENTIALS, "MANAGE_CREDENTIALS")
+            .build(),
+        new AppOpInfo.Builder(OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
+                OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, "USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER")
+            .setPermission(Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER)
+            .setDisableReset(true).build(),
+        new AppOpInfo.Builder(OP_RECORD_AUDIO_OUTPUT, OPSTR_RECORD_AUDIO_OUTPUT,
+                "RECORD_AUDIO_OUTPUT").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_SCHEDULE_EXACT_ALARM, OPSTR_SCHEDULE_EXACT_ALARM,
+                "SCHEDULE_EXACT_ALARM").setPermission(Manifest.permission.SCHEDULE_EXACT_ALARM)
+            .build(),
+        new AppOpInfo.Builder(OP_FINE_LOCATION_SOURCE, OPSTR_FINE_LOCATION_SOURCE,
+                "FINE_LOCATION_SOURCE").setSwitchCode(OP_FINE_LOCATION)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_COARSE_LOCATION_SOURCE, OPSTR_COARSE_LOCATION_SOURCE,
+                "COARSE_LOCATION_SOURCE").setSwitchCode(OP_COARSE_LOCATION)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_MANAGE_MEDIA, OPSTR_MANAGE_MEDIA, "MANAGE_MEDIA")
+            .setPermission(Manifest.permission.MANAGE_MEDIA).build(),
+        new AppOpInfo.Builder(OP_BLUETOOTH_CONNECT, OPSTR_BLUETOOTH_CONNECT, "BLUETOOTH_CONNECT")
+            .setPermission(Manifest.permission.BLUETOOTH_CONNECT)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_UWB_RANGING, OPSTR_UWB_RANGING, "UWB_RANGING")
+            .setPermission(Manifest.permission.UWB_RANGING)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_ACTIVITY_RECOGNITION_SOURCE, OPSTR_ACTIVITY_RECOGNITION_SOURCE,
+                "ACTIVITY_RECOGNITION_SOURCE")
+            .setSwitchCode(OP_ACTIVITY_RECOGNITION).setDefaultMode(AppOpsManager.MODE_ALLOWED)
+            .build(),
+        new AppOpInfo.Builder(OP_BLUETOOTH_ADVERTISE, OPSTR_BLUETOOTH_ADVERTISE,
+                "BLUETOOTH_ADVERTISE").setPermission(Manifest.permission.BLUETOOTH_ADVERTISE)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_RECORD_INCOMING_PHONE_AUDIO, OPSTR_RECORD_INCOMING_PHONE_AUDIO,
+                "RECORD_INCOMING_PHONE_AUDIO").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_NEARBY_WIFI_DEVICES, OPSTR_NEARBY_WIFI_DEVICES,
+                "NEARBY_WIFI_DEVICES").setPermission(Manifest.permission.NEARBY_WIFI_DEVICES)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_ESTABLISH_VPN_SERVICE, OPSTR_ESTABLISH_VPN_SERVICE,
+                "ESTABLISH_VPN_SERVICE").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_ESTABLISH_VPN_MANAGER, OPSTR_ESTABLISH_VPN_MANAGER,
+                "ESTABLISH_VPN_MANAGER").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_ACCESS_RESTRICTED_SETTINGS, OPSTR_ACCESS_RESTRICTED_SETTINGS,
+                "ACCESS_RESTRICTED_SETTINGS").setDefaultMode(AppOpsManager.MODE_ALLOWED)
+            .setDisableReset(true).setRestrictRead(true).build(),
+        new AppOpInfo.Builder(OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO,
+                "RECEIVE_SOUNDTRIGGER_AUDIO").setDefaultMode(AppOpsManager.MODE_ALLOWED).build()
     };
 
     /**
@@ -3101,46 +2316,18 @@
     private static final @ShouldCollectNoteOp byte[] sAppOpsToNote = new byte[_NUM_OP];
 
     static {
-        if (sOpToSwitch.length != _NUM_OP) {
-            throw new IllegalStateException("sOpToSwitch length " + sOpToSwitch.length
+        if (sAppOpInfos.length != _NUM_OP) {
+            throw new IllegalStateException("mAppOpInfos length " + sAppOpInfos.length
                     + " should be " + _NUM_OP);
         }
-        if (sOpToString.length != _NUM_OP) {
-            throw new IllegalStateException("sOpToString length " + sOpToString.length
-                    + " should be " + _NUM_OP);
-        }
-        if (sOpNames.length != _NUM_OP) {
-            throw new IllegalStateException("sOpNames length " + sOpNames.length
-                    + " should be " + _NUM_OP);
-        }
-        if (sOpPerms.length != _NUM_OP) {
-            throw new IllegalStateException("sOpPerms length " + sOpPerms.length
-                    + " should be " + _NUM_OP);
-        }
-        if (sOpDefaultMode.length != _NUM_OP) {
-            throw new IllegalStateException("sOpDefaultMode length " + sOpDefaultMode.length
-                    + " should be " + _NUM_OP);
-        }
-        if (sOpDisableReset.length != _NUM_OP) {
-            throw new IllegalStateException("sOpDisableReset length " + sOpDisableReset.length
-                    + " should be " + _NUM_OP);
-        }
-        if (sOpRestrictions.length != _NUM_OP) {
-            throw new IllegalStateException("sOpRestrictions length " + sOpRestrictions.length
-                    + " should be " + _NUM_OP);
-        }
-        if (sOpAllowSystemRestrictionBypass.length != _NUM_OP) {
-            throw new IllegalStateException("sOpAllowSYstemRestrictionsBypass length "
-                    + sOpRestrictions.length + " should be " + _NUM_OP);
-        }
         for (int i=0; i<_NUM_OP; i++) {
-            if (sOpToString[i] != null) {
-                sOpStrToOp.put(sOpToString[i], i);
+            if (sAppOpInfos[i].name != null) {
+                sOpStrToOp.put(sAppOpInfos[i].name, i);
             }
         }
         for (int op : RUNTIME_AND_APPOP_PERMISSIONS_OPS) {
-            if (sOpPerms[op] != null) {
-                sPermToOp.put(sOpPerms[op], op);
+            if (sAppOpInfos[op].permission != null) {
+                sPermToOp.put(sAppOpInfos[op].permission, op);
             }
         }
 
@@ -3170,7 +2357,7 @@
      */
     @UnsupportedAppUsage
     public static int opToSwitch(int op) {
-        return sOpToSwitch[op];
+        return sAppOpInfos[op].switchCode;
     }
 
     /**
@@ -3180,7 +2367,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static String opToName(int op) {
         if (op == OP_NONE) return "NONE";
-        return op < sOpNames.length ? sOpNames[op] : ("Unknown(" + op + ")");
+        return op < sAppOpInfos.length ? sAppOpInfos[op].simpleName : ("Unknown(" + op + ")");
     }
 
     /**
@@ -3189,15 +2376,15 @@
      * @hide
      */
     public static @NonNull String opToPublicName(int op) {
-        return sOpToString[op];
+        return sAppOpInfos[op].name;
     }
 
     /**
      * @hide
      */
     public static int strDebugOpToOp(String op) {
-        for (int i=0; i<sOpNames.length; i++) {
-            if (sOpNames[i].equals(op)) {
+        for (int i = 0; i < sAppOpInfos.length; i++) {
+            if (sAppOpInfos[i].simpleName.equals(op)) {
                 return i;
             }
         }
@@ -3211,7 +2398,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @TestApi
     public static String opToPermission(int op) {
-        return sOpPerms[op];
+        return sAppOpInfos[op].permission;
     }
 
     /**
@@ -3232,7 +2419,7 @@
      * @hide
      */
     public static String opToRestriction(int op) {
-        return sOpRestrictions[op];
+        return sAppOpInfos[op].restriction;
     }
 
     /**
@@ -3254,7 +2441,7 @@
      * @hide
      */
     public static RestrictionBypass opAllowSystemBypassRestriction(int op) {
-        return sOpAllowSystemRestrictionBypass[op];
+        return sAppOpInfos[op].allowSystemRestrictionBypass;
     }
 
     /**
@@ -3262,7 +2449,7 @@
      * @hide
      */
     public static @Mode int opToDefaultMode(int op) {
-        return sOpDefaultMode[op];
+        return sAppOpInfos[op].defaultMode;
     }
 
     /**
@@ -3295,7 +2482,7 @@
      * @hide
      */
     public static boolean opRestrictsRead(int op) {
-        return sOpRestrictRead[op];
+        return sAppOpInfos[op].restrictRead;
     }
 
     /**
@@ -3303,7 +2490,7 @@
      * @hide
      */
     public static boolean opAllowsReset(int op) {
-        return !sOpDisableReset[op];
+        return !sAppOpInfos[op].disableReset;
     }
 
     /**
@@ -4436,7 +3623,7 @@
          * @return This entry's op string name, such as {@link #OPSTR_COARSE_LOCATION}.
          */
         public @NonNull String getOpStr() {
-            return sOpToString[mOp];
+            return sAppOpInfos[mOp].name;
         }
 
         /**
@@ -6553,7 +5740,7 @@
             if (mHistoricalOps == null) {
                 mHistoricalOps = new ArrayMap<>();
             }
-            final String opStr = sOpToString[opCode];
+            final String opStr = sAppOpInfos[opCode].name;
             HistoricalOp op = mHistoricalOps.get(opStr);
             if (op == null) {
                 op = new HistoricalOp(opCode);
@@ -6898,7 +6085,7 @@
          * @return The op name.
          */
         public @NonNull String getOpName() {
-            return sOpToString[mOp];
+            return sAppOpInfos[mOp].name;
         }
 
         /** @hide */
@@ -7912,7 +7099,7 @@
         if (opCode == null) {
             return null;
         }
-        return sOpToString[opCode];
+        return sAppOpInfos[opCode].name;
     }
 
     /**
@@ -8007,8 +7194,8 @@
                         if (callback instanceof OnOpChangedInternalListener) {
                             ((OnOpChangedInternalListener)callback).onOpChanged(op, packageName);
                         }
-                        if (sOpToString[op] != null) {
-                            callback.onOpChanged(sOpToString[op], packageName);
+                        if (sAppOpInfos[op].name != null) {
+                            callback.onOpChanged(sAppOpInfos[op].name, packageName);
                         }
                     }
                 };
@@ -8096,8 +7283,8 @@
                             ((OnOpActiveChangedInternalListener) callback).onOpActiveChanged(op,
                                     uid, packageName, active);
                         }
-                        if (sOpToString[op] != null) {
-                            callback.onOpActiveChanged(sOpToString[op], uid, packageName,
+                        if (sAppOpInfos[op].name != null) {
+                            callback.onOpActiveChanged(sAppOpInfos[op].name, uid, packageName,
                                     attributionTag, active, attributionFlags, attributionChainId);
                         }
                     });
@@ -8273,7 +7460,8 @@
     }
 
     private String buildSecurityExceptionMsg(int op, int uid, String packageName) {
-        return packageName + " from uid " + uid + " not allowed to perform " + sOpNames[op];
+        return packageName + " from uid " + uid + " not allowed to perform " +
+            sAppOpInfos[op].simpleName;
     }
 
     /**
@@ -8578,9 +7766,9 @@
     public int noteProxyOp(int op, @Nullable String proxiedPackageName, int proxiedUid,
             @Nullable String proxiedAttributionTag, @Nullable String message) {
         return noteProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
-                new AttributionSource(proxiedUid, /*pid*/ -1, proxiedPackageName,
-                        proxiedAttributionTag, mContext.getAttributionSource().getToken())),
-                        message, /*skipProxyOperation*/ false);
+                new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
+                        mContext.getAttributionSource().getToken())), message,
+                        /*skipProxyOperation*/ false);
     }
 
     /**
@@ -8633,7 +7821,7 @@
                     + attributionSource.getUid() + " or calling package "
                     + attributionSource.getNextPackageName() + " from uid "
                     + attributionSource.getNextUid() + " not allowed to perform "
-                    + sOpNames[op]);
+                    + sAppOpInfos[op].simpleName);
         }
         return mode;
     }
@@ -8664,7 +7852,7 @@
     public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName,
             int proxiedUid, @Nullable String proxiedAttributionTag, @Nullable String message) {
         return noteProxyOpNoThrow(strOpToOp(op), new AttributionSource(
-                mContext.getAttributionSource(), new AttributionSource(proxiedUid, /*pid*/ -1,
+                mContext.getAttributionSource(), new AttributionSource(proxiedUid,
                         proxiedPackageName, proxiedAttributionTag, mContext.getAttributionSource()
                         .getToken())), message,/*skipProxyOperation*/ false);
     }
@@ -9076,9 +8264,9 @@
     public int startProxyOp(@NonNull String op, int proxiedUid, @NonNull String proxiedPackageName,
             @Nullable String proxiedAttributionTag, @Nullable String message) {
         return startProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
-                new AttributionSource(proxiedUid, /*pid*/ -1, proxiedPackageName,
-                        proxiedAttributionTag, mContext.getAttributionSource().getToken())),
-                        message, /*skipProxyOperation*/ false);
+                new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
+                        mContext.getAttributionSource().getToken())), message,
+                        /*skipProxyOperation*/ false);
     }
 
     /**
@@ -9124,7 +8312,7 @@
             @Nullable String message) {
         return startProxyOpNoThrow(AppOpsManager.strOpToOp(op), new AttributionSource(
                 mContext.getAttributionSource(), new AttributionSource(proxiedUid,
-                        /*pid*/ -1, proxiedPackageName, proxiedAttributionTag,
+                        proxiedPackageName, proxiedAttributionTag,
                         mContext.getAttributionSource().getToken())), message,
                         /*skipProxyOperation*/ false);
     }
@@ -9270,9 +8458,8 @@
     public void finishProxyOp(@NonNull String op, int proxiedUid,
             @NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag) {
         finishProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
-                new AttributionSource(proxiedUid, /*pid*/ -1, proxiedPackageName,
-                        proxiedAttributionTag, mContext.getAttributionSource().getToken())),
-                        /*skipProxyOperation*/ false);
+                new AttributionSource(proxiedUid, proxiedPackageName,  proxiedAttributionTag,
+                        mContext.getAttributionSource().getToken())), /*skipProxyOperation*/ false);
     }
 
     /**
@@ -10085,10 +9272,13 @@
      */
     @SystemApi
     public static String[] getOpStrs() {
-        return Arrays.copyOf(sOpToString, sOpToString.length);
+        String[] opStrs = new String[sAppOpInfos.length];
+        for(int i = 0; i < sAppOpInfos.length; i++) {
+            opStrs[i] = sAppOpInfos[i].name;
+        }
+        return opStrs;
     }
 
-
     /**
      * @return number of App ops
      * @hide
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 389da2d..f322ca9 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -96,8 +96,8 @@
 
     /** Pause the activity. */
     public abstract void handlePauseActivity(@NonNull ActivityClientRecord r, boolean finished,
-            boolean userLeaving, int configChanges, PendingTransactionActions pendingActions,
-            String reason);
+            boolean userLeaving, int configChanges, boolean autoEnteringPip,
+            PendingTransactionActions pendingActions, String reason);
 
     /**
      * Resume the activity.
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index c1a2183..4c7bc6d 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3227,9 +3227,7 @@
             @Nullable AttributionSource nextAttributionSource,
             @Nullable Set<String> renouncedPermissions) {
         AttributionSource attributionSource = new AttributionSource(Process.myUid(),
-                Process.myPid(), mOpPackageName, attributionTag,
-                (renouncedPermissions != null) ? renouncedPermissions.toArray(new String[0]) : null,
-                nextAttributionSource);
+                mOpPackageName, attributionTag, renouncedPermissions, nextAttributionSource);
         // If we want to access protected data on behalf of another app we need to
         // tell the OS that we opt in to participate in the attribution chain.
         if (nextAttributionSource != null) {
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 0e89b25..b9ad595 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -124,8 +124,13 @@
     /**
      * The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
      * limit.
+     * @hide
      */
-    private static final int MAX_TEXT_LENGTH = 1000;
+    public static final int MAX_TEXT_LENGTH = 1000;
+    /**
+     * @hide
+     */
+    public static final int MAX_VIBRATION_LENGTH = 1000;
 
     private static final String TAG_CHANNEL = "channel";
     private static final String ATT_NAME = "name";
@@ -283,17 +288,17 @@
      */
     protected NotificationChannel(Parcel in) {
         if (in.readByte() != 0) {
-            mId = in.readString();
+            mId = getTrimmedString(in.readString());
         } else {
             mId = null;
         }
         if (in.readByte() != 0) {
-            mName = in.readString();
+            mName = getTrimmedString(in.readString());
         } else {
             mName = null;
         }
         if (in.readByte() != 0) {
-            mDesc = in.readString();
+            mDesc = getTrimmedString(in.readString());
         } else {
             mDesc = null;
         }
@@ -302,18 +307,22 @@
         mLockscreenVisibility = in.readInt();
         if (in.readByte() != 0) {
             mSound = Uri.CREATOR.createFromParcel(in);
+            mSound = Uri.parse(getTrimmedString(mSound.toString()));
         } else {
             mSound = null;
         }
         mLights = in.readByte() != 0;
         mVibration = in.createLongArray();
+        if (mVibration != null && mVibration.length > MAX_VIBRATION_LENGTH) {
+            mVibration = Arrays.copyOf(mVibration, MAX_VIBRATION_LENGTH);
+        }
         mUserLockedFields = in.readInt();
         mFgServiceShown = in.readByte() != 0;
         mVibrationEnabled = in.readByte() != 0;
         mShowBadge = in.readByte() != 0;
         mDeleted = in.readByte() != 0;
         if (in.readByte() != 0) {
-            mGroup = in.readString();
+            mGroup = getTrimmedString(in.readString());
         } else {
             mGroup = null;
         }
@@ -322,8 +331,8 @@
         mBlockableSystem = in.readBoolean();
         mAllowBubbles = in.readInt();
         mOriginalImportance = in.readInt();
-        mParentId = in.readString();
-        mConversationId = in.readString();
+        mParentId = getTrimmedString(in.readString());
+        mConversationId = getTrimmedString(in.readString());
         mDemoted = in.readBoolean();
         mImportantConvo = in.readBoolean();
         mDeletedTime = in.readLong();
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index 1769993e..2b245aa 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -20,7 +20,6 @@
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Intent;
-import android.content.pm.ParceledListSlice;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -44,8 +43,9 @@
     /**
      * The maximum length for text fields in a NotificationChannelGroup. Fields will be truncated at
      * this limit.
+     * @hide
      */
-    private static final int MAX_TEXT_LENGTH = 1000;
+    public static final int MAX_TEXT_LENGTH = 1000;
 
     private static final String TAG_GROUP = "channelGroup";
     private static final String ATT_NAME = "name";
@@ -67,7 +67,7 @@
     private CharSequence mName;
     private String mDescription;
     private boolean mBlocked;
-    private ParceledListSlice<NotificationChannel> mChannels;
+    private List<NotificationChannel> mChannels = new ArrayList<>();
     // Bitwise representation of fields that have been changed by the user
     private int mUserLockedFields;
 
@@ -91,18 +91,18 @@
      */
     protected NotificationChannelGroup(Parcel in) {
         if (in.readByte() != 0) {
-            mId = in.readString();
+            mId = getTrimmedString(in.readString());
         } else {
             mId = null;
         }
         mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mName = getTrimmedString(mName.toString());
         if (in.readByte() != 0) {
-            mDescription = in.readString();
+            mDescription = getTrimmedString(in.readString());
         } else {
             mDescription = null;
         }
-        mChannels = in.readParcelable(
-                NotificationChannelGroup.class.getClassLoader(), ParceledListSlice.class);
+        in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class);
         mBlocked = in.readBoolean();
         mUserLockedFields = in.readInt();
     }
@@ -122,14 +122,14 @@
         } else {
             dest.writeByte((byte) 0);
         }
-        TextUtils.writeToParcel(mName, dest, flags);
+        TextUtils.writeToParcel(mName.toString(), dest, flags);
         if (mDescription != null) {
             dest.writeByte((byte) 1);
             dest.writeString(mDescription);
         } else {
             dest.writeByte((byte) 0);
         }
-        dest.writeParcelable(mChannels, flags);
+        dest.writeParcelableList(mChannels, flags);
         dest.writeBoolean(mBlocked);
         dest.writeInt(mUserLockedFields);
     }
@@ -159,7 +159,7 @@
      * Returns the list of channels that belong to this group
      */
     public List<NotificationChannel> getChannels() {
-        return mChannels == null ? new ArrayList<>() : mChannels.getList();
+        return mChannels;
     }
 
     /**
@@ -193,8 +193,15 @@
     /**
      * @hide
      */
+    public void addChannel(NotificationChannel channel) {
+        mChannels.add(channel);
+    }
+
+    /**
+     * @hide
+     */
     public void setChannels(List<NotificationChannel> channels) {
-        mChannels = new ParceledListSlice<>(channels);
+        mChannels = channels;
     }
 
     /**
@@ -329,7 +336,7 @@
         proto.write(NotificationChannelGroupProto.NAME, mName.toString());
         proto.write(NotificationChannelGroupProto.DESCRIPTION, mDescription);
         proto.write(NotificationChannelGroupProto.IS_BLOCKED, mBlocked);
-        for (NotificationChannel channel : mChannels.getList()) {
+        for (NotificationChannel channel : mChannels) {
             channel.dumpDebug(proto, NotificationChannelGroupProto.CHANNELS);
         }
         proto.end(token);
diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java
index 813e0f9..965e761 100644
--- a/core/java/android/app/servertransaction/PauseActivityItem.java
+++ b/core/java/android/app/servertransaction/PauseActivityItem.java
@@ -39,13 +39,14 @@
     private boolean mUserLeaving;
     private int mConfigChanges;
     private boolean mDontReport;
+    private boolean mAutoEnteringPip;
 
     @Override
     public void execute(ClientTransactionHandler client, ActivityClientRecord r,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
-        client.handlePauseActivity(r, mFinished, mUserLeaving, mConfigChanges, pendingActions,
-                "PAUSE_ACTIVITY_ITEM");
+        client.handlePauseActivity(r, mFinished, mUserLeaving, mConfigChanges, mAutoEnteringPip,
+                pendingActions, "PAUSE_ACTIVITY_ITEM");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -71,7 +72,7 @@
 
     /** Obtain an instance initialized with provided params. */
     public static PauseActivityItem obtain(boolean finished, boolean userLeaving, int configChanges,
-            boolean dontReport) {
+            boolean dontReport, boolean autoEnteringPip) {
         PauseActivityItem instance = ObjectPool.obtain(PauseActivityItem.class);
         if (instance == null) {
             instance = new PauseActivityItem();
@@ -80,6 +81,7 @@
         instance.mUserLeaving = userLeaving;
         instance.mConfigChanges = configChanges;
         instance.mDontReport = dontReport;
+        instance.mAutoEnteringPip = autoEnteringPip;
 
         return instance;
     }
@@ -94,6 +96,7 @@
         instance.mUserLeaving = false;
         instance.mConfigChanges = 0;
         instance.mDontReport = true;
+        instance.mAutoEnteringPip = false;
 
         return instance;
     }
@@ -105,6 +108,7 @@
         mUserLeaving = false;
         mConfigChanges = 0;
         mDontReport = false;
+        mAutoEnteringPip = false;
         ObjectPool.recycle(this);
     }
 
@@ -117,6 +121,7 @@
         dest.writeBoolean(mUserLeaving);
         dest.writeInt(mConfigChanges);
         dest.writeBoolean(mDontReport);
+        dest.writeBoolean(mAutoEnteringPip);
     }
 
     /** Read from Parcel. */
@@ -125,6 +130,7 @@
         mUserLeaving = in.readBoolean();
         mConfigChanges = in.readInt();
         mDontReport = in.readBoolean();
+        mAutoEnteringPip = in.readBoolean();
     }
 
     public static final @NonNull Creator<PauseActivityItem> CREATOR =
@@ -148,7 +154,8 @@
         }
         final PauseActivityItem other = (PauseActivityItem) o;
         return mFinished == other.mFinished && mUserLeaving == other.mUserLeaving
-                && mConfigChanges == other.mConfigChanges && mDontReport == other.mDontReport;
+                && mConfigChanges == other.mConfigChanges && mDontReport == other.mDontReport
+                && mAutoEnteringPip == other.mAutoEnteringPip;
     }
 
     @Override
@@ -158,12 +165,14 @@
         result = 31 * result + (mUserLeaving ? 1 : 0);
         result = 31 * result + mConfigChanges;
         result = 31 * result + (mDontReport ? 1 : 0);
+        result = 31 * result + (mAutoEnteringPip ? 1 : 0);
         return result;
     }
 
     @Override
     public String toString() {
         return "PauseActivityItem{finished=" + mFinished + ",userLeaving=" + mUserLeaving
-                + ",configChanges=" + mConfigChanges + ",dontReport=" + mDontReport + "}";
+                + ",configChanges=" + mConfigChanges + ",dontReport=" + mDontReport
+                + ",autoEnteringPip=" + mAutoEnteringPip + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 25ff8a7..de1d38a 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -227,7 +227,8 @@
                     break;
                 case ON_PAUSE:
                     mTransactionHandler.handlePauseActivity(r, false /* finished */,
-                            false /* userLeaving */, 0 /* configChanges */, mPendingActions,
+                            false /* userLeaving */, 0 /* configChanges */,
+                            false /* autoEnteringPip */, mPendingActions,
                             "LIFECYCLER_PAUSE_ACTIVITY");
                     break;
                 case ON_STOP:
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 8ffc6a2..c1a3c19 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -1008,6 +1008,22 @@
         }
     }
 
+    /**
+     * Checks whether the calling companion application is currently bound.
+     *
+     * @return true if application is bound, false otherwise
+     * @hide
+     */
+    @UserHandleAware
+    public boolean isCompanionApplicationBound() {
+        try {
+            return mService.isCompanionApplicationBound(
+                    mContext.getOpPackageName(), mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private boolean checkFeaturePresent() {
         boolean featurePresent = mService != null;
         if (!featurePresent && DEBUG) {
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 6e6e187..17e3132 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -80,4 +80,6 @@
     void attachSystemDataTransport(String packageName, int userId, int associationId, in ParcelFileDescriptor fd);
 
     void detachSystemDataTransport(String packageName, int userId, int associationId);
+
+    boolean isCompanionApplicationBound(String packageName, int userId);
 }
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 272e235..3f2fa21 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -100,28 +100,22 @@
     @TestApi
     public AttributionSource(int uid, @Nullable String packageName,
             @Nullable String attributionTag) {
-        this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken);
-    }
-
-    /** @hide */
-    public AttributionSource(int uid, int pid, @Nullable String packageName,
-            @Nullable String attributionTag) {
-        this(uid, pid, packageName, attributionTag, sDefaultToken);
+        this(uid, packageName, attributionTag, sDefaultToken);
     }
 
     /** @hide */
     @TestApi
     public AttributionSource(int uid, @Nullable String packageName,
             @Nullable String attributionTag, @NonNull IBinder token) {
-        this(uid, Process.INVALID_PID, packageName, attributionTag, token,
-                /*renouncedPermissions*/ null, /*next*/ null);
+        this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null,
+                /*next*/ null);
     }
 
     /** @hide */
-    public AttributionSource(int uid, int pid, @Nullable String packageName,
-            @Nullable String attributionTag, @NonNull IBinder token) {
-        this(uid, pid, packageName, attributionTag, token, /*renouncedPermissions*/ null,
-                /*next*/ null);
+    public AttributionSource(int uid, @Nullable String packageName,
+            @Nullable String attributionTag, @NonNull IBinder token,
+            @Nullable AttributionSource next) {
+        this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null, next);
     }
 
     /** @hide */
@@ -129,33 +123,26 @@
     public AttributionSource(int uid, @Nullable String packageName,
             @Nullable String attributionTag, @Nullable Set<String> renouncedPermissions,
             @Nullable AttributionSource next) {
-        this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken,
-                (renouncedPermissions != null)
-                ? renouncedPermissions.toArray(new String[0]) : null, /*next*/ next);
+        this(uid, packageName, attributionTag, (renouncedPermissions != null)
+                ? renouncedPermissions.toArray(new String[0]) : null, next);
     }
 
     /** @hide */
     public AttributionSource(@NonNull AttributionSource current, @Nullable AttributionSource next) {
-        this(current.getUid(), current.getPid(), current.getPackageName(),
-                current.getAttributionTag(), current.getToken(),
-                current.mAttributionSourceState.renouncedPermissions, next);
+        this(current.getUid(), current.getPackageName(), current.getAttributionTag(),
+                current.getToken(), current.mAttributionSourceState.renouncedPermissions, next);
     }
 
-    /** @hide */
-    public AttributionSource(int uid, int pid, @Nullable String packageName,
-            @Nullable String attributionTag, @Nullable String[] renouncedPermissions,
-            @Nullable AttributionSource next) {
-        this(uid, pid, packageName, attributionTag, sDefaultToken, renouncedPermissions, next);
+    AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
+            @Nullable String[] renouncedPermissions, @Nullable AttributionSource next) {
+        this(uid, packageName, attributionTag, sDefaultToken, renouncedPermissions, next);
     }
 
-    /** @hide */
-    public AttributionSource(int uid, int pid, @Nullable String packageName,
-            @Nullable String attributionTag, @NonNull IBinder token,
-            @Nullable String[] renouncedPermissions,
+    AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
+            @NonNull IBinder token, @Nullable String[] renouncedPermissions,
             @Nullable AttributionSource next) {
         mAttributionSourceState = new AttributionSourceState();
         mAttributionSourceState.uid = uid;
-        mAttributionSourceState.pid = pid;
         mAttributionSourceState.token = token;
         mAttributionSourceState.packageName = packageName;
         mAttributionSourceState.attributionTag = attributionTag;
@@ -169,17 +156,7 @@
 
         // Since we just unpacked this object as part of it transiting a Binder
         // call, this is the perfect time to enforce that its UID and PID can be trusted
-        enforceCallingUid();
-
-        // If this object is being constructed as part of a oneway Binder call, getCallingPid will
-        // return 0 instead of the true PID. In that case, invalidate the PID by setting it to
-        // INVALID_PID (-1).
-        final int callingPid = Binder.getCallingPid();
-        if (callingPid == 0) {
-            mAttributionSourceState.pid = Process.INVALID_PID;
-        }
-
-        enforceCallingPid();
+        enforceCallingUidAndPid();
     }
 
     /** @hide */
@@ -189,19 +166,19 @@
 
     /** @hide */
     public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) {
-        return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
-                getToken(), mAttributionSourceState.renouncedPermissions, next);
+        return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
+                mAttributionSourceState.renouncedPermissions, next);
     }
 
     /** @hide */
     public AttributionSource withPackageName(@Nullable String packageName) {
-        return new AttributionSource(getUid(), getPid(), packageName, getAttributionTag(),
-               getToken(), mAttributionSourceState.renouncedPermissions, getNext());
+        return new AttributionSource(getUid(), packageName, getAttributionTag(),
+                mAttributionSourceState.renouncedPermissions, getNext());
     }
 
     /** @hide */
     public AttributionSource withToken(@NonNull Binder token) {
-        return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
+        return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
                 token, mAttributionSourceState.renouncedPermissions, getNext());
     }
 
@@ -245,7 +222,6 @@
         }
         try {
             return new AttributionSource.Builder(uid)
-                .setPid(Process.myPid())
                 .setPackageName(AppGlobals.getPackageManager().getPackagesForUid(uid)[0])
                 .build();
         } catch (Exception ignored) {
@@ -283,6 +259,18 @@
     }
 
     /**
+     * If you are handling an IPC and you don't trust the caller you need to validate whether the
+     * attribution source is one for the calling app to prevent the caller to pass you a source from
+     * another app without including themselves in the attribution chain.
+     *
+     * @throws SecurityException if the attribution source cannot be trusted to be from the caller.
+     */
+    private void enforceCallingUidAndPid() {
+        enforceCallingUid();
+        enforceCallingPid();
+    }
+
+    /**
      * If you are handling an IPC and you don't trust the caller you need to validate
      * whether the attribution source is one for the calling app to prevent the caller
      * to pass you a source from another app without including themselves in the
@@ -318,10 +306,7 @@
     }
 
     /**
-     * Validate that the pid being claimed for the calling app is not spoofed.
-     *
-     * Note that the PID may be unavailable, for example if we're in a oneway Binder call. In this
-     * case, calling enforceCallingPid is guaranteed to fail. The caller should anticipate this.
+     * Validate that the pid being claimed for the calling app is not spoofed
      *
      * @throws SecurityException if the attribution source cannot be trusted to be from the caller.
      * @hide
@@ -329,12 +314,8 @@
     @TestApi
     public void enforceCallingPid() {
         if (!checkCallingPid()) {
-            if (Binder.getCallingPid() == 0) {
-                throw new SecurityException("Calling pid unavailable due to oneway Binder call.");
-            } else {
-                throw new SecurityException("Calling pid: " + Binder.getCallingPid()
-                        + " doesn't match source pid: " + mAttributionSourceState.pid);
-            }
+            throw new SecurityException("Calling pid: " + Binder.getCallingPid()
+                    + " doesn't match source pid: " + mAttributionSourceState.pid);
         }
     }
 
@@ -345,8 +326,7 @@
      */
     private boolean checkCallingPid() {
         final int callingPid = Binder.getCallingPid();
-        if (mAttributionSourceState.pid != Process.INVALID_PID
-                && callingPid != mAttributionSourceState.pid) {
+        if (mAttributionSourceState.pid != -1 && callingPid != mAttributionSourceState.pid) {
             return false;
         }
         return true;
@@ -463,13 +443,6 @@
     }
 
     /**
-     * The PID that is accessing the permission protected data.
-     */
-    public int getPid() {
-        return mAttributionSourceState.pid;
-    }
-
-    /**
      * The package that is accessing the permission protected data.
      */
     public @Nullable String getPackageName() {
@@ -577,7 +550,6 @@
                 throw new IllegalArgumentException("current AttributionSource can not be null");
             }
             mAttributionSourceState.uid = current.getUid();
-            mAttributionSourceState.pid = current.getPid();
             mAttributionSourceState.packageName = current.getPackageName();
             mAttributionSourceState.attributionTag = current.getAttributionTag();
             mAttributionSourceState.token = current.getToken();
@@ -586,24 +558,11 @@
         }
 
         /**
-         * The PID of the process that is accessing the permission protected data.
-         *
-         * If not called, pid will default to Process.INVALID_PID (-1). This indicates that the PID
-         * data is missing. Supplying a PID is not required, but recommended when accessible.
-         */
-        public @NonNull Builder setPid(int value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x2;
-            mAttributionSourceState.pid = value;
-            return this;
-        }
-
-        /**
          * The package that is accessing the permission protected data.
          */
         public @NonNull Builder setPackageName(@Nullable String value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x4;
+            mBuilderFieldsSet |= 0x2;
             mAttributionSourceState.packageName = value;
             return this;
         }
@@ -613,7 +572,7 @@
          */
         public @NonNull Builder setAttributionTag(@Nullable String value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x8;
+            mBuilderFieldsSet |= 0x4;
             mAttributionSourceState.attributionTag = value;
             return this;
         }
@@ -646,7 +605,7 @@
         @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
         public @NonNull Builder setRenouncedPermissions(@Nullable Set<String> value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x10;
+            mBuilderFieldsSet |= 0x8;
             mAttributionSourceState.renouncedPermissions = (value != null)
                     ? value.toArray(new String[0]) : null;
             return this;
@@ -657,7 +616,7 @@
          */
         public @NonNull Builder setNext(@Nullable AttributionSource value) {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x20;
+            mBuilderFieldsSet |= 0x10;
             mAttributionSourceState.next = (value != null) ? new AttributionSourceState[]
                     {value.mAttributionSourceState} : mAttributionSourceState.next;
             return this;
@@ -669,18 +628,15 @@
             mBuilderFieldsSet |= 0x40; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x2) == 0) {
-                mAttributionSourceState.pid = Process.INVALID_PID;
-            }
-            if ((mBuilderFieldsSet & 0x4) == 0) {
                 mAttributionSourceState.packageName = null;
             }
-            if ((mBuilderFieldsSet & 0x8) == 0) {
+            if ((mBuilderFieldsSet & 0x4) == 0) {
                 mAttributionSourceState.attributionTag = null;
             }
-            if ((mBuilderFieldsSet & 0x10) == 0) {
+            if ((mBuilderFieldsSet & 0x8) == 0) {
                 mAttributionSourceState.renouncedPermissions = null;
             }
-            if ((mBuilderFieldsSet & 0x20) == 0) {
+            if ((mBuilderFieldsSet & 0x10) == 0) {
                 mAttributionSourceState.next = null;
             }
 
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 0e3217d..8d3452e 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -152,7 +152,7 @@
             @NonNull String permission, int pid, int uid, @Nullable String packageName,
             @Nullable String attributionTag, @Nullable String message, boolean startDataDelivery) {
         return checkPermissionForDataDelivery(context, permission, pid, new AttributionSource(uid,
-                pid, packageName, attributionTag), message, startDataDelivery);
+                packageName, attributionTag), message, startDataDelivery);
     }
 
     /**
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index 5efc1f9..3ca0560 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -112,6 +112,28 @@
          * @param resourceName name of the target resource to overlay (in the form
          *                     [package]:type/entry)
          * @param dataType the data type of the new value
+         * @param value the unsigned 32 bit integer representing the new value
+         * @param configuration The string representation of the config this overlay is enabled for
+         *
+         * @see android.util.TypedValue#type
+         */
+        public Builder setResourceValue(@NonNull String resourceName, int dataType, int value,
+                String configuration) {
+            final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
+            entry.resourceName = resourceName;
+            entry.dataType = dataType;
+            entry.data = value;
+            entry.configuration = configuration;
+            mEntries.add(entry);
+            return this;
+        }
+
+        /**
+         * Sets the value of the fabricated overlay
+         *
+         * @param resourceName name of the target resource to overlay (in the form
+         *                     [package]:type/entry)
+         * @param dataType the data type of the new value
          * @param value the string representing the new value
          *
          * @see android.util.TypedValue#type
@@ -125,6 +147,28 @@
             return this;
         }
 
+        /**
+         * Sets the value of the fabricated overlay
+         *
+         * @param resourceName name of the target resource to overlay (in the form
+         *                     [package]:type/entry)
+         * @param dataType the data type of the new value
+         * @param value the string representing the new value
+         * @param configuration The string representation of the config this overlay is enabled for
+         *
+         * @see android.util.TypedValue#type
+         */
+        public Builder setResourceValue(@NonNull String resourceName, int dataType, String value,
+                String configuration) {
+            final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
+            entry.resourceName = resourceName;
+            entry.dataType = dataType;
+            entry.stringData = value;
+            entry.configuration = configuration;
+            mEntries.add(entry);
+            return this;
+        }
+
         /** Builds an immutable fabricated overlay. */
         public FabricatedOverlay build() {
             final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal();
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 5839b87..8e2a5ea 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -8065,7 +8065,7 @@
             }
 
             PackageParser.Package pkg = parser.parsePackage(apkFile, 0, false);
-            if ((flagsBits & GET_SIGNATURES) != 0) {
+            if ((flagsBits & GET_SIGNATURES) != 0 || (flagsBits & GET_SIGNING_CERTIFICATES) != 0) {
                 PackageParser.collectCertificates(pkg, false /* skipVerify */);
             }
             return PackageParser.generatePackageInfo(pkg, null, (int) flagsBits, 0, 0, null,
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 7092e43..7247ef7 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -29,6 +29,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricStateListener;
 import android.hardware.biometrics.CryptoObject;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.os.Binder;
@@ -674,6 +675,45 @@
     }
 
     /**
+     * Forwards BiometricStateListener to FaceService.
+     *
+     * @param listener new BiometricStateListener being added
+     * @hide
+     */
+    public void registerBiometricStateListener(@NonNull BiometricStateListener listener) {
+        try {
+            mService.registerBiometricStateListener(listener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Adds a callback that gets called when the service registers all of the face
+     * authenticators (HALs).
+     *
+     * If the face authenticators are already registered when the callback is added, the
+     * callback is invoked immediately.
+     *
+     * The callback is automatically removed after it's invoked.
+     *
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void addAuthenticatorsRegisteredCallback(
+            IFaceAuthenticatorsRegisteredCallback callback) {
+        if (mService != null) {
+            try {
+                mService.addAuthenticatorsRegisteredCallback(callback);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            Slog.w(TAG, "addAuthenticatorsRegisteredCallback(): Service not connected!");
+        }
+    }
+
+    /**
      * @hide
      */
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
diff --git a/core/java/android/hardware/face/IFaceAuthenticatorsRegisteredCallback.aidl b/core/java/android/hardware/face/IFaceAuthenticatorsRegisteredCallback.aidl
new file mode 100644
index 0000000..78f978d2
--- /dev/null
+++ b/core/java/android/hardware/face/IFaceAuthenticatorsRegisteredCallback.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.face;
+
+import android.hardware.face.FaceSensorPropertiesInternal;
+import java.util.List;
+
+/**
+ * Callback to notify FaceManager that FaceService has registered all of the
+ * face authenticators (HALs).
+ * See {@link android.hardware.face.IFaceService#registerAuthenticators}.
+ *
+ * @hide
+ */
+oneway interface IFaceAuthenticatorsRegisteredCallback {
+    /**
+     * Notifies FaceManager that all of the face authenticators have been registered.
+     *
+     * @param sensors A consolidated list of sensor properties for all of the authenticators.
+     */
+    void onAllAuthenticatorsRegistered(in List<FaceSensorPropertiesInternal> sensors);
+}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 369248e..9b56f43 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -17,9 +17,11 @@
 
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.IBiometricStateListener;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
 import android.hardware.face.IFaceServiceReceiver;
 import android.hardware.face.Face;
 import android.hardware.face.FaceSensorPropertiesInternal;
@@ -163,4 +165,11 @@
     // hidlSensors must be non-null and empty. See AuthService.java
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     void registerAuthenticators(in List<FaceSensorPropertiesInternal> hidlSensors);
+
+    // Adds a callback which gets called when the service registers all of the face
+    // authenticators. The callback is automatically removed after it's invoked.
+    void addAuthenticatorsRegisteredCallback(IFaceAuthenticatorsRegisteredCallback callback);
+
+    // Registers BiometricStateListener.
+    void registerBiometricStateListener(IBiometricStateListener listener);
 }
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index cc7ed18..1ba9a04 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -202,8 +202,10 @@
     void setSidefpsController(in ISidefpsController controller);
 
     // Registers BiometricStateListener.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     void registerBiometricStateListener(IBiometricStateListener listener);
 
     // Sends a power button pressed event to all listeners.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     oneway void onPowerPressed();
 }
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 3d59387..05daf63 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -81,6 +81,7 @@
     private static final int DO_INIT_INK_WINDOW = 120;
     private static final int DO_FINISH_STYLUS_HANDWRITING = 130;
     private static final int DO_UPDATE_TOOL_TYPE = 140;
+    private static final int DO_REMOVE_STYLUS_HANDWRITING_WINDOW = 150;
 
     final WeakReference<InputMethodServiceInternal> mTarget;
     final Context mContext;
@@ -254,6 +255,10 @@
                 inputMethod.finishStylusHandwriting();
                 return;
             }
+            case DO_REMOVE_STYLUS_HANDWRITING_WINDOW: {
+                inputMethod.removeStylusHandwritingWindow();
+                return;
+            }
 
         }
         Log.w(TAG, "Unhandled message code: " + msg.what);
@@ -434,4 +439,10 @@
     public void finishStylusHandwriting() {
         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_STYLUS_HANDWRITING));
     }
+
+    @BinderThread
+    @Override
+    public void removeStylusHandwritingWindow() {
+        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_STYLUS_HANDWRITING_WINDOW));
+    }
 }
diff --git a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
index e38e611..3260713 100644
--- a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
+++ b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
@@ -17,6 +17,7 @@
 package android.inputmethodservice;
 
 import android.annotation.AnyThread;
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Bundle;
@@ -24,9 +25,13 @@
 import android.view.KeyEvent;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.DeleteGesture;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.HandwritingGesture;
 import android.view.inputmethod.InputContentInfo;
+import android.view.inputmethod.InsertGesture;
+import android.view.inputmethod.SelectGesture;
 import android.view.inputmethod.SurroundingText;
 import android.view.inputmethod.TextAttribute;
 
@@ -35,6 +40,8 @@
 import com.android.internal.inputmethod.InputConnectionCommandHeader;
 
 import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.IntConsumer;
 
 /**
  * A stateless wrapper of {@link com.android.internal.inputmethod.IRemoteInputConnection} to
@@ -591,6 +598,27 @@
         }
     }
 
+    @AnyThread
+    public void performHandwritingGesture(
+            @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor,
+            @Nullable IntConsumer consumer) {
+        // TODO(b/210039666): implement resultReceiver
+        try {
+            if (gesture instanceof SelectGesture) {
+                mConnection.performHandwritingSelectGesture(
+                        createHeader(), (SelectGesture) gesture, null);
+            } else if (gesture instanceof InsertGesture) {
+                mConnection.performHandwritingInsertGesture(
+                        createHeader(), (InsertGesture) gesture, null);
+            } else if (gesture instanceof DeleteGesture) {
+                mConnection.performHandwritingDeleteGesture(
+                        createHeader(), (DeleteGesture) gesture, null);
+            }
+        } catch (RemoteException e) {
+            // TODO(b/210039666): return result
+        }
+    }
+
     /**
      * Invokes {@link IRemoteInputConnection#requestCursorUpdates(InputConnectionCommandHeader, int,
      * int, AndroidFuture)}.
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index c2027b1..48b9b88 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -621,7 +621,7 @@
     private boolean mDestroyed;
     private boolean mOnPreparedStylusHwCalled;
 
-    /** Stylus handwriting Ink window.  */
+    /** Stylus handwriting Ink window. */
     private InkWindow mInkWindow;
 
     /**
@@ -708,9 +708,6 @@
             mConfigTracker.onInitialize(params.configChanges);
             mPrivOps.set(params.privilegedOperations);
             InputMethodPrivilegedOperationsRegistry.put(params.token, mPrivOps);
-            if (params.stylusHandWritingSupported) {
-                mInkWindow = new InkWindow(mWindow.getContext());
-            }
             mNavigationBarController.onNavButtonFlagsChanged(params.navigationBarFlags);
             attachToken(params.token);
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -744,9 +741,6 @@
             attachToWindowToken(token);
             mToken = token;
             mWindow.setToken(token);
-            if (mInkWindow != null) {
-                mInkWindow.setToken(token);
-            }
         }
 
         /**
@@ -785,7 +779,7 @@
             mInputConnection = null;
             // free-up cached InkWindow surface on detaching from current client.
             if (mInkWindow != null) {
-                mInkWindow.hide(true /* remove */);
+                removeHandwritingInkWindow();
             }
         }
 
@@ -972,6 +966,7 @@
                 Log.d(TAG, "Input should have started before starting Stylus handwriting.");
                 return;
             }
+            maybeCreateInkWindow();
             if (!mOnPreparedStylusHwCalled) {
                 // prepare hasn't been called by Stylus HOVER.
                 onPrepareStylusHandwriting();
@@ -1031,12 +1026,24 @@
          */
         @Override
         public void initInkWindow() {
+            maybeCreateInkWindow();
             mInkWindow.initOnly();
             onPrepareStylusHandwriting();
             mOnPreparedStylusHwCalled = true;
         }
 
         /**
+         * Create and attach token to Ink window if it wasn't already created.
+         */
+        private void maybeCreateInkWindow() {
+            if (mInkWindow == null) {
+                mInkWindow = new InkWindow(mWindow.getContext());
+                mInkWindow.setToken(mToken);
+            }
+            // TODO(b/243571274): set an idle-timeout after which InkWindow is removed.
+        }
+
+        /**
          * {@inheritDoc}
          * @hide
          */
@@ -1047,6 +1054,15 @@
 
         /**
          * {@inheritDoc}
+         * @hide
+         */
+        @Override
+        public void removeStylusHandwritingWindow() {
+            InputMethodService.this.removeStylusHandwritingWindow();
+        }
+
+        /**
+         * {@inheritDoc}
          */
         @MainThread
         @Override
@@ -2511,6 +2527,7 @@
 
         mHandwritingEventReceiver.dispose();
         mHandwritingEventReceiver = null;
+        // TODO(b/243571274): set an idle-timeout after which InkWindow is removed.
         mInkWindow.hide(false /* remove */);
 
         mPrivOps.resetStylusHandwriting(requestId);
@@ -2519,6 +2536,27 @@
     }
 
     /**
+     * Remove Stylus handwriting window.
+     * Typically, this is called when {@link InkWindow} should no longer be holding a surface in
+     * memory.
+     */
+    private void removeStylusHandwritingWindow() {
+        if (mInkWindow != null) {
+            if (mHandwritingRequestId.isPresent()) {
+                // if handwriting session is still ongoing. This shouldn't happen.
+                finishStylusHandwriting();
+            }
+            removeHandwritingInkWindow();
+        }
+    }
+
+    private void removeHandwritingInkWindow() {
+        mInkWindow.hide(true /* remove */);
+        mInkWindow.destroy();
+        mInkWindow = null;
+    }
+
+    /**
      * Sets the duration after which an ongoing stylus handwriting session that hasn't received new
      * {@link MotionEvent}s will time out and {@link #finishStylusHandwriting()} will be called.
      *
diff --git a/core/java/android/inputmethodservice/RemoteInputConnection.java b/core/java/android/inputmethodservice/RemoteInputConnection.java
index 2711c4f..694293c 100644
--- a/core/java/android/inputmethodservice/RemoteInputConnection.java
+++ b/core/java/android/inputmethodservice/RemoteInputConnection.java
@@ -17,6 +17,7 @@
 package android.inputmethodservice;
 
 import android.annotation.AnyThread;
+import android.annotation.CallbackExecutor;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,6 +29,7 @@
 import android.view.inputmethod.CorrectionInfo;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.HandwritingGesture;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputContentInfo;
 import android.view.inputmethod.SurroundingText;
@@ -41,6 +43,8 @@
 
 import java.lang.ref.WeakReference;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.function.IntConsumer;
 
 /**
  * Takes care of remote method invocations of {@link InputConnection} in the IME side.
@@ -411,6 +415,13 @@
     }
 
     @AnyThread
+    public void performHandwritingGesture(
+            @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor,
+            @Nullable IntConsumer consumer) {
+        mInvoker.performHandwritingGesture(gesture, executor, consumer);
+    }
+
+    @AnyThread
     public boolean requestCursorUpdates(int cursorUpdateMode) {
         if (mCancellationGroup.isCanceled()) {
             return false;
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
index be57372..d6f191e 100644
--- a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
+++ b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
@@ -37,6 +37,7 @@
 import android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest;
 import android.os.PersistableBundle;
 import android.util.ArraySet;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.vcn.util.PersistableBundleUtils;
@@ -58,6 +59,8 @@
  */
 @VisibleForTesting(visibility = Visibility.PRIVATE)
 public final class IkeSessionParamsUtils {
+    private static final String TAG = IkeSessionParamsUtils.class.getSimpleName();
+
     private static final String SERVER_HOST_NAME_KEY = "SERVER_HOST_NAME_KEY";
     private static final String SA_PROPOSALS_KEY = "SA_PROPOSALS_KEY";
     private static final String LOCAL_ID_KEY = "LOCAL_ID_KEY";
@@ -72,6 +75,13 @@
     private static final String NATT_KEEPALIVE_DELAY_SEC_KEY = "NATT_KEEPALIVE_DELAY_SEC_KEY";
     private static final String IKE_OPTIONS_KEY = "IKE_OPTIONS_KEY";
 
+    // TODO: b/243181760 Use the IKE API when they are exposed
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static final int IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION = 6;
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static final int IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES = 7;
+
     private static final Set<Integer> IKE_OPTIONS = new ArraySet<>();
 
     static {
@@ -80,6 +90,26 @@
         IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_MOBIKE);
         IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_FORCE_PORT_4500);
         IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_INITIAL_CONTACT);
+        IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_REKEY_MOBILITY);
+        IKE_OPTIONS.add(IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION);
+        IKE_OPTIONS.add(IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES);
+    }
+
+    /**
+     * Check if an IKE option is supported in the IPsec module installed on the device
+     *
+     * <p>This method ensures caller to safely access options that are added between dessert
+     * releases.
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static boolean isIkeOptionValid(int option) {
+        try {
+            new IkeSessionParams.Builder().addIkeOption(option);
+            return true;
+        } catch (IllegalArgumentException e) {
+            Log.d(TAG, "Option not supported; discarding: " + option);
+            return false;
+        }
     }
 
     /** Serializes an IkeSessionParams to a PersistableBundle. */
@@ -130,7 +160,7 @@
         // IKE_OPTION is defined in IKE module and added in the IkeSessionParams
         final List<Integer> enabledIkeOptions = new ArrayList<>();
         for (int option : IKE_OPTIONS) {
-            if (params.hasIkeOption(option)) {
+            if (isIkeOptionValid(option) && params.hasIkeOption(option)) {
                 enabledIkeOptions.add(option);
             }
         }
@@ -205,12 +235,16 @@
 
         // Clear IKE Options that are by default enabled
         for (int option : IKE_OPTIONS) {
-            builder.removeIkeOption(option);
+            if (isIkeOptionValid(option)) {
+                builder.removeIkeOption(option);
+            }
         }
 
         final int[] optionArray = in.getIntArray(IKE_OPTIONS_KEY);
         for (int option : optionArray) {
-            builder.addIkeOption(option);
+            if (isIkeOptionValid(option)) {
+                builder.addIkeOption(option);
+            }
         }
 
         return builder.build();
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 26600e2..09a52e4 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -955,7 +955,16 @@
 
         public static final int NUM_WIFI_BATCHED_SCAN_BINS = 5;
 
-        public static final int NUM_USER_ACTIVITY_TYPES = PowerManager.USER_ACTIVITY_EVENT_MAX + 1;
+        /**
+         * Note that these must match the constants in android.os.PowerManager.
+         * Also, if the user activity types change, the BatteryStatsImpl.VERSION must
+         * also be bumped.
+         */
+        static final String[] USER_ACTIVITY_TYPES = {
+            "other", "button", "touch", "accessibility", "attention"
+        };
+
+        public static final int NUM_USER_ACTIVITY_TYPES = USER_ACTIVITY_TYPES.length;
 
         public abstract void noteUserActivityLocked(int type);
         public abstract boolean hasUserActivity();
@@ -2317,6 +2326,11 @@
     public abstract void finishIteratingHistoryLocked();
 
     /**
+     * Return the base time offset for the battery history.
+     */
+    public abstract long getHistoryBaseTime();
+
+    /**
      * Returns the number of times the device has been started.
      */
     public abstract int getStartCount();
@@ -6154,7 +6168,7 @@
                         }
                         sb.append(val);
                         sb.append(" ");
-                        sb.append(PowerManager.userActivityEventToString(i));
+                        sb.append(Uid.USER_ACTIVITY_TYPES[i]);
                     }
                 }
                 if (hasData) {
@@ -7601,6 +7615,8 @@
                 CHECKIN_VERSION, getParcelVersion(), getStartPlatformVersion(),
                 getEndPlatformVersion());
 
+        long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
+
         if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) {
             if (startIteratingHistoryLocked()) {
                 try {
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 62ee408..88649cb 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -76,6 +76,7 @@
     String getUserAccount(int userId);
     void setUserAccount(int userId, String accountName);
     long getUserCreationTime(int userId);
+    boolean isUserSwitcherEnabled(int mUserId);
     boolean isRestricted(int userId);
     boolean canHaveRestrictedProfile(int userId);
     int getUserSerialNumber(int userId);
@@ -128,6 +129,7 @@
     boolean isUserRunning(int userId);
     boolean isUserForeground(int userId);
     boolean isUserVisible(int userId);
+    List<UserHandle> getVisibleUsers();
     boolean isUserNameSet(int userId);
     boolean hasRestrictedProfiles(int userId);
     boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userId, in IntentSender target, int flags);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index a3a3e3f..01b75d1 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -348,44 +348,6 @@
     public static final int USER_ACTIVITY_EVENT_DEVICE_STATE = 6;
 
     /**
-     * @hide
-     */
-    public static final int USER_ACTIVITY_EVENT_MAX =  USER_ACTIVITY_EVENT_DEVICE_STATE;
-
-    /**
-     * @hide
-     */
-    @IntDef(prefix = { "USER_ACTIVITY_EVENT_" }, value = {
-            USER_ACTIVITY_EVENT_OTHER,
-            USER_ACTIVITY_EVENT_BUTTON,
-            USER_ACTIVITY_EVENT_TOUCH,
-            USER_ACTIVITY_EVENT_ACCESSIBILITY,
-            USER_ACTIVITY_EVENT_ATTENTION,
-            USER_ACTIVITY_EVENT_FACE_DOWN,
-            USER_ACTIVITY_EVENT_DEVICE_STATE,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface UserActivityEvent{}
-
-    /**
-     *
-     * Convert the user activity event to a string for debugging purposes.
-     * @hide
-     */
-    public static String userActivityEventToString(@UserActivityEvent int userActivityEvent) {
-        switch (userActivityEvent) {
-            case USER_ACTIVITY_EVENT_OTHER: return "other";
-            case USER_ACTIVITY_EVENT_BUTTON: return "button";
-            case USER_ACTIVITY_EVENT_TOUCH: return "touch";
-            case USER_ACTIVITY_EVENT_ACCESSIBILITY: return "accessibility";
-            case USER_ACTIVITY_EVENT_ATTENTION: return "attention";
-            case USER_ACTIVITY_EVENT_FACE_DOWN: return "faceDown";
-            case USER_ACTIVITY_EVENT_DEVICE_STATE: return "deviceState";
-            default: return Integer.toString(userActivityEvent);
-        }
-    }
-
-    /**
      * User activity flag: If already dimmed, extend the dim timeout
      * but do not brighten.  This flag is useful for keeping the screen on
      * a little longer without causing a visible change such as when
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 095d53e..14082f3 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -364,11 +364,6 @@
     public static final int LAST_APPLICATION_CACHE_GID = 29999;
 
     /**
-     * An invalid PID value.
-     */
-    public static final int INVALID_PID = -1;
-
-    /**
      * Standard priority of application threads.
      * Use with {@link #setThreadPriority(int)} and
      * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index ef04f64..f6aaee8 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1995,7 +1995,8 @@
     /** @hide */
     public UserManager(Context context, IUserManager service) {
         mService = service;
-        mContext = context.getApplicationContext();
+        Context appContext = context.getApplicationContext();
+        mContext = (appContext == null ? context : appContext);
         mUserId = context.getUserId();
     }
 
@@ -2885,6 +2886,21 @@
     }
 
     /**
+     * Gets the visible users (as defined by {@link #isUserVisible()}.
+     *
+     * @return visible users at the moment.
+     */
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.INTERACT_ACROSS_USERS})
+    public @NonNull List<UserHandle> getVisibleUsers() {
+        try {
+            return mService.getVisibleUsers();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Return whether the context user is running in an "unlocked" state.
      * <p>
      * On devices with direct boot, a user is unlocked only after they've
@@ -5246,23 +5262,13 @@
     })
     @UserHandleAware
     public boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable) {
-        if (!supportsMultipleUsers()) {
-            return false;
-        }
-        if (hasUserRestrictionForUser(DISALLOW_USER_SWITCH, mUserId)) {
-            return false;
-        }
-        // If Demo Mode is on, don't show user switcher
-        if (isDeviceInDemoMode(mContext)) {
-            return false;
-        }
-        // Check the Settings.Global.USER_SWITCHER_ENABLED that the user can toggle on/off.
-        final boolean userSwitcherSettingOn = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.USER_SWITCHER_ENABLED,
-                Resources.getSystem().getBoolean(R.bool.config_showUserSwitcherByDefault) ? 1 : 0)
-                != 0;
-        if (!userSwitcherSettingOn) {
-            return false;
+
+        try {
+            if (!mService.isUserSwitcherEnabled(mUserId)) {
+                return false;
+            }
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
         }
 
         // The feature is enabled. But is it worth showing?
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8a50e79..14598d5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9187,14 +9187,12 @@
         public static final String SCREENSAVER_DEFAULT_COMPONENT = "screensaver_default_component";
 
         /**
-         * The complications that are enabled to be shown over the screensaver by the user. Holds
-         * a comma separated list of
-         * {@link com.android.settingslib.dream.DreamBackend.ComplicationType}.
+         * Whether complications are enabled to be shown over the screensaver by the user.
          *
          * @hide
          */
-        public static final String SCREENSAVER_ENABLED_COMPLICATIONS =
-                "screensaver_enabled_complications";
+        public static final String SCREENSAVER_COMPLICATIONS_ENABLED =
+                "screensaver_complications_enabled";
 
 
         /**
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index 91042bfa..a38ef96 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -91,6 +91,21 @@
             = "android.service.notification.NotificationAssistantService";
 
     /**
+     * Activity Action: Show notification assistant detail setting page in NAS app.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_NOTIFICATION_ASSISTANT_DETAIL_SETTINGS =
+            "android.service.notification.action.NOTIFICATION_ASSISTANT_DETAIL_SETTINGS";
+
+
+    /**
      * Data type: int, the feedback rating score provided by user. The score can be any integer
      *            value depends on the experimental and feedback UX design.
      */
diff --git a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
index 95bcda5..9292e96 100644
--- a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
+++ b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
@@ -1317,7 +1317,6 @@
         contentContainer.setLayoutParams(new ViewGroup.LayoutParams(
                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
         contentContainer.setTag(FloatingToolbar.FLOATING_TOOLBAR_TAG);
-        contentContainer.setContentDescription(FloatingToolbar.FLOATING_TOOLBAR_TAG);
         contentContainer.setClipToOutline(true);
         return contentContainer;
     }
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index b73ff901..0338ceb 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -97,10 +97,6 @@
     /** @hide */
     public static final String SETTINGS_AUTO_TEXT_WRAPPING = "settings_auto_text_wrapping";
 
-    /** Flag to enable/disable guest mode UX changes as mentioned in b/214031645
-     *  @hide
-     */
-    public static final String SETTINGS_GUEST_MODE_UX_CHANGES = "settings_guest_mode_ux_changes";
 
     /** Support Clear Calling feature.
      *  @hide
@@ -150,7 +146,6 @@
         DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
         DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true");
         DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false");
-        DEFAULT_FLAGS.put(SETTINGS_GUEST_MODE_UX_CHANGES, "true");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_CLEAR_CALLING, "false");
         DEFAULT_FLAGS.put(SETTINGS_ACCESSIBILITY_SIMPLE_CURSOR, "false");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "false");
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 8aa113d..229de31 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -413,11 +413,6 @@
     boolean hasNavigationBar(int displayId);
 
     /**
-     * Get the position of the nav bar
-     */
-    int getNavBarPosition(int displayId);
-
-    /**
      * Lock the device immediately with the specified options (can be null).
      */
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 9f426a1..45d28da 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -351,6 +351,20 @@
         return insets;
     }
 
+    // TODO: Remove this once the task bar is treated as navigation bar.
+    public Insets calculateInsetsWithInternalTypes(Rect frame, @InternalInsetsType int[] types,
+            boolean ignoreVisibility) {
+        Insets insets = Insets.NONE;
+        for (int i = types.length - 1; i >= 0; i--) {
+            InsetsSource source = mSources[types[i]];
+            if (source == null) {
+                continue;
+            }
+            insets = Insets.max(source.calculateInsets(frame, ignoreVisibility), insets);
+        }
+        return insets;
+    }
+
     public Insets calculateInsets(Rect frame, @InsetsType int types,
             InsetsVisibilities overrideVisibilities) {
         Insets insets = Insets.NONE;
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 84f04c1..e0f02d6 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -130,7 +130,6 @@
             float x, float y);
     private static native void nativeSetScale(long transactionObj, long nativeObject,
             float x, float y);
-    private static native void nativeSetSize(long transactionObj, long nativeObject, int w, int h);
     private static native void nativeSetTransparentRegionHint(long transactionObj,
             long nativeObject, Region region);
     private static native void nativeSetAlpha(long transactionObj, long nativeObject, float alpha);
@@ -274,6 +273,9 @@
     private static native void nativeSanitize(long transactionObject);
     private static native void nativeSetDestinationFrame(long transactionObj, long nativeObject,
             int l, int t, int r, int b);
+    private static native void nativeSetDefaultApplyToken(IBinder token);
+    private static native IBinder nativeGetDefaultApplyToken();
+
 
     /**
      * Transforms that can be applied to buffers as they are displayed to a window.
@@ -2774,6 +2776,22 @@
         }
 
         /**
+         *
+         * @hide
+         */
+        public static void setDefaultApplyToken(IBinder token) {
+            nativeSetDefaultApplyToken(token);
+        }
+
+        /**
+         *
+         * @hide
+         */
+        public static IBinder getDefaultApplyToken() {
+            return nativeGetDefaultApplyToken();
+        }
+
+        /**
          * Apply the transaction, clearing it's state, and making it usable
          * as a new transaction.
          */
@@ -2954,7 +2972,6 @@
                 @IntRange(from = 0) int w, @IntRange(from = 0) int h) {
             checkPreconditions(sc);
             mResizedSurfaces.put(sc, new Point(w, h));
-            nativeSetSize(mNativeObject, sc.mNativeObject, w, h);
             return this;
         }
 
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index a664278..b6c92e3 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -914,7 +914,7 @@
                 && mRequestedVisible;
         final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
         final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
-        getLocationInSurface(mLocation);
+        getLocationInWindow(mLocation);
         final boolean positionChanged = mWindowSpaceLeft != mLocation[0]
             || mWindowSpaceTop != mLocation[1];
         final boolean layoutSizeChanged = getWidth() != mScreenRect.width()
@@ -925,7 +925,6 @@
         if (creating || formatChanged || sizeChanged || visibleChanged ||
                 (mUseAlpha && alphaChanged) || windowVisibleChanged ||
                 positionChanged || layoutSizeChanged || hintChanged) {
-            getLocationInWindow(mLocation);
 
             if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
                     + "Changes: creating=" + creating
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9091b79..59bc061 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -173,6 +173,7 @@
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityInteractionClient;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
 import android.view.accessibility.AccessibilityManager.HighTextContrastChangeListener;
@@ -5318,6 +5319,7 @@
         }
 
         mAccessibilityInteractionConnectionManager.ensureNoConnection();
+        mAccessibilityInteractionConnectionManager.ensureNoDirectConnection();
         removeSendWindowContentChangedCallback();
 
         destroyHardwareRenderer();
@@ -9570,6 +9572,14 @@
         }
     }
 
+    /**
+     * Return the connection ID for the {@link AccessibilityInteractionController} of this instance.
+     * @see AccessibilityNodeInfo#makeQueryableFromAppProcess(View)
+     */
+    public int getDirectAccessibilityConnectionId() {
+        return mAccessibilityInteractionConnectionManager.ensureDirectConnection();
+    }
+
     @Override
     public boolean showContextMenuForChild(View originalView) {
         return false;
@@ -10445,6 +10455,8 @@
      */
     final class AccessibilityInteractionConnectionManager
             implements AccessibilityStateChangeListener {
+        private int mDirectConnectionId = AccessibilityNodeInfo.UNDEFINED_CONNECTION_ID;
+
         @Override
         public void onAccessibilityStateChanged(boolean enabled) {
             if (enabled) {
@@ -10488,6 +10500,21 @@
                 mAccessibilityManager.removeAccessibilityInteractionConnection(mWindow);
             }
         }
+
+        public int ensureDirectConnection() {
+            if (mDirectConnectionId == AccessibilityNodeInfo.UNDEFINED_CONNECTION_ID) {
+                mDirectConnectionId = AccessibilityInteractionClient.addDirectConnection(
+                        new AccessibilityInteractionConnection(ViewRootImpl.this));
+            }
+            return mDirectConnectionId;
+        }
+
+        public void ensureNoDirectConnection() {
+            if (mDirectConnectionId != AccessibilityNodeInfo.UNDEFINED_CONNECTION_ID) {
+                AccessibilityInteractionClient.removeConnection(mDirectConnectionId);
+                mDirectConnectionId = AccessibilityNodeInfo.UNDEFINED_CONNECTION_ID;
+            }
+        }
     }
 
     final class HighContrastTextManager implements HighTextContrastChangeListener {
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index 57a0330..5ed9d2f 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -118,11 +118,11 @@
             }
             if (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) {
                 if (displayFrame.width() < displayFrame.height()) {
-                    displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
-                    displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
+                    displayCutoutSafeExceptMaybeBars.top = MIN_Y;
+                    displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
                 } else {
-                    displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
-                    displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
+                    displayCutoutSafeExceptMaybeBars.left = MIN_X;
+                    displayCutoutSafeExceptMaybeBars.right = MAX_X;
                 }
             }
             final boolean layoutInsetDecor = (attrs.flags & FLAG_LAYOUT_INSET_DECOR) != 0;
@@ -132,23 +132,23 @@
                 final Insets systemBarsInsets = state.calculateInsets(
                         displayFrame, WindowInsets.Type.systemBars(), requestedVisibilities);
                 if (systemBarsInsets.left > 0) {
-                    displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
+                    displayCutoutSafeExceptMaybeBars.left = MIN_X;
                 }
                 if (systemBarsInsets.top > 0) {
-                    displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
+                    displayCutoutSafeExceptMaybeBars.top = MIN_Y;
                 }
                 if (systemBarsInsets.right > 0) {
-                    displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
+                    displayCutoutSafeExceptMaybeBars.right = MAX_X;
                 }
                 if (systemBarsInsets.bottom > 0) {
-                    displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
+                    displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
                 }
             }
             if (type == TYPE_INPUT_METHOD) {
                 final InsetsSource navSource = state.peekSource(ITYPE_NAVIGATION_BAR);
                 if (navSource != null && navSource.calculateInsets(displayFrame, true).bottom > 0) {
                     // The IME can always extend under the bottom cutout if the navbar is there.
-                    displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
+                    displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
                 }
             }
             final boolean attachedInParent = attachedWindowFrame != null && !layoutInScreen;
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 6853278..227a8ef 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -114,6 +114,10 @@
     private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
             new SparseArray<>();
 
+    // Used to generate connection ids for direct app-process connections. Start sufficiently far
+    // enough from the connection ids generated by AccessibilityManagerService.
+    private static int sDirectConnectionIdCounter = 1 << 30;
+
     /** List of timestamps which indicate the latest time an a11y service receives a scroll event
         from a window, mapping from windowId -> timestamp. */
     private static final SparseLongArray sScrollingWindows = new SparseLongArray();
@@ -232,6 +236,12 @@
             return;
         }
         synchronized (sConnectionCache) {
+            IAccessibilityServiceConnection existingConnection = getConnection(connectionId);
+            if (existingConnection instanceof DirectAccessibilityConnection) {
+                throw new IllegalArgumentException(
+                        "Cannot add service connection with id " + connectionId
+                                + " which conflicts with existing direct connection.");
+            }
             sConnectionCache.put(connectionId, connection);
             if (!initializeCache) {
                 return;
@@ -242,6 +252,33 @@
     }
 
     /**
+     * Adds a new {@link DirectAccessibilityConnection} using the provided
+     * {@link IAccessibilityInteractionConnection} to create a direct connection between
+     * this client and the {@link android.view.ViewRootImpl} for queries inside the app process.
+     *
+     * <p>
+     * See {@link DirectAccessibilityConnection} for supported methods.
+     * </p>
+     *
+     * @param connection The ViewRootImpl's {@link IAccessibilityInteractionConnection}.
+     */
+    public static int addDirectConnection(IAccessibilityInteractionConnection connection) {
+        synchronized (sConnectionCache) {
+            int connectionId = sDirectConnectionIdCounter++;
+            if (getConnection(connectionId) != null) {
+                throw new IllegalArgumentException(
+                        "Cannot add direct connection with existing id " + connectionId);
+            }
+            DirectAccessibilityConnection directAccessibilityConnection =
+                    new DirectAccessibilityConnection(connection);
+            sConnectionCache.put(connectionId, directAccessibilityConnection);
+            // Do not use AccessibilityCache for this connection, since there is no corresponding
+            // AccessibilityService to handle cache invalidation events.
+            return connectionId;
+        }
+    }
+
+    /**
      * Gets a cached associated with the connection id if available.
      *
      */
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 953f261..5d52750 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -58,6 +58,7 @@
 import android.view.TouchDelegate;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewRootImpl;
 import android.widget.TextView;
 
 import com.android.internal.R;
@@ -82,7 +83,9 @@
  * </p>
  * <p>
  * Once an accessibility node info is delivered to an accessibility service it is
- * made immutable and calling a state mutation method generates an error.
+ * made immutable and calling a state mutation method generates an error. See
+ * {@link #makeQueryableFromAppProcess(View)} if you would like to inspect the
+ * node tree from the app process for testing or debugging tools.
  * </p>
  * <p>
  * Please refer to {@link android.accessibilityservice.AccessibilityService} for
@@ -1156,8 +1159,8 @@
      * @param index The child index.
      * @return The child node.
      *
-     * @throws IllegalStateException If called outside of an AccessibilityService.
-     *
+     * @throws IllegalStateException If called outside of an {@link AccessibilityService} and before
+     *                               calling {@link #makeQueryableFromAppProcess(View)}.
      */
     public AccessibilityNodeInfo getChild(int index) {
         return getChild(index, FLAG_PREFETCH_DESCENDANTS_HYBRID);
@@ -1171,7 +1174,8 @@
      * @param prefetchingStrategy the prefetching strategy.
      * @return The child node.
      *
-     * @throws IllegalStateException If called outside of an AccessibilityService.
+     * @throws IllegalStateException If called outside of an {@link AccessibilityService} and before
+     *                               calling {@link #makeQueryableFromAppProcess(View)}.
      *
      * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching.
      */
@@ -1893,6 +1897,9 @@
      * Gets the parent.
      *
      * @return The parent.
+     *
+     * @throws IllegalStateException If called outside of an {@link AccessibilityService} and before
+     *                               calling {@link #makeQueryableFromAppProcess(View)}.
      */
     public AccessibilityNodeInfo getParent() {
         enforceSealed();
@@ -1920,7 +1927,8 @@
      * @param prefetchingStrategy the prefetching strategy.
      * @return The parent.
      *
-     * @throws IllegalStateException If called outside of an AccessibilityService.
+     * @throws IllegalStateException If called outside of an {@link AccessibilityService} and before
+     *                               calling {@link #makeQueryableFromAppProcess(View)}.
      *
      * @see #FLAG_PREFETCH_ANCESTORS
      * @see #FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST
@@ -3642,6 +3650,47 @@
     }
 
     /**
+     * Connects this node to the View's root so that operations on this node can query the entire
+     * {@link AccessibilityNodeInfo} tree and perform accessibility actions on nodes.
+     *
+     * <p>
+     * This is intended for short-lived inspections from testing or debugging tools in the app
+     * process. After calling this method, all nodes linked to this node (children, ancestors, etc.)
+     * are also queryable. Operations on this node tree will only succeed as long as the associated
+     * view hierarchy remains attached to a window.
+     * </p>
+     *
+     * <p>
+     * Calling this method more than once on the same node is a no-op; if you wish to inspect a
+     * different view hierarchy then create a new node from any view in that hierarchy and call this
+     * method on that node.
+     * </p>
+     *
+     * <p>
+     * Testing or debugging tools should create this {@link AccessibilityNodeInfo} node using
+     * {@link View#createAccessibilityNodeInfo()} or {@link AccessibilityNodeProvider} and call this
+     * method, then navigate and interact with the node tree by calling methods on the node.
+     * </p>
+     *
+     * @param view The view that generated this node, or any view in the same view-root hierarchy.
+     * @throws IllegalStateException If called from an {@link AccessibilityService}, or if provided
+     *                               a {@link View} that is not attached to a window.
+     */
+    public void makeQueryableFromAppProcess(@NonNull View view) {
+        enforceNotSealed();
+        if (mConnectionId != UNDEFINED_CONNECTION_ID) {
+            return;
+        }
+
+        ViewRootImpl viewRootImpl = view.getViewRootImpl();
+        if (viewRootImpl == null) {
+            throw new IllegalStateException(
+                    "Cannot link a node to a view that is not attached to a window.");
+        }
+        setConnectionId(viewRootImpl.getDirectAccessibilityConnectionId());
+    }
+
+    /**
      * Sets if this instance is sealed.
      *
      * @param sealed Whether is sealed.
@@ -3665,15 +3714,21 @@
         return mSealed;
     }
 
+    private static boolean usingDirectConnection(int connectionId) {
+        return AccessibilityInteractionClient.getConnection(
+                connectionId) instanceof DirectAccessibilityConnection;
+    }
+
     /**
-     * Enforces that this instance is sealed.
+     * Enforces that this instance is sealed, unless using a {@link DirectAccessibilityConnection}
+     * which allows queries while the node is not sealed.
      *
      * @throws IllegalStateException If this instance is not sealed.
      *
      * @hide
      */
     protected void enforceSealed() {
-        if (!isSealed()) {
+        if (!usingDirectConnection(mConnectionId) && !isSealed()) {
             throw new IllegalStateException("Cannot perform this "
                     + "action on a not sealed instance.");
         }
@@ -4499,7 +4554,8 @@
 
     private static boolean canPerformRequestOverConnection(int connectionId,
             int windowId, long accessibilityNodeId) {
-        return ((windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
+        final boolean hasWindowId = windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+        return ((usingDirectConnection(connectionId) || hasWindowId)
                 && (getAccessibilityViewId(accessibilityNodeId) != UNDEFINED_ITEM_ID)
                 && (connectionId != UNDEFINED_CONNECTION_ID));
     }
diff --git a/core/java/android/view/accessibility/DirectAccessibilityConnection.java b/core/java/android/view/accessibility/DirectAccessibilityConnection.java
new file mode 100644
index 0000000..71746ee
--- /dev/null
+++ b/core/java/android/view/accessibility/DirectAccessibilityConnection.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.graphics.Matrix;
+import android.graphics.Region;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.RemoteException;
+import android.view.MagnificationSpec;
+
+/**
+ * Minimal {@link IAccessibilityServiceConnection} implementation that interacts
+ * with the {@link android.view.AccessibilityInteractionController} of a
+ * {@link android.view.ViewRootImpl}.
+ *
+ * <p>
+ * Uses {@link android.view.ViewRootImpl}'s {@link IAccessibilityServiceConnection} that wraps
+ * {@link android.view.AccessibilityInteractionController} within the app process, so that no
+ * interprocess communication is performed.
+ * </p>
+ *
+ * <p>
+ * Only the following methods are supported:
+ * <li>{@link #findAccessibilityNodeInfoByAccessibilityId}</li>
+ * <li>{@link #findAccessibilityNodeInfosByText}</li>
+ * <li>{@link #findAccessibilityNodeInfosByViewId}</li>
+ * <li>{@link #findFocus}</li>
+ * <li>{@link #focusSearch}</li>
+ * <li>{@link #performAccessibilityAction}</li>
+ * </p>
+ *
+ * <p>
+ * Other methods are no-ops and return default values.
+ * </p>
+ */
+class DirectAccessibilityConnection extends IAccessibilityServiceConnection.Default {
+    private final IAccessibilityInteractionConnection mAccessibilityInteractionConnection;
+
+    // Fetch all views, but do not use prefetching/cache since this "connection" does not
+    // receive cache invalidation events (as it is not linked to an AccessibilityService).
+    private static final int FETCH_FLAGS =
+            AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS
+                    | AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+    private static final MagnificationSpec MAGNIFICATION_SPEC = new MagnificationSpec();
+    private static final int PID = Process.myPid();
+    private static final Region INTERACTIVE_REGION = null;
+    private static final float[] TRANSFORM_MATRIX = new float[9];
+
+    static {
+        Matrix.IDENTITY_MATRIX.getValues(TRANSFORM_MATRIX);
+    }
+
+    DirectAccessibilityConnection(
+            IAccessibilityInteractionConnection accessibilityInteractionConnection) {
+        mAccessibilityInteractionConnection = accessibilityInteractionConnection;
+    }
+
+    @Override
+    public String[] findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
+            long accessibilityNodeId, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, int flags, long threadId,
+            Bundle arguments) throws RemoteException {
+        mAccessibilityInteractionConnection.findAccessibilityNodeInfoByAccessibilityId(
+                accessibilityNodeId, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID,
+                threadId, MAGNIFICATION_SPEC, TRANSFORM_MATRIX, arguments);
+        return new String[0];
+    }
+
+    @Override
+    public String[] findAccessibilityNodeInfosByText(int accessibilityWindowId,
+            long accessibilityNodeId, String text, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, long threadId)
+            throws RemoteException {
+        mAccessibilityInteractionConnection.findAccessibilityNodeInfosByText(accessibilityNodeId,
+                text, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
+                MAGNIFICATION_SPEC, TRANSFORM_MATRIX);
+        return new String[0];
+    }
+
+    @Override
+    public String[] findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
+            long accessibilityNodeId, String viewId, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, long threadId)
+            throws RemoteException {
+        mAccessibilityInteractionConnection.findAccessibilityNodeInfosByViewId(accessibilityNodeId,
+                viewId, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
+                MAGNIFICATION_SPEC, TRANSFORM_MATRIX);
+        return new String[0];
+    }
+
+    @Override
+    public String[] findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType,
+            int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId)
+            throws RemoteException {
+        mAccessibilityInteractionConnection.findFocus(accessibilityNodeId, focusType,
+                INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
+                MAGNIFICATION_SPEC, TRANSFORM_MATRIX);
+        return new String[0];
+    }
+
+    @Override
+    public String[] focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction,
+            int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId)
+            throws RemoteException {
+        mAccessibilityInteractionConnection.focusSearch(accessibilityNodeId, direction,
+                INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
+                MAGNIFICATION_SPEC, TRANSFORM_MATRIX);
+        return new String[0];
+    }
+
+    @Override
+    public boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId,
+            int action, Bundle arguments, int interactionId,
+            IAccessibilityInteractionConnectionCallback callback, long threadId)
+            throws RemoteException {
+        mAccessibilityInteractionConnection.performAccessibilityAction(accessibilityNodeId, action,
+                arguments, interactionId, callback, FETCH_FLAGS, PID, threadId);
+        return true;
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt b/core/java/android/view/inputmethod/DeleteGesture.aidl
similarity index 79%
copy from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
copy to core/java/android/view/inputmethod/DeleteGesture.aidl
index 8dde897..e9f31dd 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
+++ b/core/java/android/view/inputmethod/DeleteGesture.aidl
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spaprivileged.framework.app
+package android.view.inputmethod;
 
-import android.content.pm.ApplicationInfo
-
-interface AppRecord {
-    val app: ApplicationInfo
-}
+parcelable DeleteGesture;
\ No newline at end of file
diff --git a/core/java/android/view/inputmethod/DeleteGesture.java b/core/java/android/view/inputmethod/DeleteGesture.java
new file mode 100644
index 0000000..257254e
--- /dev/null
+++ b/core/java/android/view/inputmethod/DeleteGesture.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.graphics.RectF;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.widget.TextView;
+
+import java.util.Objects;
+
+/**
+ * A sub-class of {@link HandwritingGesture} for deleting an area of text.
+ * This class holds the information required for deletion of text in
+ * toolkit widgets like {@link TextView}.
+ */
+public final class DeleteGesture extends HandwritingGesture implements Parcelable {
+
+    private @Granularity int mGranularity;
+    private RectF mArea;
+
+    private DeleteGesture(@Granularity int granularity, RectF area, String fallbackText) {
+        mArea = area;
+        mGranularity = granularity;
+        mFallbackText = fallbackText;
+    }
+
+    private DeleteGesture(@NonNull final Parcel source) {
+        mFallbackText = source.readString8();
+        mGranularity = source.readInt();
+        mArea = source.readTypedObject(RectF.CREATOR);
+    }
+
+    /**
+     * Returns Granular level on which text should be operated.
+     * @see HandwritingGesture#GRANULARITY_CHARACTER
+     * @see HandwritingGesture#GRANULARITY_WORD
+     */
+    @Granularity
+    public int getGranularity() {
+        return mGranularity;
+    }
+
+    /**
+     * Returns the deletion area {@link RectF} in screen coordinates.
+     *
+     * Getter for deletion area set with {@link DeleteGesture.Builder#setDeletionArea(RectF)}.
+     * {@code null} if area was not set.
+     */
+    @NonNull
+    public RectF getDeletionArea() {
+        return mArea;
+    }
+
+    /**
+     * Builder for {@link DeleteGesture}. This class is not designed to be thread-safe.
+     */
+    public static final class Builder {
+        private int mGranularity;
+        private RectF mArea;
+        private String mFallbackText;
+
+        /**
+         * Set text deletion granularity. Intersecting words/characters will be
+         * included in the operation.
+         * @param granularity {@link HandwritingGesture#GRANULARITY_WORD} or
+         * {@link HandwritingGesture#GRANULARITY_CHARACTER}.
+         * @return {@link Builder}.
+         */
+        @NonNull
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setGranularity(@Granularity int granularity) {
+            mGranularity = granularity;
+            return this;
+        }
+
+        /**
+         * Set rectangular single/multiline text deletion area intersecting with text.
+         *
+         * The resulting deletion would be performed for all text intersecting rectangle. The
+         * deletion includes the first word/character in the rectangle, and the last
+         * word/character in the rectangle, and includes  everything in between even if it's not
+         * in the rectangle.
+         *
+         * Intersection is determined using
+         * {@link #setGranularity(int)}. e.g. {@link HandwritingGesture#GRANULARITY_WORD} includes
+         * all the words with their width/height center included in the deletion rectangle.
+         * @param area {@link RectF} (in screen coordinates) for which text will be deleted.
+         * @see HandwritingGesture#GRANULARITY_WORD
+         * @see HandwritingGesture#GRANULARITY_CHARACTER
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder setDeletionArea(@NonNull RectF area) {
+            mArea = area;
+            return this;
+        }
+
+        /**
+         * Set fallback text that will be committed at current cursor position if there is no
+         * applicable text beneath the area of gesture.
+         * @param fallbackText text to set
+         */
+        @NonNull
+        public Builder setFallbackText(@Nullable String fallbackText) {
+            mFallbackText = fallbackText;
+            return this;
+        }
+
+        /**
+         * @return {@link DeleteGesture} using parameters in this {@link DeleteGesture.Builder}.
+         * @throws IllegalArgumentException if one or more positional parameters are not specified.
+         */
+        @NonNull
+        public DeleteGesture build() {
+            if (mArea == null || mArea.isEmpty()) {
+                throw new IllegalArgumentException("Deletion area must be set.");
+            }
+            if (mGranularity <= GRANULARITY_UNDEFINED) {
+                throw new IllegalArgumentException("Deletion granularity must be set.");
+            }
+            return new DeleteGesture(mGranularity, mArea, mFallbackText);
+        }
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final @android.annotation.NonNull Creator<DeleteGesture> CREATOR =
+            new Creator<DeleteGesture>() {
+        @Override
+        public DeleteGesture createFromParcel(Parcel source) {
+            return new DeleteGesture(source);
+        }
+
+        @Override
+        public DeleteGesture[] newArray(int size) {
+            return new DeleteGesture[size];
+        }
+    };
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mArea, mGranularity, mFallbackText);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof DeleteGesture)) return false;
+
+        DeleteGesture that = (DeleteGesture) o;
+
+        if (mGranularity != that.mGranularity) return false;
+        if (!Objects.equals(mFallbackText, that.mFallbackText)) return false;
+        return Objects.equals(mArea, that.mArea);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mFallbackText);
+        dest.writeInt(mGranularity);
+        dest.writeTypedObject(mArea, flags);
+    }
+}
diff --git a/core/java/android/view/inputmethod/HandwritingGesture.java b/core/java/android/view/inputmethod/HandwritingGesture.java
new file mode 100644
index 0000000..15824ae
--- /dev/null
+++ b/core/java/android/view/inputmethod/HandwritingGesture.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.graphics.RectF;
+import android.inputmethodservice.InputMethodService;
+import android.view.MotionEvent;
+
+import java.util.concurrent.Executor;
+import java.util.function.IntConsumer;
+
+/**
+ * Base class for Stylus handwriting gesture.
+ *
+ * During a stylus handwriting session, user can perform a stylus gesture operation like
+ * {@link SelectGesture}, {@link DeleteGesture}, {@link InsertGesture} on an
+ * area of text. IME is responsible for listening to Stylus {@link MotionEvent} using
+ * {@link InputMethodService#onStylusHandwritingMotionEvent} and interpret if it can translate to a
+ * gesture operation.
+ * While creating Gesture operations {@link SelectGesture}, {@link DeleteGesture},
+ * , {@code Granularity} helps pick the correct granular level of text like word level
+ * {@link #GRANULARITY_WORD}, or character level {@link #GRANULARITY_CHARACTER}.
+ *
+ * @see InputConnection#performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)
+ * @see InputMethodService#onStartStylusHandwriting()
+ */
+public abstract class HandwritingGesture {
+
+    HandwritingGesture() {}
+
+    static final int GRANULARITY_UNDEFINED = 0;
+
+    /**
+     * Operate text per word basis. e.g. if selection includes width-wise center of the word,
+     * whole word is selected.
+     * <p> Strategy of operating at a granular level is maintained in the UI toolkit.
+     *     A character/word/line is included if its center is within the gesture rectangle.
+     *     e.g. if a selection {@link RectF} with {@link #GRANULARITY_WORD} includes width-wise
+     *     center of the word, it should be selected.
+     *     Similarly, text in a line should be included in the operation if rectangle includes
+     *     line height center.</p>
+     * Refer to https://www.unicode.org/reports/tr29/#Word_Boundaries for more detail on how word
+     * breaks are decided.
+     */
+    public static final int GRANULARITY_WORD = 1;
+
+    /**
+     * Operate on text per character basis. i.e. each character is selected based on its
+     * intersection with selection rectangle.
+     * <p> Strategy of operating at a granular level is maintained in the UI toolkit.
+     *     A character/word/line is included if its center is within the gesture rectangle.
+     *     e.g. if a selection {@link RectF} with {@link #GRANULARITY_CHARACTER} includes width-wise
+     *     center of the character, it should be selected.
+     *     Similarly, text in a line should be included in the operation if rectangle includes
+     *     line height center.</p>
+     */
+    public static final int GRANULARITY_CHARACTER = 2;
+
+    /**
+     * Granular level on which text should be operated.
+     */
+    @IntDef({GRANULARITY_CHARACTER, GRANULARITY_WORD})
+    @interface Granularity {}
+
+    @Nullable
+    String mFallbackText;
+
+    /**
+     * The fallback text that will be committed at current cursor position if there is no applicable
+     * text beneath the area of gesture.
+     * For example, select can fail if gesture is drawn over area that has no text beneath.
+     * example 2: join can fail if the gesture is drawn over text but there is no whitespace.
+     */
+    @Nullable
+    public String getFallbackText() {
+        return mFallbackText;
+    }
+}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index dac1be6..7b0270a1 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -16,6 +16,7 @@
 
 package android.view.inputmethod;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -31,6 +32,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+import java.util.function.IntConsumer;
 
 /**
  * The InputConnection interface is the communication channel from an
@@ -968,6 +971,17 @@
     boolean performPrivateCommand(String action, Bundle data);
 
     /**
+     * Perform a handwriting gesture on text.
+     *
+     * @param gesture the gesture to perform
+     * @param executor if the caller passes a non-null consumer  TODO(b/210039666): complete doc
+     * @param consumer if the caller passes a non-null receiver, the editor must invoke this
+     */
+    default void performHandwritingGesture(
+            @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor,
+            @Nullable IntConsumer consumer) {}
+
+    /**
      * The editor is requested to call
      * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} at
      * once, as soon as possible, regardless of cursor/anchor position changes. This flag can be
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 7a88a75..56beddf 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -16,6 +16,7 @@
 
 package android.view.inputmethod;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -25,6 +26,9 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.util.concurrent.Executor;
+import java.util.function.IntConsumer;
+
 /**
  * <p>Wrapper class for proxying calls to another InputConnection.  Subclass and have fun!
  */
@@ -323,6 +327,17 @@
      * @throws NullPointerException if the target is {@code null}.
      */
     @Override
+    public void performHandwritingGesture(
+            @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor,
+            @Nullable IntConsumer consumer) {
+        mTarget.performHandwritingGesture(gesture, executor, consumer);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
     public boolean requestCursorUpdates(int cursorUpdateMode) {
         return mTarget.requestCursorUpdates(cursorUpdateMode);
     }
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index bfe6ae6..978bfc7 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -410,4 +410,11 @@
         // intentionally empty
     }
 
+    /**
+     * Remove stylus handwriting window.
+     * @hide
+     */
+    default void removeStylusHandwritingWindow() {
+        // intentionally empty
+    }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt b/core/java/android/view/inputmethod/InsertGesture.aidl
similarity index 79%
copy from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
copy to core/java/android/view/inputmethod/InsertGesture.aidl
index 8dde897..9cdb14a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
+++ b/core/java/android/view/inputmethod/InsertGesture.aidl
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spaprivileged.framework.app
+package android.view.inputmethod;
 
-import android.content.pm.ApplicationInfo
-
-interface AppRecord {
-    val app: ApplicationInfo
-}
+parcelable InsertGesture;
\ No newline at end of file
diff --git a/core/java/android/view/inputmethod/InsertGesture.java b/core/java/android/view/inputmethod/InsertGesture.java
new file mode 100644
index 0000000..2cf015a
--- /dev/null
+++ b/core/java/android/view/inputmethod/InsertGesture.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.graphics.PointF;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import java.util.Objects;
+
+/**
+ * A sub-class of {@link HandwritingGesture} for inserting text at the defined insertion point.
+ * This class holds the information required for insertion of text in
+ * toolkit widgets like {@link TextView}.
+ */
+public final class InsertGesture extends HandwritingGesture implements Parcelable {
+
+    private String mTextToInsert;
+    private PointF mPoint;
+
+    private InsertGesture(String text, PointF point, String fallbackText) {
+        mPoint = point;
+        mTextToInsert = text;
+        mFallbackText = fallbackText;
+    }
+
+    private InsertGesture(final Parcel source) {
+        mFallbackText = source.readString8();
+        mTextToInsert = source.readString8();
+        mPoint = source.readTypedObject(PointF.CREATOR);
+    }
+
+    /** Returns the text that will be inserted at {@link #getInsertionPoint()} **/
+    @Nullable
+    public String getTextToInsert() {
+        return mTextToInsert;
+    }
+
+    /**
+     * Returns the insertion point {@link PointF} (in screen coordinates) where
+     * {@link #getTextToInsert()} will be inserted.
+     */
+    @Nullable
+    public PointF getInsertionPoint() {
+        return mPoint;
+    }
+
+    /**
+     * Builder for {@link InsertGesture}. This class is not designed to be thread-safe.
+     */
+    public static final class Builder {
+        private String mText;
+        private PointF mPoint;
+        private String mFallbackText;
+
+        /** set the text that will be inserted at {@link #setInsertionPoint(PointF)} **/
+        @NonNull
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setTextToInsert(@NonNull String text) {
+            mText = text;
+            return this;
+        }
+
+        /**
+         * Sets the insertion point (in screen coordinates) where {@link #setTextToInsert(String)}
+         * should be inserted.
+         */
+        @NonNull
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setInsertionPoint(@NonNull PointF point) {
+            mPoint = point;
+            return this;
+        }
+
+        /**
+         * Set fallback text that will be committed at current cursor position if there is no
+         * applicable text beneath the area of gesture.
+         * @param fallbackText text to set
+         */
+        @NonNull
+        public Builder setFallbackText(@Nullable String fallbackText) {
+            mFallbackText = fallbackText;
+            return this;
+        }
+
+        /**
+         * @return {@link InsertGesture} using parameters in this {@link InsertGesture.Builder}.
+         * @throws IllegalArgumentException if one or more positional parameters are not specified.
+         */
+        @NonNull
+        public InsertGesture build() {
+            if (mPoint == null) {
+                throw new IllegalArgumentException("Insertion point must be set.");
+            }
+            if (TextUtils.isEmpty(mText)) {
+                throw new IllegalArgumentException("Text to insert must be non-empty.");
+            }
+            return new InsertGesture(mText, mPoint, mFallbackText);
+        }
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final @android.annotation.NonNull Creator<InsertGesture> CREATOR =
+            new Creator<InsertGesture>() {
+        @Override
+        public InsertGesture createFromParcel(Parcel source) {
+            return new InsertGesture(source);
+        }
+
+        @Override
+        public InsertGesture[] newArray(int size) {
+            return new InsertGesture[size];
+        }
+    };
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPoint, mTextToInsert, mFallbackText);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof InsertGesture)) return false;
+
+        InsertGesture that = (InsertGesture) o;
+
+        if (!Objects.equals(mFallbackText, that.mFallbackText)) return false;
+        if (!Objects.equals(mTextToInsert, that.mTextToInsert)) return false;
+        return Objects.equals(mPoint, that.mPoint);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mFallbackText);
+        dest.writeString8(mTextToInsert);
+        dest.writeTypedObject(mPoint, flags);
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt b/core/java/android/view/inputmethod/SelectGesture.aidl
similarity index 79%
copy from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
copy to core/java/android/view/inputmethod/SelectGesture.aidl
index 8dde897..65da4f3 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
+++ b/core/java/android/view/inputmethod/SelectGesture.aidl
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spaprivileged.framework.app
+package android.view.inputmethod;
 
-import android.content.pm.ApplicationInfo
-
-interface AppRecord {
-    val app: ApplicationInfo
-}
+parcelable SelectGesture;
\ No newline at end of file
diff --git a/core/java/android/view/inputmethod/SelectGesture.java b/core/java/android/view/inputmethod/SelectGesture.java
new file mode 100644
index 0000000..f3cd71e
--- /dev/null
+++ b/core/java/android/view/inputmethod/SelectGesture.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.graphics.RectF;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.widget.TextView;
+
+import java.util.Objects;
+
+/**
+ * A sub-class of {@link HandwritingGesture} for selecting an area of text.
+ * This class holds the information required for selection of text in
+ * toolkit widgets like {@link TextView}.
+ */
+public final class SelectGesture extends HandwritingGesture implements Parcelable {
+
+    private @Granularity int mGranularity;
+    private RectF mArea;
+
+    private SelectGesture(int granularity, RectF area, String fallbackText) {
+        mArea = area;
+        mGranularity = granularity;
+        mFallbackText = fallbackText;
+    }
+
+    private SelectGesture(@NonNull Parcel source) {
+        mFallbackText = source.readString8();
+        mGranularity = source.readInt();
+        mArea = source.readTypedObject(RectF.CREATOR);
+    }
+
+    /**
+     * Returns Granular level on which text should be operated.
+     * @see #GRANULARITY_CHARACTER
+     * @see #GRANULARITY_WORD
+     */
+    @Granularity
+    public int getGranularity() {
+        return mGranularity;
+    }
+
+    /**
+     * Returns the Selection area {@link RectF} in screen coordinates.
+     *
+     * Getter for selection area set with {@link Builder#setSelectionArea(RectF)}. {@code null}
+     * if area was not set.
+     */
+    @NonNull
+    public RectF getSelectionArea() {
+        return mArea;
+    }
+
+
+    /**
+     * Builder for {@link SelectGesture}. This class is not designed to be thread-safe.
+     */
+    public static final class Builder {
+        private int mGranularity;
+        private RectF mArea;
+        private String mFallbackText;
+
+        /**
+         * Define text selection granularity. Intersecting words/characters will be
+         * included in the operation.
+         * @param granularity {@link HandwritingGesture#GRANULARITY_WORD} or
+         * {@link HandwritingGesture#GRANULARITY_CHARACTER}.
+         * @return {@link Builder}.
+         */
+        @NonNull
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setGranularity(@Granularity int granularity) {
+            mGranularity = granularity;
+            return this;
+        }
+
+        /**
+         * Set rectangular single/multiline text selection area intersecting with text.
+         *
+         * The resulting selection would be performed for all text intersecting rectangle. The
+         * selection includes the first word/character in the  rectangle, and the last
+         * word/character in the rectangle, and includes  everything in between even if it's not
+         * in the rectangle.
+         *
+         * Intersection is determined using
+         * {@link #setGranularity(int)}. e.g. {@link HandwritingGesture#GRANULARITY_WORD} includes
+         * all the words with their width/height center included in the selection rectangle.
+         * @param area {@link RectF} (in screen coordinates) for which text will be selection.
+         */
+        @NonNull
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setSelectionArea(@NonNull RectF area) {
+            mArea = area;
+            return this;
+        }
+
+        /**
+         * Set fallback text that will be committed at current cursor position if there is no
+         * applicable text beneath the area of gesture.
+         * @param fallbackText text to set
+         */
+        @NonNull
+        public Builder setFallbackText(@Nullable String fallbackText) {
+            mFallbackText = fallbackText;
+            return this;
+        }
+
+        /**
+         * @return {@link SelectGesture} using parameters in this {@link InsertGesture.Builder}.
+         * @throws IllegalArgumentException if one or more positional parameters are not specified.
+         */
+        @NonNull
+        public SelectGesture build() {
+            if (mArea == null || mArea.isEmpty()) {
+                throw new IllegalArgumentException("Selection area must be set.");
+            }
+            if (mGranularity <= GRANULARITY_UNDEFINED) {
+                throw new IllegalArgumentException("Selection granularity must be set.");
+            }
+            return new SelectGesture(mGranularity, mArea, mFallbackText);
+        }
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<SelectGesture> CREATOR =
+            new Parcelable.Creator<SelectGesture>() {
+        @Override
+        public SelectGesture createFromParcel(Parcel source) {
+            return new SelectGesture(source);
+        }
+
+        @Override
+        public SelectGesture[] newArray(int size) {
+            return new SelectGesture[size];
+        }
+    };
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mGranularity, mArea, mFallbackText);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof SelectGesture)) return false;
+
+        SelectGesture that = (SelectGesture) o;
+
+        if (mGranularity != that.mGranularity) return false;
+        if (!Objects.equals(mFallbackText, that.mFallbackText)) return false;
+        return Objects.equals(mArea, that.mArea);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mFallbackText);
+        dest.writeInt(mGranularity);
+        dest.writeTypedObject(mArea, flags);
+    }
+}
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index e243aae..efe3fd4 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -31,6 +31,7 @@
 import android.view.ContextMenu.ContextMenuInfo;
 import android.view.SoundEffectConstants;
 import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ExpandableListConnector.PositionMetadata;
 
 import com.android.internal.R;
@@ -1144,6 +1145,24 @@
         return new ExpandableListContextMenuInfo(view, packedPosition, id);
     }
 
+    /** @hide */
+    @Override
+    public void onInitializeAccessibilityNodeInfoForItem(
+            View view, int position, AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfoForItem(view, position, info);
+
+        final PositionMetadata metadata = mConnector.getUnflattenedPos(position);
+        if (metadata.position.type == ExpandableListPosition.GROUP) {
+            if (isGroupExpanded(metadata.position.groupPos)) {
+                info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE);
+            } else {
+                info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
+            }
+        }
+
+        metadata.recycle();
+    }
+
     /**
      * Gets the ID of the group or child at the given <code>position</code>.
      * This is useful since there is no ListAdapter ID -> ExpandableListAdapter
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index fa84407..7314ad8 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -732,6 +732,10 @@
      * @hide Pending API consideration. Currently only used internally by the system.
      */
     protected boolean hasDividerBeforeChildAt(int childIndex) {
+        if (mShowDividers == SHOW_DIVIDER_NONE) {
+            // Short-circuit to save iteration over child views.
+            return false;
+        }
         if (childIndex == getVirtualChildCount()) {
             // Check whether the end divider should draw.
             return (mShowDividers & SHOW_DIVIDER_END) != 0;
@@ -746,6 +750,24 @@
     }
 
     /**
+     * Determines whether or not there's a divider after a specified child index.
+     *
+     * @param childIndex Index of child to check for following divider
+     * @return true if there should be a divider after the child at childIndex
+     */
+    private boolean hasDividerAfterChildAt(int childIndex) {
+        if (mShowDividers == SHOW_DIVIDER_NONE) {
+            // Short-circuit to save iteration over child views.
+            return false;
+        }
+        if (allViewsAreGoneAfter(childIndex)) {
+            // This is the last view that's not gone, check if end divider is enabled.
+            return (mShowDividers & SHOW_DIVIDER_END) != 0;
+        }
+        return (mShowDividers & SHOW_DIVIDER_MIDDLE) != 0;
+    }
+
+    /**
      * Checks whether all (virtual) child views before the given index are gone.
      */
     private boolean allViewsAreGoneBefore(int childIndex) {
@@ -759,6 +781,20 @@
     }
 
     /**
+     * Checks whether all (virtual) child views after the given index are gone.
+     */
+    private boolean allViewsAreGoneAfter(int childIndex) {
+        final int count = getVirtualChildCount();
+        for (int i = childIndex + 1; i < count; i++) {
+            final View child = getVirtualChildAt(i);
+            if (child != null && child.getVisibility() != GONE) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
      * Measures the children when the orientation of this LinearLayout is set
      * to {@link #VERTICAL}.
      *
@@ -1295,6 +1331,7 @@
         if (useLargestChild &&
                 (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED)) {
             mTotalLength = 0;
+            nonSkippedChildCount = 0;
 
             for (int i = 0; i < count; ++i) {
                 final View child = getVirtualChildAt(i);
@@ -1308,6 +1345,11 @@
                     continue;
                 }
 
+                nonSkippedChildCount++;
+                if (hasDividerBeforeChildAt(i)) {
+                    mTotalLength += mDividerWidth;
+                }
+
                 final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                         child.getLayoutParams();
                 if (isExactly) {
@@ -1319,6 +1361,10 @@
                             lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
                 }
             }
+
+            if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) {
+                mTotalLength += mDividerWidth;
+            }
         }
 
         // Add in our padding
@@ -1347,6 +1393,7 @@
             maxHeight = -1;
 
             mTotalLength = 0;
+            nonSkippedChildCount = 0;
 
             for (int i = 0; i < count; ++i) {
                 final View child = getVirtualChildAt(i);
@@ -1354,6 +1401,11 @@
                     continue;
                 }
 
+                nonSkippedChildCount++;
+                if (hasDividerBeforeChildAt(i)) {
+                    mTotalLength += mDividerWidth;
+                }
+
                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                 final float childWeight = lp.weight;
                 if (childWeight > 0) {
@@ -1423,6 +1475,10 @@
                 }
             }
 
+            if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) {
+                mTotalLength += mDividerWidth;
+            }
+
             // Add in our padding
             mTotalLength += mPaddingLeft + mPaddingRight;
             // TODO: Should we update widthSize with the new total length?
@@ -1810,7 +1866,13 @@
                         break;
                 }
 
-                if (hasDividerBeforeChildAt(childIndex)) {
+                if (isLayoutRtl) {
+                    // Because rtl rendering occurs in the reverse direction, we need to check
+                    // after the child rather than before (since after=left in this context)
+                    if (hasDividerAfterChildAt(childIndex)) {
+                        childLeft += mDividerWidth;
+                    }
+                } else if (hasDividerBeforeChildAt(childIndex)) {
                     childLeft += mDividerWidth;
                 }
 
diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java
index f946fe6..ec0d862 100644
--- a/core/java/android/widget/RatingBar.java
+++ b/core/java/android/widget/RatingBar.java
@@ -16,17 +16,22 @@
 
 package android.widget;
 
+import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.shapes.RectShape;
 import android.graphics.drawable.shapes.Shape;
 import android.util.AttributeSet;
+import android.util.PluralsMessageFormatter;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.inspector.InspectableProperty;
 
 import com.android.internal.R;
 
+import java.util.HashMap;
+
+
 /**
  * A RatingBar is an extension of SeekBar and ProgressBar that shows a rating in
  * stars. The user can touch/drag or use arrow keys to set the rating when using
@@ -53,6 +58,20 @@
 public class RatingBar extends AbsSeekBar {
 
     /**
+     * Key used for generating Text-to-Speech output regarding the current star rating.
+     * @hide
+     */
+    @TestApi
+    public static final String PLURALS_RATING = "rating";
+
+    /**
+     * Key used for generating Text-to-Speech output regarding the maximum star count.
+     * @hide
+     */
+    @TestApi
+    public static final String PLURALS_MAX = "max";
+
+    /**
      * A callback that notifies clients when the rating has been changed. This
      * includes changes that were initiated by the user through a touch gesture
      * or arrow key/trackball as well as changes that were initiated
@@ -354,6 +373,16 @@
         if (canUserSetProgress()) {
             info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_PROGRESS);
         }
+
+        final float scaledMax = getMax() * getStepSize();
+        final HashMap<String, Object> params = new HashMap();
+        params.put(PLURALS_RATING, getRating());
+        params.put(PLURALS_MAX, scaledMax);
+        info.setStateDescription(PluralsMessageFormatter.format(
+                getContext().getResources(),
+                params,
+                R.string.rating_label
+        ));
     }
 
     @Override
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1c7c582..cd1c23c 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4891,24 +4891,24 @@
      *
      * <p>Line-break style specifies the line-break strategies that can be used
      * for text wrapping. The line-break style affects rule-based line breaking
-     * by specifying the strictness of line-breaking rules.</p>
+     * by specifying the strictness of line-breaking rules.
      *
-     * <p>The following are types of line-break styles:</p>
+     * <p>The following are types of line-break styles:
      * <ul>
-     *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE}</li>
-     *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL}</li>
-     *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}</li>
+     *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE}
+     *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL}
+     *   <li>{@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}
      * </ul>
      *
      * <p>The default line-break style is
      * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, which specifies that no
-     * line-breaking rules are used.</p>
+     * line-breaking rules are used.
      *
      * <p>See the
      * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
-     * line-break property</a> for more information.</p>
+     * line-break property</a> for more information.
      *
-     * @param lineBreakStyle The line break style for the text.
+     * @param lineBreakStyle The line-break style for the text.
      */
     public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) {
         if (mLineBreakStyle != lineBreakStyle) {
@@ -4927,17 +4927,17 @@
      * <p>The line-break word style affects dictionary-based line breaking by
      * providing phrase-based line-breaking opportunities. Use
      * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE} to specify
-     * phrase-based line breaking.</p>
+     * phrase-based line breaking.
      *
      * <p>The default line-break word style is
      * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, which specifies that
-     * no line-breaking word style is used.</p>
+     * no line-breaking word style is used.
      *
      * <p>See the
      * <a href="https://www.w3.org/TR/css-text-3/#word-break-property" class="external">
-     * word-break property</a> for more information.</p>
+     * word-break property</a> for more information.
      *
-     * @param lineBreakWordStyle The line break word style for the text.
+     * @param lineBreakWordStyle The line-break word style for the text.
      */
     public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
         mUserSpeficiedLineBreakwordStyle = true;
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 8407d10..884ca77 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -16,8 +16,10 @@
 
 package android.window;
 
+import android.os.IBinder;
 import android.view.RemoteAnimationDefinition;
 import android.window.ITaskFragmentOrganizer;
+import android.window.WindowContainerTransaction;
 
 /** @hide */
 interface ITaskFragmentOrganizerController {
@@ -46,8 +48,15 @@
     void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer, int taskId);
 
     /**
-      * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
-      * only occupies a portion of Task bounds.
-      */
+     * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
+     * only occupies a portion of Task bounds.
+     */
     boolean isActivityEmbedded(in IBinder activityToken);
+
+    /**
+     * Notifies the server that the organizer has finished handling the given transaction. The
+     * server should apply the given {@link WindowContainerTransaction} for the necessary changes.
+     */
+    void onTransactionHandled(in ITaskFragmentOrganizer organizer, in IBinder transactionToken,
+        in WindowContainerTransaction wct);
 }
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index cd15df84..7359172 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -26,7 +26,6 @@
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.content.Intent;
 import android.content.res.Configuration;
@@ -141,6 +140,28 @@
     }
 
     /**
+     * Notifies the server that the organizer has finished handling the given transaction. The
+     * server should apply the given {@link WindowContainerTransaction} for the necessary changes.
+     *
+     * @param transactionToken  {@link TaskFragmentTransaction#getTransactionToken()} from
+     *                          {@link #onTransactionReady(TaskFragmentTransaction)}
+     * @param wct               {@link WindowContainerTransaction} that the server should apply for
+     *                          update of the transaction.
+     * @see com.android.server.wm.WindowOrganizerController#enforceTaskPermission for permission
+     * requirement.
+     * @hide
+     */
+    public void onTransactionHandled(@NonNull IBinder transactionToken,
+            @NonNull WindowContainerTransaction wct) {
+        wct.setTaskFragmentOrganizer(mInterface);
+        try {
+            getController().onTransactionHandled(mInterface, transactionToken, wct);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Called when a TaskFragment is created and organized by this organizer.
      *
      * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed. No
@@ -227,12 +248,8 @@
     /**
      * Called when the transaction is ready so that the organizer can update the TaskFragments based
      * on the changes in transaction.
-     * Note: {@link WindowOrganizer#applyTransaction} permission requirement is conditional for
-     * {@link TaskFragmentOrganizer}.
-     * @see com.android.server.wm.WindowOrganizerController#enforceTaskPermission
      * @hide
      */
-    @SuppressLint("AndroidFrameworkRequiresPermission")
     public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
@@ -274,8 +291,9 @@
                             "Unknown TaskFragmentEvent=" + change.getType());
             }
         }
-        // TODO(b/240519866): notify TaskFragmentOrganizerController that the transition is done.
-        applyTransaction(wct);
+
+        // Notify the server, and the server should apply the WindowContainerTransaction.
+        onTransactionHandled(transaction.getTransactionToken(), wct);
     }
 
     @Override
diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java
index 07e8e8c..84a5fea 100644
--- a/core/java/android/window/TaskFragmentTransaction.java
+++ b/core/java/android/window/TaskFragmentTransaction.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.content.Intent;
 import android.content.res.Configuration;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -41,19 +42,31 @@
  */
 public final class TaskFragmentTransaction implements Parcelable {
 
+    /** Unique token to represent this transaction. */
+    private final IBinder mTransactionToken;
+
+    /** Changes in this transaction. */
     private final ArrayList<Change> mChanges = new ArrayList<>();
 
-    public TaskFragmentTransaction() {}
+    public TaskFragmentTransaction() {
+        mTransactionToken = new Binder();
+    }
 
     private TaskFragmentTransaction(Parcel in) {
+        mTransactionToken = in.readStrongBinder();
         in.readTypedList(mChanges, Change.CREATOR);
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongBinder(mTransactionToken);
         dest.writeTypedList(mChanges);
     }
 
+    public IBinder getTransactionToken() {
+        return mTransactionToken;
+    }
+
     /** Adds a {@link Change} to this transaction. */
     public void addChange(@Nullable Change change) {
         if (change != null) {
@@ -74,7 +87,9 @@
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
-        sb.append("TaskFragmentTransaction{changes=[");
+        sb.append("TaskFragmentTransaction{token=");
+        sb.append(mTransactionToken);
+        sb.append(" changes=[");
         for (int i = 0; i < mChanges.size(); ++i) {
             if (i > 0) {
                 sb.append(',');
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index b263b08..dc1f612 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -119,6 +119,12 @@
     /** The container is going to show IME on its task after the transition. */
     public static final int FLAG_WILL_IME_SHOWN = 1 << 11;
 
+    /** The container attaches owner profile thumbnail for cross profile animation. */
+    public static final int FLAG_CROSS_PROFILE_OWNER_THUMBNAIL = 1 << 12;
+
+    /** The container attaches work profile thumbnail for cross profile animation. */
+    public static final int FLAG_CROSS_PROFILE_WORK_THUMBNAIL = 1 << 13;
+
     /** @hide */
     @IntDef(prefix = { "FLAG_" }, value = {
             FLAG_NONE,
@@ -508,6 +514,11 @@
             return mFlags;
         }
 
+        /** Whether the given change flags has included in this change. */
+        public boolean hasFlags(@ChangeFlags int flags) {
+            return (mFlags & flags) != 0;
+        }
+
         /**
          * @return the bounds of the container before the change. It may be empty if the container
          * is coming into existence.
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index f67e785..c8bc204 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1283,9 +1283,6 @@
         }
 
         if (target != null) {
-            if (intent != null && isLaunchingTargetInOtherProfile()) {
-                prepareIntentForCrossProfileLaunch(intent);
-            }
             safelyStartActivity(target);
 
             // Rely on the ActivityManager to pop up a dialog regarding app suspension
@@ -1298,15 +1295,6 @@
         return true;
     }
 
-    private void prepareIntentForCrossProfileLaunch(Intent intent) {
-        intent.fixUris(UserHandle.myUserId());
-    }
-
-    private boolean isLaunchingTargetInOtherProfile() {
-        return mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier()
-                != UserHandle.myUserId();
-    }
-
     @VisibleForTesting
     public void safelyStartActivity(TargetInfo cti) {
         // We're dispatching intents that might be coming from legacy apps, so
@@ -1513,9 +1501,6 @@
 
         findViewById(R.id.button_open).setOnClickListener(v -> {
             Intent intent = otherProfileResolveInfo.getResolvedIntent();
-            if (intent != null) {
-                prepareIntentForCrossProfileLaunch(intent);
-            }
             safelyStartActivityAsUser(otherProfileResolveInfo,
                     inactiveAdapter.mResolverListController.getUserHandle());
             finish();
diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
index fcdcb2d..8f6bc43 100644
--- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
@@ -217,7 +217,11 @@
             case TYPE_HEADER_ALL_OTHERS:
                 TextView textView = (TextView) itemView;
                 if (itemType == TYPE_HEADER_SUGGESTED) {
-                    setTextTo(textView, R.string.language_picker_section_suggested);
+                   if (mCountryMode) {
+                        setTextTo(textView, R.string.language_picker_regions_section_suggested);
+                    } else {
+                        setTextTo(textView, R.string.language_picker_section_suggested);
+                    }
                 } else {
                     if (mCountryMode) {
                         setTextTo(textView, R.string.region_picker_section_all);
diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
index 96cc5e1b..5f4a9cd 100644
--- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
+++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
@@ -172,12 +172,14 @@
 
     @Override
     public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
+        prepareIntentForCrossProfileLaunch(mResolvedIntent, userId);
         activity.startActivityAsCaller(mResolvedIntent, options, false, userId);
         return true;
     }
 
     @Override
     public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
+        prepareIntentForCrossProfileLaunch(mResolvedIntent, user.getIdentifier());
         activity.startActivityAsUser(mResolvedIntent, options, user);
         return false;
     }
@@ -222,6 +224,13 @@
         }
     };
 
+    private static void prepareIntentForCrossProfileLaunch(Intent intent, int targetUserId) {
+        final int currentUserId = UserHandle.myUserId();
+        if (targetUserId != currentUserId) {
+            intent.fixUris(currentUserId);
+        }
+    }
+
     private DisplayResolveInfo(Parcel in) {
         mDisplayLabel = in.readCharSequence();
         mExtendedInfo = in.readCharSequence();
diff --git a/core/java/com/android/internal/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
index 5db2e84..9182d1d 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
@@ -40,7 +40,6 @@
         IBinder token;
         IInputMethodPrivilegedOperations privilegedOperations;
         int configChanges;
-        boolean stylusHandWritingSupported;
         int navigationBarFlags;
     }
 
@@ -86,4 +85,6 @@
     void initInkWindow();
 
     void finishStylusHandwriting();
+
+    void removeStylusHandwritingWindow();
 }
diff --git a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
index a7dd6f1..7a219c6 100644
--- a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
+++ b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
@@ -17,11 +17,15 @@
 package com.android.internal.inputmethod;
 
 import android.os.Bundle;
+import android.os.ResultReceiver;
 import android.view.KeyEvent;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.DeleteGesture;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputContentInfo;
+import android.view.inputmethod.InsertGesture;
+import android.view.inputmethod.SelectGesture;
 import android.view.inputmethod.TextAttribute;
 
 import com.android.internal.infra.AndroidFuture;
@@ -86,6 +90,15 @@
     void performPrivateCommand(in InputConnectionCommandHeader header, String action,
             in Bundle data);
 
+    void performHandwritingSelectGesture(in InputConnectionCommandHeader header,
+            in SelectGesture gesture, in ResultReceiver resultReceiver);
+
+    void performHandwritingInsertGesture(in InputConnectionCommandHeader header,
+            in InsertGesture gesture, in ResultReceiver resultReceiver);
+
+    void performHandwritingDeleteGesture(in InputConnectionCommandHeader header,
+            in DeleteGesture gesture, in ResultReceiver resultReceiver);
+
     void setComposingRegion(in InputConnectionCommandHeader header, int start, int end);
 
     void setComposingRegionWithTextAttribute(in InputConnectionCommandHeader header, int start,
diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
index b63ce1b..c65a69f 100644
--- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
@@ -31,6 +31,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.ResultReceiver;
 import android.os.Trace;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
@@ -39,11 +40,15 @@
 import android.view.ViewRootImpl;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.DeleteGesture;
 import android.view.inputmethod.DumpableInputConnection;
 import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.HandwritingGesture;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputContentInfo;
 import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InsertGesture;
+import android.view.inputmethod.SelectGesture;
 import android.view.inputmethod.TextAttribute;
 import android.view.inputmethod.TextSnapshot;
 
@@ -970,6 +975,67 @@
 
     @Dispatching(cancellable = true)
     @Override
+    public void performHandwritingSelectGesture(
+            InputConnectionCommandHeader header, SelectGesture gesture,
+            ResultReceiver resultReceiver) {
+        performHandwritingGestureInternal(header, gesture, resultReceiver);
+    }
+
+    @Dispatching(cancellable = true)
+    @Override
+    public void performHandwritingInsertGesture(
+            InputConnectionCommandHeader header, InsertGesture gesture,
+            ResultReceiver resultReceiver) {
+        performHandwritingGestureInternal(header, gesture, resultReceiver);
+    }
+
+    @Dispatching(cancellable = true)
+    @Override
+    public void performHandwritingDeleteGesture(
+            InputConnectionCommandHeader header, DeleteGesture gesture,
+            ResultReceiver resultReceiver) {
+        performHandwritingGestureInternal(header, gesture, resultReceiver);
+    }
+
+    private <T extends HandwritingGesture> void performHandwritingGestureInternal(
+            InputConnectionCommandHeader header,  T gesture, ResultReceiver resultReceiver) {
+        dispatchWithTracing("performHandwritingGesture", () -> {
+            if (header.mSessionId != mCurrentSessionId.get()) {
+                return;  // cancelled
+            }
+            InputConnection ic = getInputConnection();
+            if (ic == null || !isActive()) {
+                Log.w(TAG, "performHandwritingGesture on inactive InputConnection");
+                return;
+            }
+            // TODO(b/210039666): implement resultReceiver
+            ic.performHandwritingGesture(gesture, null, null);
+        });
+    }
+
+    /**
+     * Dispatches {@link InputConnection#requestCursorUpdates(int)}.
+     *
+     * <p>This method is intended to be called only from {@link InputMethodManager}.</p>
+     * @param cursorUpdateMode the mode for {@link InputConnection#requestCursorUpdates(int, int)}
+     * @param cursorUpdateFilter the filter for
+     *      {@link InputConnection#requestCursorUpdates(int, int)}
+     * @param imeDisplayId displayId on which IME is displayed.
+     */
+    @Dispatching(cancellable = true)
+    public void requestCursorUpdatesFromImm(int cursorUpdateMode, int cursorUpdateFilter,
+            int imeDisplayId) {
+        final int currentSessionId = mCurrentSessionId.get();
+        dispatchWithTracing("requestCursorUpdatesFromImm", () -> {
+            if (currentSessionId != mCurrentSessionId.get()) {
+                return;  // cancelled
+            }
+            requestCursorUpdatesInternal(cursorUpdateMode, cursorUpdateFilter, imeDisplayId);
+        });
+    }
+
+    @Dispatching(cancellable = true)
+    @Override
     public void requestCursorUpdates(InputConnectionCommandHeader header, int cursorUpdateMode,
             int imeDisplayId, AndroidFuture future /* T=Boolean */) {
         dispatchWithTracing("requestCursorUpdates", future, () -> {
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index d066945..fc4e041 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -87,6 +87,7 @@
 import android.annotation.NonNull;
 import android.annotation.UiThread;
 import android.annotation.WorkerThread;
+import android.app.ActivityThread;
 import android.content.Context;
 import android.os.Build;
 import android.os.Handler;
@@ -397,6 +398,11 @@
      */
     @VisibleForTesting
     public InteractionJankMonitor(@NonNull HandlerThread worker) {
+        // Check permission early.
+        DeviceConfig.enforceReadPermission(
+            ActivityThread.currentApplication().getApplicationContext(),
+            DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR);
+
         mRunningTrackers = new SparseArray<>();
         mTimeoutActions = new SparseArray<>();
         mWorker = worker;
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 6909965..962870e 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -17,35 +17,25 @@
 package com.android.internal.os;
 
 import android.annotation.Nullable;
-import android.os.BatteryManager;
-import android.os.BatteryStats.HistoryItem;
-import android.os.BatteryStats.HistoryStepDetails;
-import android.os.BatteryStats.HistoryTag;
+import android.os.BatteryStats;
 import android.os.Parcel;
-import android.os.ParcelFormatException;
-import android.os.Process;
 import android.os.StatFs;
 import android.os.SystemClock;
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Slog;
-import android.util.SparseArray;
-import android.util.TimeUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ParseUtils;
 
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.FilenameFilter;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Supplier;
 
 /**
  * BatteryStatsHistory encapsulates battery history files.
@@ -66,62 +56,57 @@
  * All interfaces in BatteryStatsHistory should only be called by BatteryStatsImpl and protected by
  * locks on BatteryStatsImpl object.
  */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public class BatteryStatsHistory {
     private static final boolean DEBUG = false;
     private static final String TAG = "BatteryStatsHistory";
 
     // Current on-disk Parcel version. Must be updated when the format of the parcelable changes
-    private static final int VERSION = 208;
+    public static final int VERSION = 208;
 
-    private static final String HISTORY_DIR = "battery-history";
-    private static final String FILE_SUFFIX = ".bin";
+    public static final String HISTORY_DIR = "battery-history";
+    public static final String FILE_SUFFIX = ".bin";
     private static final int MIN_FREE_SPACE = 100 * 1024 * 1024;
-
     // Part of initial delta int that specifies the time delta.
-    static final int DELTA_TIME_MASK = 0x7ffff;
-    static final int DELTA_TIME_LONG = 0x7ffff;   // The delta is a following long
-    static final int DELTA_TIME_INT = 0x7fffe;    // The delta is a following int
-    static final int DELTA_TIME_ABS = 0x7fffd;    // Following is an entire abs update.
+    public static final int DELTA_TIME_MASK = 0x7ffff;
+    public static final int DELTA_TIME_LONG = 0x7ffff;   // The delta is a following long
+    public static final int DELTA_TIME_INT = 0x7fffe;    // The delta is a following int
+    public static final int DELTA_TIME_ABS = 0x7fffd;    // Following is an entire abs update.
     // Flag in delta int: a new battery level int follows.
-    static final int DELTA_BATTERY_LEVEL_FLAG = 0x00080000;
+    public static final int DELTA_BATTERY_LEVEL_FLAG  = 0x00080000;
     // Flag in delta int: a new full state and battery status int follows.
-    static final int DELTA_STATE_FLAG = 0x00100000;
+    public static final int DELTA_STATE_FLAG          = 0x00100000;
     // Flag in delta int: a new full state2 int follows.
-    static final int DELTA_STATE2_FLAG = 0x00200000;
+    public static final int DELTA_STATE2_FLAG         = 0x00200000;
     // Flag in delta int: contains a wakelock or wakeReason tag.
-    static final int DELTA_WAKELOCK_FLAG = 0x00400000;
+    public static final int DELTA_WAKELOCK_FLAG       = 0x00400000;
     // Flag in delta int: contains an event description.
-    static final int DELTA_EVENT_FLAG = 0x00800000;
+    public static final int DELTA_EVENT_FLAG          = 0x00800000;
     // Flag in delta int: contains the battery charge count in uAh.
-    static final int DELTA_BATTERY_CHARGE_FLAG = 0x01000000;
+    public static final int DELTA_BATTERY_CHARGE_FLAG = 0x01000000;
     // These upper bits are the frequently changing state bits.
-    static final int DELTA_STATE_MASK = 0xfe000000;
+    public static final int DELTA_STATE_MASK          = 0xfe000000;
     // These are the pieces of battery state that are packed in to the upper bits of
     // the state int that have been packed in to the first delta int.  They must fit
     // in STATE_BATTERY_MASK.
-    static final int STATE_BATTERY_MASK = 0xff000000;
-    static final int STATE_BATTERY_STATUS_MASK = 0x00000007;
-    static final int STATE_BATTERY_STATUS_SHIFT = 29;
-    static final int STATE_BATTERY_HEALTH_MASK = 0x00000007;
-    static final int STATE_BATTERY_HEALTH_SHIFT = 26;
-    static final int STATE_BATTERY_PLUG_MASK = 0x00000003;
-    static final int STATE_BATTERY_PLUG_SHIFT = 24;
+    public static final int STATE_BATTERY_MASK         = 0xff000000;
+    public static final int STATE_BATTERY_STATUS_MASK  = 0x00000007;
+    public static final int STATE_BATTERY_STATUS_SHIFT = 29;
+    public static final int STATE_BATTERY_HEALTH_MASK  = 0x00000007;
+    public static final int STATE_BATTERY_HEALTH_SHIFT = 26;
+    public static final int STATE_BATTERY_PLUG_MASK    = 0x00000003;
+    public static final int STATE_BATTERY_PLUG_SHIFT   = 24;
     // We use the low bit of the battery state int to indicate that we have full details
     // from a battery level change.
-    static final int BATTERY_DELTA_LEVEL_FLAG = 0x00000001;
+    public static final int BATTERY_DELTA_LEVEL_FLAG   = 0x00000001;
     // Flag in history tag index: indicates that this is the first occurrence of this tag,
     // therefore the tag value is written in the parcel
-    static final int TAG_FIRST_OCCURRENCE_FLAG = 0x8000;
+    public static final int TAG_FIRST_OCCURRENCE_FLAG = 0x8000;
 
+    @Nullable
+    private final Supplier<Integer> mMaxHistoryFiles;
     private final Parcel mHistoryBuffer;
-    private final File mSystemDir;
-    private final HistoryStepDetailsCalculator mStepDetailsCalculator;
     private final File mHistoryDir;
-    private final Clock mClock;
-
-    private int mMaxHistoryFiles;
-    private int mMaxHistoryBufferSize;
-
     /**
      * The active history file that the history buffer is backed up into.
      */
@@ -159,77 +144,19 @@
      */
     private int mParcelIndex = 0;
 
-    private final ReentrantLock mWriteLock = new ReentrantLock();
-
-    private final HistoryItem mHistoryCur = new HistoryItem();
-
-    private boolean mHaveBatteryLevel;
-    private boolean mRecordingHistory;
-
-    private static final int HISTORY_TAG_INDEX_LIMIT = 0x7ffe;
-    private static final int MAX_HISTORY_TAG_STRING_LENGTH = 1024;
-
-    private final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<>();
-    private SparseArray<HistoryTag> mHistoryTags;
-    private final HistoryItem mHistoryLastWritten = new HistoryItem();
-    private final HistoryItem mHistoryLastLastWritten = new HistoryItem();
-    private final HistoryItem mHistoryAddTmp = new HistoryItem();
-    private int mNextHistoryTagIdx = 0;
-    private int mNumHistoryTagChars = 0;
-    private int mHistoryBufferLastPos = -1;
-    private int mActiveHistoryStates = 0xffffffff;
-    private int mActiveHistoryStates2 = 0xffffffff;
-    private long mLastHistoryElapsedRealtimeMs = 0;
-    private long mTrackRunningHistoryElapsedRealtimeMs = 0;
-    private long mTrackRunningHistoryUptimeMs = 0;
-    private long mHistoryBaseTimeMs;
-
-    private byte mLastHistoryStepLevel = 0;
-
-    private BatteryStatsHistoryIterator mBatteryStatsHistoryIterator;
-
-    /**
-     * A delegate responsible for computing additional details for a step in battery history.
-     */
-    public interface HistoryStepDetailsCalculator {
-        /**
-         * Returns additional details for the current history step or null.
-         */
-        @Nullable
-        HistoryStepDetails getHistoryStepDetails();
-
-        /**
-         * Resets the calculator to get ready for a new battery session
-         */
-        void clear();
-    }
-
     /**
      * Constructor
      *
-     * @param systemDir            typically /data/system
-     * @param maxHistoryFiles      the largest number of history buffer files to keep
-     * @param maxHistoryBufferSize the most amount of RAM to used for buffering of history steps
+     * @param historyBuffer   The in-memory history buffer.
+     * @param systemDir       typically /data/system
+     * @param maxHistoryFiles the largest number of history buffer files to keep
      */
-    public BatteryStatsHistory(File systemDir, int maxHistoryFiles, int maxHistoryBufferSize,
-            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
-        this(Parcel.obtain(), systemDir, maxHistoryFiles, maxHistoryBufferSize,
-                stepDetailsCalculator, clock);
-        initHistoryBuffer();
-    }
-
-    @VisibleForTesting
     public BatteryStatsHistory(Parcel historyBuffer, File systemDir,
-            int maxHistoryFiles, int maxHistoryBufferSize,
-            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+            Supplier<Integer> maxHistoryFiles) {
         mHistoryBuffer = historyBuffer;
-        mSystemDir = systemDir;
-        mMaxHistoryFiles = maxHistoryFiles;
-        mMaxHistoryBufferSize = maxHistoryBufferSize;
-        mStepDetailsCalculator = stepDetailsCalculator;
-        mClock = clock;
-
         mHistoryDir = new File(systemDir, HISTORY_DIR);
+        mMaxHistoryFiles = maxHistoryFiles;
+
         mHistoryDir.mkdirs();
         if (!mHistoryDir.exists()) {
             Slog.wtf(TAG, "HistoryDir does not exist:" + mHistoryDir.getPath());
@@ -265,81 +192,19 @@
         }
     }
 
-    public BatteryStatsHistory(HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
-        mStepDetailsCalculator = stepDetailsCalculator;
-        mClock = clock;
-
-        mHistoryBuffer = Parcel.obtain();
-        mSystemDir = null;
-        mHistoryDir = null;
-        initHistoryBuffer();
-    }
-
     /**
      * Used when BatteryStatsImpl object is created from deserialization of a parcel,
-     * such as a checkin file.
+     * such as Settings app or checkin file.
+     * @param historyBuffer the history buffer
      */
-    private BatteryStatsHistory(Parcel historyBuffer,
-            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
-        mHistoryBuffer = historyBuffer;
-        mClock = clock;
-        mSystemDir = null;
+    public BatteryStatsHistory(Parcel historyBuffer) {
         mHistoryDir = null;
-        mStepDetailsCalculator = stepDetailsCalculator;
+        mHistoryBuffer = historyBuffer;
+        mMaxHistoryFiles = null;
     }
 
-    private void initHistoryBuffer() {
-        mHistoryBaseTimeMs = 0;
-        mLastHistoryElapsedRealtimeMs = 0;
-        mTrackRunningHistoryElapsedRealtimeMs = 0;
-        mTrackRunningHistoryUptimeMs = 0;
-
-        mHistoryBuffer.setDataSize(0);
-        mHistoryBuffer.setDataPosition(0);
-        mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
-        mHistoryLastLastWritten.clear();
-        mHistoryLastWritten.clear();
-        mHistoryTagPool.clear();
-        mNextHistoryTagIdx = 0;
-        mNumHistoryTagChars = 0;
-        mHistoryBufferLastPos = -1;
-        mActiveHistoryStates = 0xffffffff;
-        mActiveHistoryStates2 = 0xffffffff;
-        if (mStepDetailsCalculator != null) {
-            mStepDetailsCalculator.clear();
-        }
-    }
-
-    /**
-     * Changes the maximum number of history files to be kept.
-     */
-    public void setMaxHistoryFiles(int maxHistoryFiles) {
-        mMaxHistoryFiles = maxHistoryFiles;
-    }
-
-    /**
-     * Changes the maximum size of the history buffer, in bytes.
-     */
-    public void setMaxHistoryBufferSize(int maxHistoryBufferSize) {
-        mMaxHistoryBufferSize = maxHistoryBufferSize;
-    }
-
-    /**
-     * Creates a read-only copy of the battery history.  Does not copy the files stored
-     * in the system directory, so it is not safe while actively writing history.
-     */
-    public BatteryStatsHistory copy() {
-        // Make a copy of battery history to avoid concurrent modification.
-        Parcel historyBuffer = Parcel.obtain();
-        historyBuffer.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
-        return new BatteryStatsHistory(historyBuffer, mSystemDir, 0, 0, null, null);
-    }
-
-    /**
-     * Returns true if this instance only supports reading history.
-     */
-    public boolean isReadOnly() {
-        return mActiveFile == null;
+    public File getHistoryDirectory() {
+        return mHistoryDir;
     }
 
     /**
@@ -356,13 +221,12 @@
 
     /**
      * Create history AtomicFile from file number.
-     *
      * @param num file number.
      * @return AtomicFile object.
      */
     private AtomicFile getFile(int num) {
         return new AtomicFile(
-                new File(mHistoryDir, num + FILE_SUFFIX));
+                new File(mHistoryDir,  num + FILE_SUFFIX));
     }
 
     /**
@@ -370,7 +234,7 @@
      * create next history file.
      */
     public void startNextFile() {
-        if (mMaxHistoryFiles == 0) {
+        if (mMaxHistoryFiles == null) {
             Slog.wtf(TAG, "mMaxHistoryFiles should not be zero when writing history");
             return;
         }
@@ -400,7 +264,7 @@
         // if there are more history files than allowed, delete oldest history files.
         // mMaxHistoryFiles comes from Constants.MAX_HISTORY_FILES and can be updated by GService
         // config at run time.
-        while (mFileNumbers.size() > mMaxHistoryFiles) {
+        while (mFileNumbers.size() > mMaxHistoryFiles.get()) {
             int oldest = mFileNumbers.get(0);
             getFile(oldest).delete();
             mFileNumbers.remove(0);
@@ -408,43 +272,36 @@
     }
 
     /**
-     * Clear history buffer and delete all existing history files. Active history file start from
-     * number 0 again.
+     * Delete all existing history files. Active history file start from number 0 again.
      */
-    public void reset() {
-        if (DEBUG) Slog.i(TAG, "********** CLEARING HISTORY!");
+    public void resetAllFiles() {
         for (Integer i : mFileNumbers) {
             getFile(i).delete();
         }
         mFileNumbers.clear();
         mFileNumbers.add(0);
         setActiveFile(0);
-
-        initHistoryBuffer();
     }
 
     /**
      * Start iterating history files and history buffer.
-     *
      * @return always return true.
      */
-    public BatteryStatsHistoryIterator iterate() {
+    public boolean startIteratingHistory() {
         mRecordCount = 0;
         mCurrentFileIndex = 0;
         mCurrentParcel = null;
         mCurrentParcelEnd = 0;
         mParcelIndex = 0;
-        mBatteryStatsHistoryIterator = new BatteryStatsHistoryIterator(this);
-        return mBatteryStatsHistoryIterator;
+        return true;
     }
 
     /**
      * Finish iterating history files and history buffer.
      */
-    void finishIteratingHistory() {
+    public void finishIteratingHistory() {
         // setDataPosition so mHistoryBuffer Parcel can be written.
         mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
-        mBatteryStatsHistoryIterator = null;
         if (DEBUG) {
             Slog.d(TAG, "Battery history records iterated: " + mRecordCount);
         }
@@ -454,12 +311,11 @@
      * When iterating history files and history buffer, always start from the lowest numbered
      * history file, when reached the mActiveFile (highest numbered history file), do not read from
      * mActiveFile, read from history buffer instead because the buffer has more updated data.
-     *
      * @param out a history item.
      * @return The parcel that has next record. null if finished all history files and history
-     * buffer
+     *         buffer
      */
-    public Parcel getNextParcel(HistoryItem out) {
+    public Parcel getNextParcel(BatteryStats.HistoryItem out) {
         if (mRecordCount == 0) {
             // reset out if it is the first record.
             out.clear();
@@ -467,7 +323,8 @@
         ++mRecordCount;
 
         // First iterate through all records in current parcel.
-        if (mCurrentParcel != null) {
+        if (mCurrentParcel != null)
+        {
             if (mCurrentParcel.dataPosition() < mCurrentParcelEnd) {
                 // There are more records in current parcel.
                 return mCurrentParcel;
@@ -532,8 +389,7 @@
 
     /**
      * Read history file into a parcel.
-     *
-     * @param out  the Parcel read into.
+     * @param out the Parcel read into.
      * @param file the File to read from.
      * @return true if success, false otherwise.
      */
@@ -546,8 +402,8 @@
                 Slog.d(TAG, "readFileToParcel:" + file.getBaseFile().getPath()
                         + " duration ms:" + (SystemClock.uptimeMillis() - start));
             }
-        } catch (Exception e) {
-            Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e);
+        } catch(Exception e) {
+            Slog.e(TAG, "Error reading file "+ file.getBaseFile().getPath(), e);
             return false;
         }
         out.unmarshall(raw, 0, raw.length);
@@ -557,7 +413,6 @@
 
     /**
      * Skip the header part of history parcel.
-     *
      * @param p history parcel to skip head.
      * @return true if version match, false if not.
      */
@@ -573,68 +428,18 @@
     }
 
     /**
-     * Writes the battery history contents for persistence.
-     */
-    public void writeSummaryToParcel(Parcel out, boolean inclHistory) {
-        out.writeBoolean(inclHistory);
-        if (inclHistory) {
-            writeToParcel(out);
-        }
-
-        out.writeInt(mHistoryTagPool.size());
-        for (Map.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) {
-            HistoryTag tag = ent.getKey();
-            out.writeInt(ent.getValue());
-            out.writeString(tag.string);
-            out.writeInt(tag.uid);
-        }
-    }
-
-    /**
-     * Reads battery history contents from a persisted parcel.
-     */
-    public void readSummaryFromParcel(Parcel in) {
-        boolean inclHistory = in.readBoolean();
-        if (inclHistory) {
-            readFromParcel(in);
-        }
-
-        mHistoryTagPool.clear();
-        mNextHistoryTagIdx = 0;
-        mNumHistoryTagChars = 0;
-
-        int numTags = in.readInt();
-        for (int i = 0; i < numTags; i++) {
-            int idx = in.readInt();
-            String str = in.readString();
-            int uid = in.readInt();
-            HistoryTag tag = new HistoryTag();
-            tag.string = str;
-            tag.uid = uid;
-            tag.poolIdx = idx;
-            mHistoryTagPool.put(tag, idx);
-            if (idx >= mNextHistoryTagIdx) {
-                mNextHistoryTagIdx = idx + 1;
-            }
-            mNumHistoryTagChars += tag.string.length() + 1;
-        }
-    }
-
-    /**
      * Read all history files and serialize into a big Parcel.
      * Checkin file calls this method.
      *
      * @param out the output parcel
      */
     public void writeToParcel(Parcel out) {
-        writeHistoryBuffer(out);
         writeToParcel(out, false /* useBlobs */);
     }
 
     /**
      * This is for Settings app, when Settings app receives big history parcel, it call
      * this method to parse it into list of parcels.
-     *
      * @param out the output parcel
      */
     public void writeToBatteryUsageStatsParcel(Parcel out) {
@@ -645,13 +450,13 @@
     private void writeToParcel(Parcel out, boolean useBlobs) {
         final long start = SystemClock.uptimeMillis();
         out.writeInt(mFileNumbers.size() - 1);
-        for (int i = 0; i < mFileNumbers.size() - 1; i++) {
+        for(int i = 0;  i < mFileNumbers.size() - 1; i++) {
             AtomicFile file = getFile(mFileNumbers.get(i));
             byte[] raw = new byte[0];
             try {
                 raw = file.readFully();
-            } catch (Exception e) {
-                Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e);
+            } catch(Exception e) {
+                Slog.e(TAG, "Error reading file "+ file.getBaseFile().getPath(), e);
             }
             if (useBlobs) {
                 out.writeBlob(raw);
@@ -675,55 +480,17 @@
         Parcel historyBuffer = Parcel.obtain();
         historyBuffer.unmarshall(historyBlob, 0, historyBlob.length);
 
-        BatteryStatsHistory history = new BatteryStatsHistory(historyBuffer, null,
-                Clock.SYSTEM_CLOCK);
+        BatteryStatsHistory history = new BatteryStatsHistory(historyBuffer);
         history.readFromParcel(in, true /* useBlobs */);
         return history;
     }
 
     /**
-     * Read history from a check-in file.
-     */
-    public boolean readSummary() {
-        if (mActiveFile == null) {
-            Slog.w(TAG, "readSummary: no history file associated with this instance");
-            return false;
-        }
-
-        Parcel parcel = Parcel.obtain();
-        try {
-            final long start = SystemClock.uptimeMillis();
-            if (mActiveFile.exists()) {
-                byte[] raw = mActiveFile.readFully();
-                if (raw.length > 0) {
-                    parcel.unmarshall(raw, 0, raw.length);
-                    parcel.setDataPosition(0);
-                    readHistoryBuffer(parcel);
-                }
-                if (DEBUG) {
-                    Slog.d(TAG, "read history file::"
-                            + mActiveFile.getBaseFile().getPath()
-                            + " bytes:" + raw.length + " took ms:" + (SystemClock.uptimeMillis()
-                            - start));
-                }
-            }
-        } catch (Exception e) {
-            Slog.e(TAG, "Error reading battery history", e);
-            reset();
-            return false;
-        } finally {
-            parcel.recycle();
-        }
-        return true;
-    }
-
-    /**
      * This is for the check-in file, which has all history files embedded.
      *
      * @param in the input parcel.
      */
     public void readFromParcel(Parcel in) {
-        readHistoryBuffer(in);
         readFromParcel(in, false /* useBlobs */);
     }
 
@@ -731,7 +498,7 @@
         final long start = SystemClock.uptimeMillis();
         mHistoryParcels = new ArrayList<>();
         final int count = in.readInt();
-        for (int i = 0; i < count; i++) {
+        for(int i = 0; i < count; i++) {
             byte[] temp = useBlobs ? in.readBlob() : in.createByteArray();
             if (temp == null || temp.length == 0) {
                 continue;
@@ -754,12 +521,10 @@
         return stats.getAvailableBytes() > MIN_FREE_SPACE;
     }
 
-    @VisibleForTesting
     public List<Integer> getFilesNumbers() {
         return mFileNumbers;
     }
 
-    @VisibleForTesting
     public AtomicFile getActiveFile() {
         return mActiveFile;
     }
@@ -769,972 +534,15 @@
      */
     public int getHistoryUsedSize() {
         int ret = 0;
-        for (int i = 0; i < mFileNumbers.size() - 1; i++) {
+        for(int i = 0; i < mFileNumbers.size() - 1; i++) {
             ret += getFile(mFileNumbers.get(i)).getBaseFile().length();
         }
         ret += mHistoryBuffer.dataSize();
         if (mHistoryParcels != null) {
-            for (int i = 0; i < mHistoryParcels.size(); i++) {
+            for(int i = 0; i < mHistoryParcels.size(); i++) {
                 ret += mHistoryParcels.get(i).dataSize();
             }
         }
         return ret;
     }
-
-    /**
-     * Enables/disables recording of history.  When disabled, all "record*" calls are a no-op.
-     */
-    public void setHistoryRecordingEnabled(boolean enabled) {
-        mRecordingHistory = enabled;
-    }
-
-    /**
-     * Returns true if history recording is enabled.
-     */
-    public boolean isRecordingHistory() {
-        return mRecordingHistory;
-    }
-
-    /**
-     * Forces history recording regardless of charging state.
-     */
-    @VisibleForTesting
-    public void forceRecordAllHistory() {
-        mHaveBatteryLevel = true;
-        mRecordingHistory = true;
-    }
-
-    /**
-     * Starts a history buffer by recording the current wall-clock time.
-     */
-    public void startRecordingHistory(final long elapsedRealtimeMs, final long uptimeMs,
-            boolean reset) {
-        mRecordingHistory = true;
-        mHistoryCur.currentTime = mClock.currentTimeMillis();
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur,
-                reset ? HistoryItem.CMD_RESET : HistoryItem.CMD_CURRENT_TIME);
-        mHistoryCur.currentTime = 0;
-    }
-
-    /**
-     * Prepares to continue recording after restoring previous history from persistent storage.
-     */
-    public void continueRecordingHistory() {
-        if (mHistoryBuffer.dataPosition() <= 0 && mFileNumbers.size() <= 1) {
-            return;
-        }
-
-        mRecordingHistory = true;
-        final long elapsedRealtimeMs = mClock.elapsedRealtime();
-        final long uptimeMs = mClock.uptimeMillis();
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur, HistoryItem.CMD_START);
-        startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
-    }
-
-    /**
-     * Notes the current battery state to be reflected in the next written history item.
-     */
-    public void setBatteryState(boolean charging, int status, int level, int chargeUah) {
-        mHaveBatteryLevel = true;
-        setChargingState(charging);
-        mHistoryCur.batteryStatus = (byte) status;
-        mHistoryCur.batteryLevel = (byte) level;
-        mHistoryCur.batteryChargeUah = chargeUah;
-    }
-
-    /**
-     * Notes the current battery state to be reflected in the next written history item.
-     */
-    public void setBatteryState(int status, int level, int health, int plugType, int temperature,
-            int voltageMv, int chargeUah) {
-        mHaveBatteryLevel = true;
-        mHistoryCur.batteryStatus = (byte) status;
-        mHistoryCur.batteryLevel = (byte) level;
-        mHistoryCur.batteryHealth = (byte) health;
-        mHistoryCur.batteryPlugType = (byte) plugType;
-        mHistoryCur.batteryTemperature = (short) temperature;
-        mHistoryCur.batteryVoltage = (char) voltageMv;
-        mHistoryCur.batteryChargeUah = chargeUah;
-    }
-
-    /**
-     * Notes the current power plugged-in state to be reflected in the next written history item.
-     */
-    public void setPluggedInState(boolean pluggedIn) {
-        if (pluggedIn) {
-            mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
-        } else {
-            mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
-        }
-    }
-
-    /**
-     * Notes the current battery charging state to be reflected in the next written history item.
-     */
-    public void setChargingState(boolean charging) {
-        if (charging) {
-            mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG;
-        } else {
-            mHistoryCur.states2 &= ~HistoryItem.STATE2_CHARGING_FLAG;
-        }
-    }
-
-    /**
-     * Records a history event with the given code, name and UID.
-     */
-    public void recordEvent(long elapsedRealtimeMs, long uptimeMs, int code, String name,
-            int uid) {
-        mHistoryCur.eventCode = code;
-        mHistoryCur.eventTag = mHistoryCur.localEventTag;
-        mHistoryCur.eventTag.string = name;
-        mHistoryCur.eventTag.uid = uid;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
-    }
-
-    /**
-     * Records a time change event.
-     */
-    public void recordCurrentTimeChange(long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) {
-        if (!mRecordingHistory) {
-            return;
-        }
-
-        mHistoryCur.currentTime = currentTimeMs;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur,
-                HistoryItem.CMD_CURRENT_TIME);
-        mHistoryCur.currentTime = 0;
-    }
-
-    /**
-     * Records a system shutdown event.
-     */
-    public void recordShutdownEvent(long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) {
-        if (!mRecordingHistory) {
-            return;
-        }
-
-        mHistoryCur.currentTime = currentTimeMs;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur, HistoryItem.CMD_SHUTDOWN);
-        mHistoryCur.currentTime = 0;
-    }
-
-    /**
-     * Records a battery state change event.
-     */
-    public void recordBatteryState(long elapsedRealtimeMs, long uptimeMs, int batteryLevel,
-            boolean isPlugged) {
-        mHistoryCur.batteryLevel = (byte) batteryLevel;
-        setPluggedInState(isPlugged);
-        if (DEBUG) {
-            Slog.v(TAG, "Battery unplugged to: "
-                    + Integer.toHexString(mHistoryCur.states));
-        }
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
-    }
-
-    /**
-     * Records a history item with the amount of charge consumed by WiFi.  Used on certain devices
-     * equipped with on-device power metering.
-     */
-    public void recordWifiConsumedCharge(long elapsedRealtimeMs, long uptimeMs,
-            double monitoredRailChargeMah) {
-        mHistoryCur.wifiRailChargeMah += monitoredRailChargeMah;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
-    }
-
-    /**
-     * Records a wakelock start event.
-     */
-    public void recordWakelockStartEvent(long elapsedRealtimeMs, long uptimeMs, String historyName,
-            int uid) {
-        mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
-        mHistoryCur.wakelockTag.string = historyName;
-        mHistoryCur.wakelockTag.uid = uid;
-        recordStateStartEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.STATE_WAKE_LOCK_FLAG);
-    }
-
-    /**
-     * Updates the previous history event with a wakelock name and UID.
-     */
-    public boolean maybeUpdateWakelockTag(long elapsedRealtimeMs, long uptimeMs, String historyName,
-            int uid) {
-        if (mHistoryLastWritten.cmd != HistoryItem.CMD_UPDATE) {
-            return false;
-        }
-        if (mHistoryLastWritten.wakelockTag != null) {
-            // We'll try to update the last tag.
-            mHistoryLastWritten.wakelockTag = null;
-            mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
-            mHistoryCur.wakelockTag.string = historyName;
-            mHistoryCur.wakelockTag.uid = uid;
-            writeHistoryItem(elapsedRealtimeMs, uptimeMs);
-        }
-        return true;
-    }
-
-    /**
-     * Records an event when some state flag changes to true.
-     */
-    public void recordStateStartEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
-        mHistoryCur.states |= stateFlags;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
-    }
-
-    /**
-     * Records an event when some state flag changes to false.
-     */
-    public void recordStateStopEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
-        mHistoryCur.states &= ~stateFlags;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
-    }
-
-    /**
-     * Records an event when some state flags change to true and some to false.
-     */
-    public void recordStateChangeEvent(long elapsedRealtimeMs, long uptimeMs, int stateStartFlags,
-            int stateStopFlags) {
-        mHistoryCur.states = (mHistoryCur.states | stateStartFlags) & ~stateStopFlags;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
-    }
-
-    /**
-     * Records an event when some state2 flag changes to true.
-     */
-    public void recordState2StartEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
-        mHistoryCur.states2 |= stateFlags;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
-    }
-
-    /**
-     * Records an event when some state2 flag changes to false.
-     */
-    public void recordState2StopEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
-        mHistoryCur.states2 &= ~stateFlags;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
-    }
-
-    /**
-     * Records an wakeup event.
-     */
-    public void recordWakeupEvent(long elapsedRealtimeMs, long uptimeMs, String reason) {
-        mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag;
-        mHistoryCur.wakeReasonTag.string = reason;
-        mHistoryCur.wakeReasonTag.uid = 0;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
-    }
-
-    /**
-     * Records a screen brightness change event.
-     */
-    public void recordScreenBrightnessEvent(long elapsedRealtimeMs, long uptimeMs,
-            int brightnessBin) {
-        mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_BRIGHTNESS_MASK)
-                | (brightnessBin << HistoryItem.STATE_BRIGHTNESS_SHIFT);
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
-    }
-
-    /**
-     * Records a GNSS signal level change event.
-     */
-    public void recordGpsSignalQualityEvent(long elapsedRealtimeMs, long uptimeMs,
-            int signalLevel) {
-        mHistoryCur.states2 = (mHistoryCur.states2 & ~HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
-                | (signalLevel << HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT);
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
-    }
-
-    /**
-     * Records a device idle mode change event.
-     */
-    public void recordDeviceIdleEvent(long elapsedRealtimeMs, long uptimeMs, int mode) {
-        mHistoryCur.states2 = (mHistoryCur.states2 & ~HistoryItem.STATE2_DEVICE_IDLE_MASK)
-                | (mode << HistoryItem.STATE2_DEVICE_IDLE_SHIFT);
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
-    }
-
-    /**
-     * Records a telephony state change event.
-     */
-    public void recordPhoneStateChangeEvent(long elapsedRealtimeMs, long uptimeMs, int addStateFlag,
-            int removeStateFlag, int state, int signalStrength) {
-        mHistoryCur.states = (mHistoryCur.states | addStateFlag) & ~removeStateFlag;
-        if (state != -1) {
-            mHistoryCur.states =
-                    (mHistoryCur.states & ~HistoryItem.STATE_PHONE_STATE_MASK)
-                            | (state << HistoryItem.STATE_PHONE_STATE_SHIFT);
-        }
-        if (signalStrength != -1) {
-            mHistoryCur.states =
-                    (mHistoryCur.states & ~HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
-                            | (signalStrength << HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT);
-        }
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
-    }
-
-    /**
-     * Records a data connection type change event.
-     */
-    public void recordDataConnectionTypeChangeEvent(long elapsedRealtimeMs, long uptimeMs,
-            int dataConnectionType) {
-        mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_DATA_CONNECTION_MASK)
-                | (dataConnectionType << HistoryItem.STATE_DATA_CONNECTION_SHIFT);
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
-    }
-
-    /**
-     * Records a WiFi supplicant state change event.
-     */
-    public void recordWifiSupplicantStateChangeEvent(long elapsedRealtimeMs, long uptimeMs,
-            int supplState) {
-        mHistoryCur.states2 =
-                (mHistoryCur.states2 & ~HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
-                        | (supplState << HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
-    }
-
-    /**
-     * Records a WiFi signal strength change event.
-     */
-    public void recordWifiSignalStrengthChangeEvent(long elapsedRealtimeMs, long uptimeMs,
-            int strengthBin) {
-        mHistoryCur.states2 =
-                (mHistoryCur.states2 & ~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK)
-                        | (strengthBin << HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT);
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs);
-    }
-
-    /**
-     * Writes the current history item to history.
-     */
-    public void writeHistoryItem(long elapsedRealtimeMs, long uptimeMs) {
-        if (mTrackRunningHistoryElapsedRealtimeMs != 0) {
-            final long diffElapsedMs = elapsedRealtimeMs - mTrackRunningHistoryElapsedRealtimeMs;
-            final long diffUptimeMs = uptimeMs - mTrackRunningHistoryUptimeMs;
-            if (diffUptimeMs < (diffElapsedMs - 20)) {
-                final long wakeElapsedTimeMs = elapsedRealtimeMs - (diffElapsedMs - diffUptimeMs);
-                mHistoryAddTmp.setTo(mHistoryLastWritten);
-                mHistoryAddTmp.wakelockTag = null;
-                mHistoryAddTmp.wakeReasonTag = null;
-                mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE;
-                mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG;
-                writeHistoryItem(wakeElapsedTimeMs, uptimeMs, mHistoryAddTmp);
-            }
-        }
-        mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG;
-        mTrackRunningHistoryElapsedRealtimeMs = elapsedRealtimeMs;
-        mTrackRunningHistoryUptimeMs = uptimeMs;
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur);
-    }
-
-    private void writeHistoryItem(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
-        if (!mHaveBatteryLevel || !mRecordingHistory) {
-            return;
-        }
-
-        final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time;
-        final int diffStates = mHistoryLastWritten.states ^ (cur.states & mActiveHistoryStates);
-        final int diffStates2 = mHistoryLastWritten.states2 ^ (cur.states2 & mActiveHistoryStates2);
-        final int lastDiffStates = mHistoryLastWritten.states ^ mHistoryLastLastWritten.states;
-        final int lastDiffStates2 = mHistoryLastWritten.states2 ^ mHistoryLastLastWritten.states2;
-        if (DEBUG) {
-            Slog.i(TAG, "ADD: tdelta=" + timeDiffMs + " diff="
-                    + Integer.toHexString(diffStates) + " lastDiff="
-                    + Integer.toHexString(lastDiffStates) + " diff2="
-                    + Integer.toHexString(diffStates2) + " lastDiff2="
-                    + Integer.toHexString(lastDiffStates2));
-        }
-        if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE
-                && timeDiffMs < 1000 && (diffStates & lastDiffStates) == 0
-                && (diffStates2 & lastDiffStates2) == 0
-                && (!mHistoryLastWritten.tagsFirstOccurrence && !cur.tagsFirstOccurrence)
-                && (mHistoryLastWritten.wakelockTag == null || cur.wakelockTag == null)
-                && (mHistoryLastWritten.wakeReasonTag == null || cur.wakeReasonTag == null)
-                && mHistoryLastWritten.stepDetails == null
-                && (mHistoryLastWritten.eventCode == HistoryItem.EVENT_NONE
-                || cur.eventCode == HistoryItem.EVENT_NONE)
-                && mHistoryLastWritten.batteryLevel == cur.batteryLevel
-                && mHistoryLastWritten.batteryStatus == cur.batteryStatus
-                && mHistoryLastWritten.batteryHealth == cur.batteryHealth
-                && mHistoryLastWritten.batteryPlugType == cur.batteryPlugType
-                && mHistoryLastWritten.batteryTemperature == cur.batteryTemperature
-                && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage) {
-            // We can merge this new change in with the last one.  Merging is
-            // allowed as long as only the states have changed, and within those states
-            // as long as no bit has changed both between now and the last entry, as
-            // well as the last entry and the one before it (so we capture any toggles).
-            if (DEBUG) Slog.i(TAG, "ADD: rewinding back to " + mHistoryBufferLastPos);
-            mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
-            mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
-            mHistoryBufferLastPos = -1;
-            elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTimeMs;
-            // If the last written history had a wakelock tag, we need to retain it.
-            // Note that the condition above made sure that we aren't in a case where
-            // both it and the current history item have a wakelock tag.
-            if (mHistoryLastWritten.wakelockTag != null) {
-                cur.wakelockTag = cur.localWakelockTag;
-                cur.wakelockTag.setTo(mHistoryLastWritten.wakelockTag);
-            }
-            // If the last written history had a wake reason tag, we need to retain it.
-            // Note that the condition above made sure that we aren't in a case where
-            // both it and the current history item have a wakelock tag.
-            if (mHistoryLastWritten.wakeReasonTag != null) {
-                cur.wakeReasonTag = cur.localWakeReasonTag;
-                cur.wakeReasonTag.setTo(mHistoryLastWritten.wakeReasonTag);
-            }
-            // If the last written history had an event, we need to retain it.
-            // Note that the condition above made sure that we aren't in a case where
-            // both it and the current history item have an event.
-            if (mHistoryLastWritten.eventCode != HistoryItem.EVENT_NONE) {
-                cur.eventCode = mHistoryLastWritten.eventCode;
-                cur.eventTag = cur.localEventTag;
-                cur.eventTag.setTo(mHistoryLastWritten.eventTag);
-            }
-            mHistoryLastWritten.setTo(mHistoryLastLastWritten);
-        }
-        final int dataSize = mHistoryBuffer.dataSize();
-
-        if (dataSize >= mMaxHistoryBufferSize) {
-            if (mMaxHistoryBufferSize == 0) {
-                Slog.wtf(TAG, "mMaxHistoryBufferSize should not be zero when writing history");
-                mMaxHistoryBufferSize = 1024;
-            }
-
-            //open a new history file.
-            final long start = SystemClock.uptimeMillis();
-            writeHistory();
-            if (DEBUG) {
-                Slog.d(TAG, "addHistoryBufferLocked writeHistory took ms:"
-                        + (SystemClock.uptimeMillis() - start));
-            }
-            startNextFile();
-            mHistoryBuffer.setDataSize(0);
-            mHistoryBuffer.setDataPosition(0);
-            mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
-            mHistoryBufferLastPos = -1;
-            mHistoryLastWritten.clear();
-            mHistoryLastLastWritten.clear();
-
-            // Mark every entry in the pool with a flag indicating that the tag
-            // has not yet been encountered while writing the current history buffer.
-            for (Map.Entry<HistoryTag, Integer> entry : mHistoryTagPool.entrySet()) {
-                entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
-            }
-            // Make a copy of mHistoryCur.
-            HistoryItem copy = new HistoryItem();
-            copy.setTo(cur);
-            // startRecordingHistory will reset mHistoryCur.
-            startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
-            // Add the copy into history buffer.
-            writeHistoryItem(elapsedRealtimeMs, uptimeMs, copy, HistoryItem.CMD_UPDATE);
-            return;
-        }
-
-        if (dataSize == 0) {
-            // The history is currently empty; we need it to start with a time stamp.
-            cur.currentTime = mClock.currentTimeMillis();
-            writeHistoryItem(elapsedRealtimeMs, uptimeMs, cur, HistoryItem.CMD_RESET);
-        }
-        writeHistoryItem(elapsedRealtimeMs, uptimeMs, cur, HistoryItem.CMD_UPDATE);
-    }
-
-    private void writeHistoryItem(long elapsedRealtimeMs,
-            @SuppressWarnings("UnusedVariable") long uptimeMs, HistoryItem cur, byte cmd) {
-        if (mBatteryStatsHistoryIterator != null) {
-            throw new IllegalStateException("Can't do this while iterating history!");
-        }
-        mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
-        mHistoryLastLastWritten.setTo(mHistoryLastWritten);
-        final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence;
-        mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur);
-        mHistoryLastWritten.tagsFirstOccurrence = hasTags;
-        mHistoryLastWritten.states &= mActiveHistoryStates;
-        mHistoryLastWritten.states2 &= mActiveHistoryStates2;
-        writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
-        mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
-        cur.wakelockTag = null;
-        cur.wakeReasonTag = null;
-        cur.eventCode = HistoryItem.EVENT_NONE;
-        cur.eventTag = null;
-        cur.tagsFirstOccurrence = false;
-        if (DEBUG) {
-            Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
-                    + " now " + mHistoryBuffer.dataPosition()
-                    + " size is now " + mHistoryBuffer.dataSize());
-        }
-    }
-
-    /*
-        The history delta format uses flags to denote further data in subsequent ints in the parcel.
-
-        There is always the first token, which may contain the delta time, or an indicator of
-        the length of the time (int or long) following this token.
-
-        First token: always present,
-        31              23              15               7             0
-        █M|L|K|J|I|H|G|F█E|D|C|B|A|T|T|T█T|T|T|T|T|T|T|T█T|T|T|T|T|T|T|T█
-
-        T: the delta time if it is <= 0x7fffd. Otherwise 0x7fffe indicates an int immediately
-           follows containing the time, and 0x7ffff indicates a long immediately follows with the
-           delta time.
-        A: battery level changed and an int follows with battery data.
-        B: state changed and an int follows with state change data.
-        C: state2 has changed and an int follows with state2 change data.
-        D: wakelock/wakereason has changed and an wakelock/wakereason struct follows.
-        E: event data has changed and an event struct follows.
-        F: battery charge in coulombs has changed and an int with the charge follows.
-        G: state flag denoting that the mobile radio was active.
-        H: state flag denoting that the wifi radio was active.
-        I: state flag denoting that a wifi scan occurred.
-        J: state flag denoting that a wifi full lock was held.
-        K: state flag denoting that the gps was on.
-        L: state flag denoting that a wakelock was held.
-        M: state flag denoting that the cpu was running.
-
-        Time int/long: if T in the first token is 0x7ffff or 0x7fffe, then an int or long follows
-        with the time delta.
-
-        Battery level int: if A in the first token is set,
-        31              23              15               7             0
-        █L|L|L|L|L|L|L|T█T|T|T|T|T|T|T|T█T|V|V|V|V|V|V|V█V|V|V|V|V|V|V|D█
-
-        D: indicates that extra history details follow.
-        V: the battery voltage.
-        T: the battery temperature.
-        L: the battery level (out of 100).
-
-        State change int: if B in the first token is set,
-        31              23              15               7             0
-        █S|S|S|H|H|H|P|P█F|E|D|C|B| | |A█ | | | | | | | █ | | | | | | | █
-
-        A: wifi multicast was on.
-        B: battery was plugged in.
-        C: screen was on.
-        D: phone was scanning for signal.
-        E: audio was on.
-        F: a sensor was active.
-
-        State2 change int: if C in the first token is set,
-        31              23              15               7             0
-        █M|L|K|J|I|H|H|G█F|E|D|C| | | | █ | | | | | | | █ |B|B|B|A|A|A|A█
-
-        A: 4 bits indicating the wifi supplicant state: {@link BatteryStats#WIFI_SUPPL_STATE_NAMES}.
-        B: 3 bits indicating the wifi signal strength: 0, 1, 2, 3, 4.
-        C: a bluetooth scan was active.
-        D: the camera was active.
-        E: bluetooth was on.
-        F: a phone call was active.
-        G: the device was charging.
-        H: 2 bits indicating the device-idle (doze) state: off, light, full
-        I: the flashlight was on.
-        J: wifi was on.
-        K: wifi was running.
-        L: video was playing.
-        M: power save mode was on.
-
-        Wakelock/wakereason struct: if D in the first token is set,
-        Event struct: if E in the first token is set,
-        History step details struct: if D in the battery level int is set,
-
-        Battery charge int: if F in the first token is set, an int representing the battery charge
-        in coulombs follows.
-     */
-    /**
-     * Writes the delta between the previous and current history items into history buffer.
-     */
-    public void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) {
-        if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) {
-            dest.writeInt(BatteryStatsHistory.DELTA_TIME_ABS);
-            cur.writeToParcel(dest, 0);
-            return;
-        }
-
-        final long deltaTime = cur.time - last.time;
-        final int lastBatteryLevelInt = buildBatteryLevelInt(last);
-        final int lastStateInt = buildStateInt(last);
-
-        int deltaTimeToken;
-        if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) {
-            deltaTimeToken = BatteryStatsHistory.DELTA_TIME_LONG;
-        } else if (deltaTime >= BatteryStatsHistory.DELTA_TIME_ABS) {
-            deltaTimeToken = BatteryStatsHistory.DELTA_TIME_INT;
-        } else {
-            deltaTimeToken = (int) deltaTime;
-        }
-        int firstToken = deltaTimeToken | (cur.states & BatteryStatsHistory.DELTA_STATE_MASK);
-        final int includeStepDetails = mLastHistoryStepLevel > cur.batteryLevel
-                ? BatteryStatsHistory.BATTERY_DELTA_LEVEL_FLAG : 0;
-        mLastHistoryStepLevel = cur.batteryLevel;
-        final int batteryLevelInt = buildBatteryLevelInt(cur) | includeStepDetails;
-        final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt;
-        if (batteryLevelIntChanged) {
-            firstToken |= BatteryStatsHistory.DELTA_BATTERY_LEVEL_FLAG;
-        }
-        final int stateInt = buildStateInt(cur);
-        final boolean stateIntChanged = stateInt != lastStateInt;
-        if (stateIntChanged) {
-            firstToken |= BatteryStatsHistory.DELTA_STATE_FLAG;
-        }
-        final boolean state2IntChanged = cur.states2 != last.states2;
-        if (state2IntChanged) {
-            firstToken |= BatteryStatsHistory.DELTA_STATE2_FLAG;
-        }
-        if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
-            firstToken |= BatteryStatsHistory.DELTA_WAKELOCK_FLAG;
-        }
-        if (cur.eventCode != HistoryItem.EVENT_NONE) {
-            firstToken |= BatteryStatsHistory.DELTA_EVENT_FLAG;
-        }
-
-        final boolean batteryChargeChanged = cur.batteryChargeUah != last.batteryChargeUah;
-        if (batteryChargeChanged) {
-            firstToken |= BatteryStatsHistory.DELTA_BATTERY_CHARGE_FLAG;
-        }
-        dest.writeInt(firstToken);
-        if (DEBUG) {
-            Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken)
-                    + " deltaTime=" + deltaTime);
-        }
-
-        if (deltaTimeToken >= BatteryStatsHistory.DELTA_TIME_INT) {
-            if (deltaTimeToken == BatteryStatsHistory.DELTA_TIME_INT) {
-                if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int) deltaTime);
-                dest.writeInt((int) deltaTime);
-            } else {
-                if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime);
-                dest.writeLong(deltaTime);
-            }
-        }
-        if (batteryLevelIntChanged) {
-            dest.writeInt(batteryLevelInt);
-            if (DEBUG) {
-                Slog.i(TAG, "WRITE DELTA: batteryToken=0x"
-                        + Integer.toHexString(batteryLevelInt)
-                        + " batteryLevel=" + cur.batteryLevel
-                        + " batteryTemp=" + cur.batteryTemperature
-                        + " batteryVolt=" + (int) cur.batteryVoltage);
-            }
-        }
-        if (stateIntChanged) {
-            dest.writeInt(stateInt);
-            if (DEBUG) {
-                Slog.i(TAG, "WRITE DELTA: stateToken=0x"
-                        + Integer.toHexString(stateInt)
-                        + " batteryStatus=" + cur.batteryStatus
-                        + " batteryHealth=" + cur.batteryHealth
-                        + " batteryPlugType=" + cur.batteryPlugType
-                        + " states=0x" + Integer.toHexString(cur.states));
-            }
-        }
-        if (state2IntChanged) {
-            dest.writeInt(cur.states2);
-            if (DEBUG) {
-                Slog.i(TAG, "WRITE DELTA: states2=0x"
-                        + Integer.toHexString(cur.states2));
-            }
-        }
-        if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
-            int wakeLockIndex;
-            int wakeReasonIndex;
-            if (cur.wakelockTag != null) {
-                wakeLockIndex = writeHistoryTag(cur.wakelockTag);
-                if (DEBUG) {
-                    Slog.i(TAG, "WRITE DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx
-                            + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string);
-                }
-            } else {
-                wakeLockIndex = 0xffff;
-            }
-            if (cur.wakeReasonTag != null) {
-                wakeReasonIndex = writeHistoryTag(cur.wakeReasonTag);
-                if (DEBUG) {
-                    Slog.i(TAG, "WRITE DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx
-                            + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string);
-                }
-            } else {
-                wakeReasonIndex = 0xffff;
-            }
-            dest.writeInt((wakeReasonIndex << 16) | wakeLockIndex);
-            if (cur.wakelockTag != null
-                    && (wakeLockIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
-                cur.wakelockTag.writeToParcel(dest, 0);
-                cur.tagsFirstOccurrence = true;
-            }
-            if (cur.wakeReasonTag != null
-                    && (wakeReasonIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
-                cur.wakeReasonTag.writeToParcel(dest, 0);
-                cur.tagsFirstOccurrence = true;
-            }
-        }
-        if (cur.eventCode != HistoryItem.EVENT_NONE) {
-            final int index = writeHistoryTag(cur.eventTag);
-            final int codeAndIndex = (cur.eventCode & 0xffff) | (index << 16);
-            dest.writeInt(codeAndIndex);
-            if ((index & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
-                cur.eventTag.writeToParcel(dest, 0);
-                cur.tagsFirstOccurrence = true;
-            }
-            if (DEBUG) {
-                Slog.i(TAG, "WRITE DELTA: event=" + cur.eventCode + " tag=#"
-                        + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":"
-                        + cur.eventTag.string);
-            }
-        }
-
-        cur.stepDetails = mStepDetailsCalculator.getHistoryStepDetails();
-        if (includeStepDetails != 0) {
-            cur.stepDetails.writeToParcel(dest);
-        }
-
-        if (batteryChargeChanged) {
-            if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryChargeUah=" + cur.batteryChargeUah);
-            dest.writeInt(cur.batteryChargeUah);
-        }
-        dest.writeDouble(cur.modemRailChargeMah);
-        dest.writeDouble(cur.wifiRailChargeMah);
-    }
-
-    private int buildBatteryLevelInt(HistoryItem h) {
-        return ((((int) h.batteryLevel) << 25) & 0xfe000000)
-                | ((((int) h.batteryTemperature) << 15) & 0x01ff8000)
-                | ((((int) h.batteryVoltage) << 1) & 0x00007ffe);
-    }
-
-    private int buildStateInt(HistoryItem h) {
-        int plugType = 0;
-        if ((h.batteryPlugType & BatteryManager.BATTERY_PLUGGED_AC) != 0) {
-            plugType = 1;
-        } else if ((h.batteryPlugType & BatteryManager.BATTERY_PLUGGED_USB) != 0) {
-            plugType = 2;
-        } else if ((h.batteryPlugType & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0) {
-            plugType = 3;
-        }
-        return ((h.batteryStatus & BatteryStatsHistory.STATE_BATTERY_STATUS_MASK)
-                << BatteryStatsHistory.STATE_BATTERY_STATUS_SHIFT)
-                | ((h.batteryHealth & BatteryStatsHistory.STATE_BATTERY_HEALTH_MASK)
-                << BatteryStatsHistory.STATE_BATTERY_HEALTH_SHIFT)
-                | ((plugType & BatteryStatsHistory.STATE_BATTERY_PLUG_MASK)
-                << BatteryStatsHistory.STATE_BATTERY_PLUG_SHIFT)
-                | (h.states & (~BatteryStatsHistory.STATE_BATTERY_MASK));
-    }
-
-    /**
-     * Returns the index for the specified tag. If this is the first time the tag is encountered
-     * while writing the current history buffer, the method returns
-     * <code>(index | TAG_FIRST_OCCURRENCE_FLAG)</code>
-     */
-    private int writeHistoryTag(HistoryTag tag) {
-        if (tag.string == null) {
-            Slog.wtfStack(TAG, "writeHistoryTag called with null name");
-        }
-
-        final int stringLength = tag.string.length();
-        if (stringLength > MAX_HISTORY_TAG_STRING_LENGTH) {
-            Slog.e(TAG, "Long battery history tag: " + tag.string);
-            tag.string = tag.string.substring(0, MAX_HISTORY_TAG_STRING_LENGTH);
-        }
-
-        Integer idxObj = mHistoryTagPool.get(tag);
-        int idx;
-        if (idxObj != null) {
-            idx = idxObj;
-            if ((idx & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
-                mHistoryTagPool.put(tag, idx & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
-            }
-            return idx;
-        } else if (mNextHistoryTagIdx < HISTORY_TAG_INDEX_LIMIT) {
-            idx = mNextHistoryTagIdx;
-            HistoryTag key = new HistoryTag();
-            key.setTo(tag);
-            tag.poolIdx = idx;
-            mHistoryTagPool.put(key, idx);
-            mNextHistoryTagIdx++;
-
-            mNumHistoryTagChars += stringLength + 1;
-            if (mHistoryTags != null) {
-                mHistoryTags.put(idx, key);
-            }
-            return idx | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
-        } else {
-            // Tag pool overflow: include the tag itself in the parcel
-            return HISTORY_TAG_INDEX_LIMIT | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
-        }
-    }
-
-    /**
-     * Don't allow any more batching in to the current history event.
-     */
-    public void commitCurrentHistoryBatchLocked() {
-        mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
-    }
-
-    /**
-     * Saves the accumulated history buffer in the active file, see {@link #getActiveFile()} .
-     */
-    public void writeHistory() {
-        if (mActiveFile == null) {
-            Slog.w(TAG, "writeHistory: no history file associated with this instance");
-            return;
-        }
-
-        Parcel p = Parcel.obtain();
-        try {
-            final long start = SystemClock.uptimeMillis();
-            writeHistoryBuffer(p);
-            if (DEBUG) {
-                Slog.d(TAG, "writeHistoryBuffer duration ms:"
-                        + (SystemClock.uptimeMillis() - start) + " bytes:" + p.dataSize());
-            }
-            writeParcelToFileLocked(p, mActiveFile);
-        } finally {
-            p.recycle();
-        }
-    }
-
-    /**
-     * Reads history buffer from a persisted Parcel.
-     */
-    public void readHistoryBuffer(Parcel in) throws ParcelFormatException {
-        final int version = in.readInt();
-        if (version != BatteryStatsHistory.VERSION) {
-            Slog.w("BatteryStats", "readHistoryBuffer: version got " + version
-                    + ", expected " + BatteryStatsHistory.VERSION + "; erasing old stats");
-            return;
-        }
-
-        final long historyBaseTime = in.readLong();
-
-        mHistoryBuffer.setDataSize(0);
-        mHistoryBuffer.setDataPosition(0);
-
-        int bufSize = in.readInt();
-        int curPos = in.dataPosition();
-        if (bufSize >= (mMaxHistoryBufferSize * 100)) {
-            throw new ParcelFormatException(
-                    "File corrupt: history data buffer too large " + bufSize);
-        } else if ((bufSize & ~3) != bufSize) {
-            throw new ParcelFormatException(
-                    "File corrupt: history data buffer not aligned " + bufSize);
-        } else {
-            if (DEBUG) {
-                Slog.i(TAG, "***************** READING NEW HISTORY: " + bufSize
-                        + " bytes at " + curPos);
-            }
-            mHistoryBuffer.appendFrom(in, curPos, bufSize);
-            in.setDataPosition(curPos + bufSize);
-        }
-
-        if (DEBUG) {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("****************** OLD mHistoryBaseTimeMs: ");
-            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
-            Slog.i(TAG, sb.toString());
-        }
-        mHistoryBaseTimeMs = historyBaseTime;
-        if (DEBUG) {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("****************** NEW mHistoryBaseTimeMs: ");
-            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
-            Slog.i(TAG, sb.toString());
-        }
-
-        // We are just arbitrarily going to insert 1 minute from the sample of
-        // the last run until samples in this run.
-        if (mHistoryBaseTimeMs > 0) {
-            long oldnow = mClock.elapsedRealtime();
-            mHistoryBaseTimeMs = mHistoryBaseTimeMs - oldnow + 1;
-            if (DEBUG) {
-                StringBuilder sb = new StringBuilder(128);
-                sb.append("****************** ADJUSTED mHistoryBaseTimeMs: ");
-                TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
-                Slog.i(TAG, sb.toString());
-            }
-        }
-    }
-
-    private void writeHistoryBuffer(Parcel out) {
-        if (DEBUG) {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("****************** WRITING mHistoryBaseTimeMs: ");
-            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
-            sb.append(" mLastHistoryElapsedRealtimeMs: ");
-            TimeUtils.formatDuration(mLastHistoryElapsedRealtimeMs, sb);
-            Slog.i(TAG, sb.toString());
-        }
-        out.writeInt(BatteryStatsHistory.VERSION);
-        out.writeLong(mHistoryBaseTimeMs + mLastHistoryElapsedRealtimeMs);
-        out.writeInt(mHistoryBuffer.dataSize());
-        if (DEBUG) {
-            Slog.i(TAG, "***************** WRITING HISTORY: "
-                    + mHistoryBuffer.dataSize() + " bytes at " + out.dataPosition());
-        }
-        out.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
-    }
-
-    private void writeParcelToFileLocked(Parcel p, AtomicFile file) {
-        FileOutputStream fos = null;
-        mWriteLock.lock();
-        try {
-            final long startTimeMs = SystemClock.uptimeMillis();
-            fos = file.startWrite();
-            fos.write(p.marshall());
-            fos.flush();
-            file.finishWrite(fos);
-            if (DEBUG) {
-                Slog.d(TAG, "writeParcelToFileLocked file:" + file.getBaseFile().getPath()
-                        + " duration ms:" + (SystemClock.uptimeMillis() - startTimeMs)
-                        + " bytes:" + p.dataSize());
-            }
-            com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
-                    "batterystats", SystemClock.uptimeMillis() - startTimeMs);
-        } catch (IOException e) {
-            Slog.w(TAG, "Error writing battery statistics", e);
-            file.failWrite(fos);
-        } finally {
-            mWriteLock.unlock();
-        }
-    }
-
-    /**
-     * Returns the total number of history tags in the tag pool.
-     */
-    public int getHistoryStringPoolSize() {
-        return mHistoryTagPool.size();
-    }
-
-    /**
-     * Returns the total number of bytes occupied by the history tag pool.
-     */
-    public int getHistoryStringPoolBytes() {
-        return mNumHistoryTagChars;
-    }
-
-    /**
-     * Returns the string held by the requested history tag.
-     */
-    public String getHistoryTagPoolString(int index) {
-        ensureHistoryTagArray();
-        HistoryTag historyTag = mHistoryTags.get(index);
-        return historyTag != null ? historyTag.string : null;
-    }
-
-    /**
-     * Returns the UID held by the requested history tag.
-     */
-    public int getHistoryTagPoolUid(int index) {
-        ensureHistoryTagArray();
-        HistoryTag historyTag = mHistoryTags.get(index);
-        return historyTag != null ? historyTag.uid : Process.INVALID_UID;
-    }
-
-    private void ensureHistoryTagArray() {
-        if (mHistoryTags != null) {
-            return;
-        }
-
-        mHistoryTags = new SparseArray<>(mHistoryTagPool.size());
-        for (Map.Entry<HistoryTag, Integer> entry : mHistoryTagPool.entrySet()) {
-            mHistoryTags.put(entry.getValue() & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG,
-                    entry.getKey());
-        }
-    }
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index 1bf878cb..de8b414 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -36,6 +36,7 @@
 
     public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history) {
         mBatteryStatsHistory = history;
+        mBatteryStatsHistory.startIteratingHistory();
     }
 
     /**
@@ -230,11 +231,4 @@
         out.batteryTemperature = (short) ((batteryLevelInt & 0x01ff8000) >>> 15);
         out.batteryVoltage = (char) ((batteryLevelInt & 0x00007ffe) >>> 1);
     }
-
-    /**
-     * Should be called when iteration is complete.
-     */
-    public void close() {
-        mBatteryStatsHistory.finishIteratingHistory();
-    }
 }
diff --git a/core/java/com/android/internal/util/FastDataOutput.java b/core/java/com/android/internal/util/FastDataOutput.java
index c9e8f8f..5b6075e 100644
--- a/core/java/com/android/internal/util/FastDataOutput.java
+++ b/core/java/com/android/internal/util/FastDataOutput.java
@@ -59,7 +59,7 @@
     /**
      * Values that have been "interned" by {@link #writeInternedUTF(String)}.
      */
-    private final HashMap<String, Short> mStringRefs = new HashMap<>();
+    private final HashMap<String, Integer> mStringRefs = new HashMap<>();
 
     /**
      * @deprecated callers must specify {@code use4ByteSequence} so they make a
@@ -256,7 +256,7 @@
      * @see FastDataInput#readInternedUTF()
      */
     public void writeInternedUTF(@NonNull String s) throws IOException {
-        Short ref = mStringRefs.get(s);
+        Integer ref = mStringRefs.get(s);
         if (ref != null) {
             writeShort(ref);
         } else {
@@ -265,7 +265,7 @@
 
             // We can only safely intern when we have remaining values; if we're
             // full we at least sent the string value above
-            ref = (short) mStringRefs.size();
+            ref = mStringRefs.size();
             if (ref < MAX_UNSIGNED_SHORT) {
                 mStringRefs.put(s, ref);
             }
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
index 95a4e12..bc729f1 100644
--- a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
+++ b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
@@ -1475,7 +1475,6 @@
         contentContainer.setLayoutParams(new ViewGroup.LayoutParams(
                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
         contentContainer.setTag(FloatingToolbar.FLOATING_TOOLBAR_TAG);
-        contentContainer.setContentDescription(FloatingToolbar.FLOATING_TOOLBAR_TAG);
         contentContainer.setClipToOutline(true);
         return contentContainer;
     }
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 671e634..7f50204 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -49,7 +49,7 @@
 per-file *Zygote* = file:/ZYGOTE_OWNERS
 per-file core_jni_helpers.* = file:/ZYGOTE_OWNERS
 per-file fd_utils.* = file:/ZYGOTE_OWNERS
-per-file Android.bp = file:platform/build/soong:/OWNERS
+per-file Android.bp = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION}
 per-file android_animation_* = file:/core/java/android/animation/OWNERS
 per-file android_app_admin_* = file:/core/java/android/app/admin/OWNERS
 per-file android_hardware_Usb* = file:/services/usb/OWNERS
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
index d852265..17cfbfc 100644
--- a/core/jni/android/opengl/util.cpp
+++ b/core/jni/android/opengl/util.cpp
@@ -720,19 +720,22 @@
         jint internalformat, jobject bitmapObj, jint type, jint border)
 {
     graphics::Bitmap bitmap(env, bitmapObj);
-    AndroidBitmapInfo bitmapInfo = bitmap.getInfo();
+    if (bitmap.isValid() && bitmap.getPixels() != nullptr) {
+        AndroidBitmapInfo bitmapInfo = bitmap.getInfo();
 
-    if (internalformat < 0) {
-        internalformat = getInternalFormat(bitmapInfo.format);
-    }
-    if (type < 0) {
-        type = getType(bitmapInfo.format);
-    }
+        if (internalformat < 0) {
+            internalformat = getInternalFormat(bitmapInfo.format);
+        }
+        if (type < 0) {
+            type = getType(bitmapInfo.format);
+        }
 
-    if (checkInternalFormat(bitmapInfo.format, internalformat, type)) {
-        glTexImage2D(target, level, internalformat, bitmapInfo.width, bitmapInfo.height, border,
-                     getPixelFormatFromInternalFormat(internalformat), type, bitmap.getPixels());
-        return 0;
+        if (checkInternalFormat(bitmapInfo.format, internalformat, type)) {
+            glTexImage2D(target, level, internalformat, bitmapInfo.width, bitmapInfo.height, border,
+                         getPixelFormatFromInternalFormat(internalformat), type,
+                         bitmap.getPixels());
+            return 0;
+        }
     }
     return -1;
 }
@@ -741,19 +744,21 @@
         jint xoffset, jint yoffset, jobject bitmapObj, jint format, jint type)
 {
     graphics::Bitmap bitmap(env, bitmapObj);
-    AndroidBitmapInfo bitmapInfo = bitmap.getInfo();
+    if (bitmap.isValid() && bitmap.getPixels() != nullptr) {
+        AndroidBitmapInfo bitmapInfo = bitmap.getInfo();
 
-    int internalFormat = getInternalFormat(bitmapInfo.format);
-    if (format < 0) {
-        format = getPixelFormatFromInternalFormat(internalFormat);
-        if (format == GL_PALETTE8_RGBA8_OES)
-            return -1; // glCompressedTexSubImage2D() not supported
-    }
+        int internalFormat = getInternalFormat(bitmapInfo.format);
+        if (format < 0) {
+            format = getPixelFormatFromInternalFormat(internalFormat);
+            if (format == GL_PALETTE8_RGBA8_OES)
+                return -1; // glCompressedTexSubImage2D() not supported
+        }
 
-    if (checkInternalFormat(bitmapInfo.format, internalFormat, type)) {
-        glTexSubImage2D(target, level, xoffset, yoffset, bitmapInfo.width, bitmapInfo.height,
-                        format, type, bitmap.getPixels());
-        return 0;
+        if (checkInternalFormat(bitmapInfo.format, internalFormat, type)) {
+            glTexSubImage2D(target, level, xoffset, yoffset, bitmapInfo.width, bitmapInfo.height,
+                            format, type, bitmap.getPixels());
+            return 0;
+        }
     }
     return -1;
 }
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 9ae1630..bc299fd 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -820,14 +820,6 @@
     transaction->setStretchEffect(ctrl, stretch);
 }
 
-static void nativeSetSize(JNIEnv* env, jclass clazz, jlong transactionObj,
-        jlong nativeObject, jint w, jint h) {
-    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
-
-    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
-    transaction->setSize(ctrl, w, h);
-}
-
 static void nativeSetFlags(JNIEnv* env, jclass clazz, jlong transactionObj,
         jlong nativeObject, jint flags, jint mask) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -2121,6 +2113,20 @@
     return surface->getLayerId();
 }
 
+static void nativeSetDefaultApplyToken(JNIEnv* env, jclass clazz, jobject applyToken) {
+    sp<IBinder> token(ibinderForJavaObject(env, applyToken));
+    if (token == nullptr) {
+        ALOGE("Null apply token provided.");
+        return;
+    }
+    SurfaceComposerClient::Transaction::setDefaultApplyToken(token);
+}
+
+static jobject nativeGetDefaultApplyToken(JNIEnv* env, jclass clazz) {
+    sp<IBinder> token = SurfaceComposerClient::Transaction::getDefaultApplyToken();
+    return javaObjectForIBinder(env, token);
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod sSurfaceControlMethods[] = {
@@ -2163,8 +2169,6 @@
             (void*)nativeSetPosition },
     {"nativeSetScale", "(JJFF)V",
             (void*)nativeSetScale },
-    {"nativeSetSize", "(JJII)V",
-            (void*)nativeSetSize },
     {"nativeSetTransparentRegionHint", "(JJLandroid/graphics/Region;)V",
             (void*)nativeSetTransparentRegionHint },
     {"nativeSetDamageRegion", "(JJLandroid/graphics/Region;)V",
@@ -2343,6 +2347,10 @@
             (void*) nativeSanitize },
     {"nativeSetDestinationFrame", "(JJIIII)V",
                 (void*)nativeSetDestinationFrame },
+    {"nativeSetDefaultApplyToken", "(Landroid/os/IBinder;)V",
+                (void*)nativeSetDefaultApplyToken },
+    {"nativeGetDefaultApplyToken", "()Landroid/os/IBinder;",
+                (void*)nativeGetDefaultApplyToken },
         // clang-format on
 };
 
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index 0ebf2dd..5b946d5 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -99,12 +99,13 @@
     jniThrowRuntimeException(env, "Could not write LongArrayMultiStateCounter to Parcel");
 }
 
-#define THROW_ON_WRITE_ERROR(expr)     \
-    {                                  \
-        binder_status_t status = expr; \
-        if (status != STATUS_OK) {     \
-            throwWriteRE(env, status); \
-        }                              \
+#define THROW_AND_RETURN_ON_WRITE_ERROR(expr) \
+    {                                         \
+        binder_status_t status = expr;        \
+        if (status != STATUS_OK) {            \
+            throwWriteRE(env, status);        \
+            return;                           \
+        }                                     \
     }
 
 static void native_writeToParcel(JNIEnv *env, jobject self, jlong nativePtr, jobject jParcel,
@@ -114,14 +115,15 @@
     ndk::ScopedAParcel parcel(AParcel_fromJavaParcel(env, jParcel));
 
     uint16_t stateCount = counter->getStateCount();
-    THROW_ON_WRITE_ERROR(AParcel_writeInt32(parcel.get(), stateCount));
+    THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeInt32(parcel.get(), stateCount));
 
     // LongArrayMultiStateCounter has at least state 0
     const std::vector<uint64_t> &anyState = counter->getCount(0);
-    THROW_ON_WRITE_ERROR(AParcel_writeInt32(parcel.get(), anyState.size()));
+    THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeInt32(parcel.get(), anyState.size()));
 
     for (battery::state_t state = 0; state < stateCount; state++) {
-        THROW_ON_WRITE_ERROR(ndk::AParcel_writeVector(parcel.get(), counter->getCount(state)));
+        THROW_AND_RETURN_ON_WRITE_ERROR(
+                ndk::AParcel_writeVector(parcel.get(), counter->getCount(state)));
     }
 }
 
@@ -130,35 +132,37 @@
     jniThrowRuntimeException(env, "Could not read LongArrayMultiStateCounter from Parcel");
 }
 
-#define THROW_ON_READ_ERROR(expr)      \
-    {                                  \
-        binder_status_t status = expr; \
-        if (status != STATUS_OK) {     \
-            throwReadRE(env, status);  \
-        }                              \
+#define THROW_AND_RETURN_ON_READ_ERROR(expr) \
+    {                                        \
+        binder_status_t status = expr;       \
+        if (status != STATUS_OK) {           \
+            throwReadRE(env, status);        \
+            return 0L;                       \
+        }                                    \
     }
 
 static jlong native_initFromParcel(JNIEnv *env, jclass theClass, jobject jParcel) {
     ndk::ScopedAParcel parcel(AParcel_fromJavaParcel(env, jParcel));
 
     int32_t stateCount;
-    THROW_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &stateCount));
+    THROW_AND_RETURN_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &stateCount));
 
     int32_t arrayLength;
-    THROW_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &arrayLength));
+    THROW_AND_RETURN_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &arrayLength));
 
-    battery::LongArrayMultiStateCounter *counter =
-            new battery::LongArrayMultiStateCounter(stateCount, std::vector<uint64_t>(arrayLength));
+    auto counter = std::make_unique<battery::LongArrayMultiStateCounter>(stateCount,
+                                                                         std::vector<uint64_t>(
+                                                                                 arrayLength));
 
     std::vector<uint64_t> value;
     value.reserve(arrayLength);
 
     for (battery::state_t state = 0; state < stateCount; state++) {
-        THROW_ON_READ_ERROR(ndk::AParcel_readVector(parcel.get(), &value));
+        THROW_AND_RETURN_ON_READ_ERROR(ndk::AParcel_readVector(parcel.get(), &value));
         counter->setValue(state, value);
     }
 
-    return reinterpret_cast<jlong>(counter);
+    return reinterpret_cast<jlong>(counter.release());
 }
 
 static jint native_getStateCount(jlong nativePtr) {
diff --git a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
index 8c69d27..1712b3a8 100644
--- a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
@@ -109,12 +109,13 @@
     jniThrowRuntimeException(env, "Could not write LongMultiStateCounter to Parcel");
 }
 
-#define THROW_ON_WRITE_ERROR(expr)     \
-    {                                  \
-        binder_status_t status = expr; \
-        if (status != STATUS_OK) {     \
-            throwWriteRE(env, status); \
-        }                              \
+#define THROW_AND_RETURN_ON_WRITE_ERROR(expr) \
+    {                                         \
+        binder_status_t status = expr;        \
+        if (status != STATUS_OK) {            \
+            throwWriteRE(env, status);        \
+            return;                           \
+        }                                     \
     }
 
 static void native_writeToParcel(JNIEnv *env, jobject self, jlong nativePtr, jobject jParcel,
@@ -123,10 +124,10 @@
     ndk::ScopedAParcel parcel(AParcel_fromJavaParcel(env, jParcel));
 
     uint16_t stateCount = counter->getStateCount();
-    THROW_ON_WRITE_ERROR(AParcel_writeInt32(parcel.get(), stateCount));
+    THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeInt32(parcel.get(), stateCount));
 
     for (battery::state_t state = 0; state < stateCount; state++) {
-        THROW_ON_WRITE_ERROR(AParcel_writeInt64(parcel.get(), counter->getCount(state)));
+        THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeInt64(parcel.get(), counter->getCount(state)));
     }
 }
 
@@ -135,29 +136,30 @@
     jniThrowRuntimeException(env, "Could not read LongMultiStateCounter from Parcel");
 }
 
-#define THROW_ON_READ_ERROR(expr)      \
-    {                                  \
-        binder_status_t status = expr; \
-        if (status != STATUS_OK) {     \
-            throwReadRE(env, status);  \
-        }                              \
+#define THROW_AND_RETURN_ON_READ_ERROR(expr) \
+    {                                        \
+        binder_status_t status = expr;       \
+        if (status != STATUS_OK) {           \
+            throwReadRE(env, status);        \
+            return 0L;                       \
+        }                                    \
     }
 
 static jlong native_initFromParcel(JNIEnv *env, jclass theClass, jobject jParcel) {
     ndk::ScopedAParcel parcel(AParcel_fromJavaParcel(env, jParcel));
 
     int32_t stateCount;
-    THROW_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &stateCount));
+    THROW_AND_RETURN_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &stateCount));
 
-    battery::LongMultiStateCounter *counter = new battery::LongMultiStateCounter(stateCount, 0);
+    auto counter = std::make_unique<battery::LongMultiStateCounter>(stateCount, 0);
 
     for (battery::state_t state = 0; state < stateCount; state++) {
         int64_t value;
-        THROW_ON_READ_ERROR(AParcel_readInt64(parcel.get(), &value));
+        THROW_AND_RETURN_ON_READ_ERROR(AParcel_readInt64(parcel.get(), &value));
         counter->setValue(state, value);
     }
 
-    return reinterpret_cast<jlong>(counter);
+    return reinterpret_cast<jlong>(counter.release());
 }
 
 static jint native_getStateCount(jlong nativePtr) {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt b/core/proto/android/os/tombstone.proto
similarity index 63%
copy from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
copy to core/proto/android/os/tombstone.proto
index 8dde897..2d3b1f3 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
+++ b/core/proto/android/os/tombstone.proto
@@ -14,10 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spaprivileged.framework.app
+syntax = "proto2";
 
-import android.content.pm.ApplicationInfo
+package android.os;
 
-interface AppRecord {
-    val app: ApplicationInfo
-}
+option java_multiple_files = true;
+
+message TombstoneWithHeadersProto {
+  // The original proto tombstone bytes.
+  // Proto located at: system/core/debuggerd/proto/tombstone.proto
+  optional bytes tombstone = 1;
+
+  // The count of dropped dropbox entries due to rate limiting.
+  optional int32 dropped_count = 2;
+}
\ No newline at end of file
diff --git a/core/res/Android.bp b/core/res/Android.bp
index c42517d..93ce783 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -73,18 +73,18 @@
         ":remote-color-resources-compile-colors",
     ],
     out: ["remote-color-resources.apk"],
-    cmd: "$(location aapt2) link -o $(out) --manifest $(in)"
+    cmd: "$(location aapt2) link -o $(out) --manifest $(in)",
 }
 
 genrule {
     name: "remote-color-resources-arsc",
     srcs: [":remote-color-resources-apk"],
     out: ["res/raw/remote_views_color_resources.arsc"],
-    cmd: "mkdir -p $(genDir)/remote-color-resources-arsc && "
-        + "unzip -x $(in) resources.arsc -d $(genDir)/remote-color-resources-arsc && "
-        + "mkdir -p $$(dirname $(out)) && "
-        + "mv $(genDir)/remote-color-resources-arsc/resources.arsc $(out) && "
-        + "echo 'Created $(out)'"
+    cmd: "mkdir -p $(genDir)/remote-color-resources-arsc && " +
+        "unzip -x $(in) resources.arsc -d $(genDir)/remote-color-resources-arsc && " +
+        "mkdir -p $$(dirname $(out)) && " +
+        "mv $(genDir)/remote-color-resources-arsc/resources.arsc $(out) && " +
+        "echo 'Created $(out)'",
 }
 
 genrule {
@@ -95,11 +95,11 @@
         "remote_color_resources_res/symbols.xml",
     ],
     out: ["remote_views_color_resources.zip"],
-    cmd: "INPUTS=($(in)) && "
-        + "RES_DIR=$$(dirname $$(dirname $${INPUTS[0]})) && "
-        + "mkdir -p $$RES_DIR/values && "
-        + "cp $${INPUTS[1]} $$RES_DIR/values && "
-        + "$(location soong_zip) -o $(out) -C $$RES_DIR -D $$RES_DIR"
+    cmd: "INPUTS=($(in)) && " +
+        "RES_DIR=$$(dirname $$(dirname $${INPUTS[0]})) && " +
+        "mkdir -p $$RES_DIR/values && " +
+        "cp $${INPUTS[1]} $$RES_DIR/values && " +
+        "$(location soong_zip) -o $(out) -C $$RES_DIR -D $$RES_DIR",
 }
 
 android_app {
@@ -154,31 +154,21 @@
     cmd: "cp $(in) $(out)",
 }
 
-// This logic can be removed once robolectric's transition to binary resources is complete
-filegroup {
-    name: "robolectric_framework_raw_res_files",
-    srcs: [
-        "assets/**/*",
-        "res/**/*",
-        ":remote-color-resources-arsc",
-    ],
-}
-
 // Generate a text file containing a list of permissions that non-system apps
 // are allowed to obtain.
 genrule {
-  name: "permission-list-normal",
-  out: ["permission-list-normal.txt"],
-  srcs: ["AndroidManifest.xml"],
-  cmd: "cat $(in) " +
-       // xmllint has trouble accessing attributes under the android namespace.
-       // Strip these prefixes prior to processing with xmllint.
-       " | sed -r 's/android:(name|protectionLevel)/\\1/g' " +
-       " | $(location xmllint) /dev/stdin --xpath " +
-       " '//permission[not(contains(@protectionLevel, \"signature\"))]/@name'" +
-       // The result of xmllint is name="value" pairs. Format these to just the
-       // permission name, one per-line.
-       " | sed -r 's/\\s*name=\\s*//g' | tr -d '\"'" +
-       " > $(out)",
-  tools: ["xmllint"]
+    name: "permission-list-normal",
+    out: ["permission-list-normal.txt"],
+    srcs: ["AndroidManifest.xml"],
+    cmd: "cat $(in) " +
+        // xmllint has trouble accessing attributes under the android namespace.
+        // Strip these prefixes prior to processing with xmllint.
+        " | sed -r 's/android:(name|protectionLevel)/\\1/g' " +
+        " | $(location xmllint) /dev/stdin --xpath " +
+        " '//permission[not(contains(@protectionLevel, \"signature\"))]/@name'" +
+        // The result of xmllint is name="value" pairs. Format these to just the
+        // permission name, one per-line.
+        " | sed -r 's/\\s*name=\\s*//g' | tr -d '\"'" +
+        " > $(out)",
+    tools: ["xmllint"],
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3fbd797..cd518ce 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2975,7 +2975,7 @@
     <permission android:name="android.permission.REAL_GET_TASKS"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @TestApi Allows an application to start a task from a ActivityManager#RecentTaskInfo.
+    <!-- @SystemApi Allows an application to start a task from a ActivityManager#RecentTaskInfo.
          @hide -->
     <permission android:name="android.permission.START_TASKS_FROM_RECENTS"
         android:protectionLevel="signature|privileged|recents" />
diff --git a/core/res/res/layout/floating_popup_container.xml b/core/res/res/layout/floating_popup_container.xml
index ca03737..776a35d 100644
--- a/core/res/res/layout/floating_popup_container.xml
+++ b/core/res/res/layout/floating_popup_container.xml
@@ -16,6 +16,7 @@
 */
 -->
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/floating_popup_container"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:padding="0dp"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9890614..116b150 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2392,9 +2392,6 @@
     <!-- The list of supported dream complications -->
     <integer-array name="config_supportedDreamComplications">
     </integer-array>
-    <!-- The list of dream complications which should be enabled by default -->
-    <integer-array name="config_dreamComplicationsEnabledByDefault">
-    </integer-array>
 
     <!-- Are we allowed to dream while not plugged in? -->
     <bool name="config_dreamsEnabledOnBattery">false</bool>
@@ -5872,4 +5869,10 @@
 
     <!-- The number of tasks to scan to get the visibility of Home -->
     <integer name="config_maxScanTasksForHomeVisibility">10</integer>
+
+    <!-- Device state that corresponds to rear display mode, feature provided
+         through Jetpack WindowManager
+         TODO(b/236022708) Move rear display state to device state config file
+    -->
+    <integer name="config_deviceStateRearDisplay">-1</integer>
 </resources>
diff --git a/core/res/res/values/locale_config.xml b/core/res/res/values/locale_config.xml
index e9b42d3..78ec145 100644
--- a/core/res/res/values/locale_config.xml
+++ b/core/res/res/values/locale_config.xml
@@ -23,7 +23,7 @@
         <item>ak-GH</item> <!-- Akan (Ghana) -->
         <item>am-ET</item> <!-- Amharic (Ethiopia) -->
         <item>ar-AE</item> <!-- Arabic (United Arab Emirates) -->
-        <item>ar-AE-u-nu-latn</item> <!-- Arabic (United Arab Emirates, Western Digits) -->
+        <item>ar-AE-u-nu-arab</item> <!-- Arabic (United Arab Emirates, Arabic Digits) -->
         <item>ar-BH</item> <!-- Arabic (Bahrain) -->
         <item>ar-BH-u-nu-latn</item> <!-- Arabic (Bahrain, Western Digits) -->
         <item>ar-DJ</item> <!-- Arabic (Djibouti) -->
@@ -190,6 +190,7 @@
         <item>en-MS</item> <!-- English (Montserrat) -->
         <item>en-MT</item> <!-- English (Malta) -->
         <item>en-MU</item> <!-- English (Mauritius) -->
+        <item>en-MV</item> <!-- English (Maldives) -->
         <item>en-MW</item> <!-- English (Malawi) -->
         <item>en-MY</item> <!-- English (Malaysia) -->
         <item>en-NA</item> <!-- English (Namibia) -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index b7da6ae..e5b1cf9 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3365,6 +3365,13 @@
     <!-- Default not selected text used by accessibility for an element that can be selected or unselected. [CHAR LIMIT=NONE] -->
     <string name="not_selected">not selected</string>
 
+    <!-- Default label text used by accessibility for ratingbars. [CHAR LIMIT=NONE] -->
+    <string name ="rating_label">{ rating, plural,
+        =1 {One star out of {max}}
+        other {# stars out of {max}}
+    }
+    </string>
+
     <!-- Default state description for indeterminate progressbar. [CHAR LIMIT=NONE] -->
     <string name="in_progress">in progress</string>
 
@@ -5422,8 +5429,10 @@
     <!-- Hint text in a search edit box (used to filter long language / country lists) [CHAR LIMIT=25] -->
     <string name="search_language_hint">Type language name</string>
 
-    <!-- List section subheader for the language picker, containing a list of suggested languages determined by the default region [CHAR LIMIT=30] -->
+    <!-- List section subheader for the language picker, containing a list of suggested languages [CHAR LIMIT=30] -->
     <string name="language_picker_section_suggested">Suggested</string>
+    <!-- "List section subheader for the language picker, containing a list of suggested regions available for that language [CHAR LIMIT=30] -->
+    <string name="language_picker_regions_section_suggested">Suggested</string>
 
     <!-- List section subheader for the language picker, containing a list of suggested languages determined by the default region [CHAR LIMIT=30] -->
     <string name="language_picker_section_suggested_bilingual">Suggested languages</string>
@@ -5750,7 +5759,7 @@
 
     <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
     <string name="log_access_confirmation_body">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
-        \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device. Learn more
+        \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.
     </string>
 
     <!-- Privacy notice do not show [CHAR LIMIT=20] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0654fff..d7836c1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -925,6 +925,7 @@
   <java-symbol type="string" name="mobile_provisioning_apn" />
   <java-symbol type="string" name="mobile_provisioning_url" />
   <java-symbol type="string" name="quick_contacts_not_available" />
+  <java-symbol type="string" name="rating_label" />
   <java-symbol type="string" name="reboot_to_update_package" />
   <java-symbol type="string" name="reboot_to_update_prepare" />
   <java-symbol type="string" name="reboot_to_update_title" />
@@ -2228,7 +2229,6 @@
   <java-symbol type="string" name="config_dreamsDefaultComponent" />
   <java-symbol type="bool" name="config_dreamsOnlyEnabledForSystemUser" />
   <java-symbol type="array" name="config_supportedDreamComplications" />
-  <java-symbol type="array" name="config_dreamComplicationsEnabledByDefault" />
   <java-symbol type="array" name="config_disabledDreamComponents" />
   <java-symbol type="bool" name="config_dismissDreamOnActivityStart" />
   <java-symbol type="string" name="config_loggable_dream_prefix" />
@@ -3141,6 +3141,7 @@
   <java-symbol type="string" name="language_picker_section_all" />
   <java-symbol type="string" name="region_picker_section_all" />
   <java-symbol type="string" name="language_picker_section_suggested" />
+  <java-symbol type="string" name="language_picker_regions_section_suggested" />
   <java-symbol type="string" name="language_picker_section_suggested_bilingual" />
   <java-symbol type="string" name="region_picker_section_suggested_bilingual" />
   <java-symbol type="string" name="language_selection_title" />
@@ -4830,6 +4831,7 @@
   <java-symbol type="drawable" name="ic_swap_horiz" />
   <java-symbol type="array" name="config_deviceStatesAvailableForAppRequests" />
   <java-symbol type="array" name="config_serviceStateLocationAllowedPackages" />
+  <java-symbol type="integer" name="config_deviceStateRearDisplay"/>
 
   <!-- For app language picker -->
   <java-symbol type="string" name="system_locale_title" />
diff --git a/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java b/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java
new file mode 100644
index 0000000..2a3da05
--- /dev/null
+++ b/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static junit.framework.TestCase.assertEquals;
+
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.base.Strings;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NotificationChannelGroupTest {
+    private final String CLASS = "android.app.NotificationChannelGroup";
+
+    @Test
+    public void testLongStringFields() {
+        NotificationChannelGroup group = new NotificationChannelGroup("my_group_01", "groupName");
+
+        try {
+            String longString = Strings.repeat("A", 65536);
+            Field mName = Class.forName(CLASS).getDeclaredField("mName");
+            mName.setAccessible(true);
+            mName.set(group, longString);
+            Field mId = Class.forName(CLASS).getDeclaredField("mId");
+            mId.setAccessible(true);
+            mId.set(group, longString);
+            Field mDescription = Class.forName(CLASS).getDeclaredField("mDescription");
+            mDescription.setAccessible(true);
+            mDescription.set(group, longString);
+        } catch (NoSuchFieldException e) {
+            e.printStackTrace();
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        }
+
+        Parcel parcel = Parcel.obtain();
+        group.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        NotificationChannelGroup fromParcel =
+                NotificationChannelGroup.CREATOR.createFromParcel(parcel);
+        assertEquals(NotificationChannelGroup.MAX_TEXT_LENGTH, fromParcel.getId().length());
+        assertEquals(NotificationChannelGroup.MAX_TEXT_LENGTH, fromParcel.getName().length());
+        assertEquals(NotificationChannelGroup.MAX_TEXT_LENGTH,
+                fromParcel.getDescription().length());
+    }
+}
diff --git a/core/tests/coretests/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java
new file mode 100644
index 0000000..647bfe8
--- /dev/null
+++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static junit.framework.TestCase.assertEquals;
+
+import android.net.Uri;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.base.Strings;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NotificationChannelTest {
+    private final String CLASS = "android.app.NotificationChannel";
+
+    @Test
+    public void testLongStringFields() {
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+
+        try {
+            String longString = Strings.repeat("A", 65536);
+            Field mName = Class.forName(CLASS).getDeclaredField("mName");
+            mName.setAccessible(true);
+            mName.set(channel, longString);
+            Field mId = Class.forName(CLASS).getDeclaredField("mId");
+            mId.setAccessible(true);
+            mId.set(channel, longString);
+            Field mDesc = Class.forName(CLASS).getDeclaredField("mDesc");
+            mDesc.setAccessible(true);
+            mDesc.set(channel, longString);
+            Field mParentId = Class.forName(CLASS).getDeclaredField("mParentId");
+            mParentId.setAccessible(true);
+            mParentId.set(channel, longString);
+            Field mGroup = Class.forName(CLASS).getDeclaredField("mGroup");
+            mGroup.setAccessible(true);
+            mGroup.set(channel, longString);
+            Field mConversationId = Class.forName(CLASS).getDeclaredField("mConversationId");
+            mConversationId.setAccessible(true);
+            mConversationId.set(channel, longString);
+        } catch (NoSuchFieldException e) {
+            e.printStackTrace();
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        }
+
+        Parcel parcel = Parcel.obtain();
+        channel.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        NotificationChannel fromParcel = NotificationChannel.CREATOR.createFromParcel(parcel);
+        assertEquals(NotificationChannel.MAX_TEXT_LENGTH, fromParcel.getId().length());
+        assertEquals(NotificationChannel.MAX_TEXT_LENGTH, fromParcel.getName().length());
+        assertEquals(NotificationChannel.MAX_TEXT_LENGTH,
+                fromParcel.getDescription().length());
+        assertEquals(NotificationChannel.MAX_TEXT_LENGTH,
+                fromParcel.getParentChannelId().length());
+        assertEquals(NotificationChannel.MAX_TEXT_LENGTH,
+                fromParcel.getGroup().length());
+        assertEquals(NotificationChannel.MAX_TEXT_LENGTH,
+                fromParcel.getConversationId().length());
+    }
+
+    @Test
+    public void testLongAlertFields() {
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+
+        channel.setSound(Uri.parse("content://" + Strings.repeat("A",65536)),
+                Notification.AUDIO_ATTRIBUTES_DEFAULT);
+        channel.setVibrationPattern(new long[65550/2]);
+
+        Parcel parcel = Parcel.obtain();
+        channel.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        NotificationChannel fromParcel = NotificationChannel.CREATOR.createFromParcel(parcel);
+        assertEquals(NotificationChannel.MAX_VIBRATION_LENGTH,
+                fromParcel.getVibrationPattern().length);
+        assertEquals(NotificationChannel.MAX_TEXT_LENGTH,
+                fromParcel.getSound().toString().length());
+    }
+}
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 0a5a4d5..993ecf6 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -221,15 +221,15 @@
 
     @Test
     public void testRecyclePauseActivityItemItem() {
-        PauseActivityItem emptyItem = PauseActivityItem.obtain(false, false, 0, false);
-        PauseActivityItem item = PauseActivityItem.obtain(true, true, 5, true);
+        PauseActivityItem emptyItem = PauseActivityItem.obtain(false, false, 0, false, false);
+        PauseActivityItem item = PauseActivityItem.obtain(true, true, 5, true, true);
         assertNotSame(item, emptyItem);
         assertFalse(item.equals(emptyItem));
 
         item.recycle();
         assertEquals(item, emptyItem);
 
-        PauseActivityItem item2 = PauseActivityItem.obtain(true, false, 5, true);
+        PauseActivityItem item2 = PauseActivityItem.obtain(true, false, 5, true, true);
         assertSame(item, item2);
         assertFalse(item2.equals(emptyItem));
     }
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index e9bbdbe..b292d7d 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -234,7 +234,8 @@
     public void testPause() {
         // Write to parcel
         PauseActivityItem item = PauseActivityItem.obtain(true /* finished */,
-                true /* userLeaving */, 135 /* configChanges */, true /* dontReport */);
+                true /* userLeaving */, 135 /* configChanges */, true /* dontReport */,
+                true /* autoEnteringPip */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
diff --git a/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java b/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java
index c6f5924..2d3ed95 100644
--- a/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java
+++ b/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java
@@ -16,13 +16,12 @@
 
 package android.widget;
 
-import static com.android.internal.widget.floatingtoolbar.FloatingToolbar.FLOATING_TOOLBAR_TAG;
-
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.content.res.Resources;
 import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.Until;
 
@@ -33,25 +32,27 @@
 final class FloatingToolbarUtils {
 
     private final UiDevice mDevice;
+    private static final BySelector TOOLBAR_CONTAINER_SELECTOR =
+            By.res("android", "floating_popup_container");
 
     FloatingToolbarUtils() {
         mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
     }
 
     void waitForFloatingToolbarPopup() {
-        mDevice.wait(Until.findObject(By.desc(FLOATING_TOOLBAR_TAG)), 500);
+        mDevice.wait(Until.findObject(TOOLBAR_CONTAINER_SELECTOR), 500);
     }
 
     void assertFloatingToolbarIsDisplayed() {
         waitForFloatingToolbarPopup();
-        assertThat(mDevice.hasObject(By.desc(FLOATING_TOOLBAR_TAG))).isTrue();
+        assertThat(mDevice.hasObject(TOOLBAR_CONTAINER_SELECTOR)).isTrue();
     }
 
     void assertFloatingToolbarContainsItem(String itemLabel) {
         waitForFloatingToolbarPopup();
         assertWithMessage("Expected to find item labelled [" + itemLabel + "]")
                 .that(mDevice.hasObject(
-                        By.desc(FLOATING_TOOLBAR_TAG).hasDescendant(By.text(itemLabel))))
+                        TOOLBAR_CONTAINER_SELECTOR.hasDescendant(By.text(itemLabel))))
                 .isTrue();
     }
 
@@ -59,14 +60,14 @@
         waitForFloatingToolbarPopup();
         assertWithMessage("Expected to not find item labelled [" + itemLabel + "]")
                 .that(mDevice.hasObject(
-                        By.desc(FLOATING_TOOLBAR_TAG).hasDescendant(By.text(itemLabel))))
+                        TOOLBAR_CONTAINER_SELECTOR.hasDescendant(By.text(itemLabel))))
                 .isFalse();
     }
 
     void assertFloatingToolbarContainsItemAtIndex(String itemLabel, int index) {
         waitForFloatingToolbarPopup();
         assertWithMessage("Expected to find item labelled [" + itemLabel + "] at index " + index)
-                .that(mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG))
+                .that(mDevice.findObject(TOOLBAR_CONTAINER_SELECTOR)
                         .findObjects(By.clickable(true))
                         .get(index)
                         .getChildren()
@@ -77,7 +78,7 @@
 
     void clickFloatingToolbarItem(String label) {
         waitForFloatingToolbarPopup();
-        mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG))
+        mDevice.findObject(TOOLBAR_CONTAINER_SELECTOR)
                 .findObject(By.text(label))
                 .click();
     }
@@ -85,13 +86,13 @@
     void clickFloatingToolbarOverflowItem(String label) {
         // TODO: There might be a benefit to combining this with "clickFloatingToolbarItem" method.
         waitForFloatingToolbarPopup();
-        mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG))
+        mDevice.findObject(TOOLBAR_CONTAINER_SELECTOR)
                 .findObject(By.desc(str(R.string.floating_toolbar_open_overflow_description)))
                 .click();
         mDevice.wait(
-                Until.findObject(By.desc(FLOATING_TOOLBAR_TAG).hasDescendant(By.text(label))),
+                Until.findObject(TOOLBAR_CONTAINER_SELECTOR.hasDescendant(By.text(label))),
                 1000);
-        mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG))
+        mDevice.findObject(TOOLBAR_CONTAINER_SELECTOR)
                 .findObject(By.text(label))
                 .click();
     }
diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
index 9eb4ccb..a22dd1c 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
+
 import android.os.Parcel;
 
 import androidx.test.filters.SmallTest;
@@ -149,4 +151,14 @@
         container.getValues(counts);
         assertThat(counts).isEqualTo(expected);
     }
+
+    @Test
+    public void createFromBadParcel() {
+        // Check we don't crash the runtime if the Parcel data is bad (b/243434675).
+        Parcel parcel = Parcel.obtain();
+        parcel.writeInt(2);
+        parcel.setDataPosition(0);
+        assertThrows(RuntimeException.class,
+                () -> LongArrayMultiStateCounter.CREATOR.createFromParcel(parcel));
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
index e717ebb..fc86ebe 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
+
 import android.os.Parcel;
 
 import androidx.test.filters.SmallTest;
@@ -198,4 +200,14 @@
 
         assertThat(newCounter.getCount(0)).isEqualTo(116);
     }
+
+    @Test
+    public void createFromBadParcel() {
+        // Check we don't crash the runtime if the Parcel data is bad (b/243434675).
+        Parcel parcel = Parcel.obtain();
+        parcel.writeInt(13);
+        parcel.setDataPosition(0);
+        assertThrows(RuntimeException.class,
+                () -> LongMultiStateCounter.CREATOR.createFromParcel(parcel));
+    }
 }
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index f4a6f02..613eddd 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -298,8 +298,8 @@
 
         private void pauseActivity(ActivityClientRecord r) {
             mThread.handlePauseActivity(r, false /* finished */,
-                    false /* userLeaving */, 0 /* configChanges */, null /* pendingActions */,
-                    "test");
+                    false /* userLeaving */, 0 /* configChanges */, false /* autoEnteringPip */,
+                    null /* pendingActions */, "test");
         }
 
         private void stopActivity(ActivityClientRecord r) {
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 03d22ac..6aedcd4 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2065,6 +2065,12 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-108248992": {
+      "message": "Defer transition ready for TaskFragmentTransaction=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
+    },
     "-106400104": {
       "message": "Preload recents with %s",
       "level": "DEBUG",
@@ -2113,6 +2119,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/TaskFragment.java"
     },
+    "-79016993": {
+      "message": "Continue transition ready for TaskFragmentTransaction=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
+    },
     "-70719599": {
       "message": "Unregister remote animations for organizer=%s uid=%d pid=%d",
       "level": "VERBOSE",
@@ -2659,6 +2671,12 @@
       "group": "WM_DEBUG_ANIM",
       "at": "com\/android\/server\/wm\/WindowContainer.java"
     },
+    "390947100": {
+      "message": "Screenshotting %s [%s]",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "397382873": {
       "message": "Moving to PAUSED: %s %s",
       "level": "VERBOSE",
@@ -4177,6 +4195,12 @@
       "group": "WM_DEBUG_FOCUS_LIGHT",
       "at": "com\/android\/server\/wm\/InputMonitor.java"
     },
+    "2004282287": {
+      "message": "Override sync-method for %s because seamless rotating",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "2010476671": {
       "message": "Animation done in %s: reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b",
       "level": "VERBOSE",
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index f8c015f..8e2a59c 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -39,7 +39,11 @@
           <axis tag="wdth" stylevalue="100" />
           <axis tag="wght" stylevalue="300" />
         </font>
-        <font weight="400" style="normal">RobotoStatic-Regular.ttf</font>
+        <font weight="400" style="normal">Roboto-Regular.ttf
+          <axis tag="ital" stylevalue="0" />
+          <axis tag="wdth" stylevalue="100" />
+          <axis tag="wght" stylevalue="400" />
+        </font>
         <font weight="500" style="normal">Roboto-Regular.ttf
           <axis tag="ital" stylevalue="0" />
           <axis tag="wdth" stylevalue="100" />
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index b341a4e..c93b733 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -26,10 +26,21 @@
      @Retention(RetentionPolicy.SOURCE)
      @IntDef(value = {
              UNKNOWN,
+             /**
+              * Since some APIs accept either ImageFormat or PixelFormat (and the two
+              * enums do not overlap since they're both partial versions of the
+              * internal format enum), add PixelFormat values here so linting
+              * tools won't complain when method arguments annotated with
+              * ImageFormat are provided with PixelFormat values.
+              */
+             PixelFormat.RGBA_8888,
+             PixelFormat.RGBX_8888,
+             PixelFormat.RGB_888,
              RGB_565,
              YV12,
              Y8,
              Y16,
+             YCBCR_P010,
              NV16,
              NV21,
              YUY2,
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index 48aecd6..d0327159 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -28,7 +28,7 @@
  *
  * <p>See the
  * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
- * line-break property</a> for more information.</p>
+ * line-break property</a> for more information.
  */
 public final class LineBreakConfig {
 
@@ -72,7 +72,7 @@
      *
      * <p>Support for this line-break word style depends on locale. If the
      * current locale does not support phrase-based text wrapping, this setting
-     * has no effect.</p>
+     * has no effect.
      */
     public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1;
 
@@ -194,7 +194,7 @@
      * Constructor with line-break parameters.
      *
      * <p>Use {@link LineBreakConfig.Builder} to create the
-     * {@code LineBreakConfig} instance.</p>
+     * {@code LineBreakConfig} instance.
      */
     private LineBreakConfig(@LineBreakStyle int lineBreakStyle,
             @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index bdf703c..7e9c418 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -20,11 +20,14 @@
 import android.content.Context;
 
 import androidx.annotation.NonNull;
+import androidx.window.extensions.area.WindowAreaComponent;
+import androidx.window.extensions.area.WindowAreaComponentImpl;
 import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
 import androidx.window.extensions.embedding.SplitController;
 import androidx.window.extensions.layout.WindowLayoutComponent;
 import androidx.window.extensions.layout.WindowLayoutComponentImpl;
 
+
 /**
  * The reference implementation of {@link WindowExtensions} that implements the initial API version.
  */
@@ -33,10 +36,12 @@
     private final Object mLock = new Object();
     private volatile WindowLayoutComponent mWindowLayoutComponent;
     private volatile SplitController mSplitController;
+    private volatile WindowAreaComponent mWindowAreaComponent;
 
+    // TODO(b/241126279) Introduce constants to better version functionality
     @Override
     public int getVendorApiLevel() {
-        return 1;
+        return 2;
     }
 
     /**
@@ -75,4 +80,23 @@
         }
         return mSplitController;
     }
+
+    /**
+     * Returns a reference implementation of {@link WindowAreaComponent} if available,
+     * {@code null} otherwise. The implementation must match the API level reported in
+     * {@link WindowExtensions#getWindowAreaComponent()}.
+     * @return {@link WindowAreaComponent} OEM implementation.
+     */
+    public WindowAreaComponent getWindowAreaComponent() {
+        if (mWindowAreaComponent == null) {
+            synchronized (mLock) {
+                if (mWindowAreaComponent == null) {
+                    Context context = ActivityThread.currentApplication();
+                    mWindowAreaComponent =
+                            new WindowAreaComponentImpl(context);
+                }
+            }
+        }
+        return mWindowAreaComponent;
+    }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
new file mode 100644
index 0000000..3adae70
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.area;
+
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+
+import android.app.Activity;
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateRequest;
+import android.util.ArraySet;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Reference implementation of androidx.window.extensions.area OEM interface for use with
+ * WindowManager Jetpack.
+ *
+ * This component currently supports Rear Display mode with the ability to add and remove
+ * status listeners for this mode.
+ *
+ * The public methods in this class are thread-safe.
+ **/
+public class WindowAreaComponentImpl implements WindowAreaComponent,
+        DeviceStateManager.DeviceStateCallback {
+
+    private final Object mLock = new Object();
+
+    private final DeviceStateManager mDeviceStateManager;
+    private final Executor mExecutor;
+
+    @GuardedBy("mLock")
+    private final ArraySet<Consumer<Integer>> mRearDisplayStatusListeners = new ArraySet<>();
+    private final int mRearDisplayState;
+    @WindowAreaSessionState
+    private int mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE;
+
+    @GuardedBy("mLock")
+    private int mCurrentDeviceState = INVALID_DEVICE_STATE;
+    @GuardedBy("mLock")
+    private int mCurrentDeviceBaseState = INVALID_DEVICE_STATE;
+    @GuardedBy("mLock")
+    private DeviceStateRequest mDeviceStateRequest;
+
+    public WindowAreaComponentImpl(@NonNull Context context) {
+        mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
+        mExecutor = context.getMainExecutor();
+
+        // TODO(b/236022708) Move rear display state to device state config file
+        mRearDisplayState = context.getResources().getInteger(
+                R.integer.config_deviceStateRearDisplay);
+
+        mDeviceStateManager.registerCallback(mExecutor, this);
+    }
+
+    /**
+     * Adds a listener interested in receiving updates on the RearDisplayStatus
+     * of the device. Because this is being called from the OEM provided
+     * extensions, we will post the result of the listener on the executor
+     * provided by the developer at the initial call site.
+     *
+     * Depending on the initial state of the device, we will return either
+     * {@link WindowAreaComponent#STATUS_AVAILABLE} or
+     * {@link WindowAreaComponent#STATUS_UNAVAILABLE} if the feature is supported or not in that
+     * state respectively. When the rear display feature is triggered, we update the status to be
+     * {@link WindowAreaComponent#STATUS_UNAVAILABLE}. TODO(b/240727590) Prefix with AREA_
+     *
+     * TODO(b/239833099) Add a STATUS_ACTIVE option to let apps know if a feature is currently
+     * enabled.
+     *
+     * @param consumer {@link Consumer} interested in receiving updates to the status of
+     * rear display mode.
+     */
+    public void addRearDisplayStatusListener(
+            @NonNull Consumer<@WindowAreaStatus Integer> consumer) {
+        synchronized (mLock) {
+            mRearDisplayStatusListeners.add(consumer);
+
+            // If current device state is still invalid, we haven't gotten our initial value yet
+            if (mCurrentDeviceState == INVALID_DEVICE_STATE) {
+                return;
+            }
+            consumer.accept(getCurrentStatus());
+        }
+    }
+
+    /**
+     * Removes a listener no longer interested in receiving updates.
+     * @param consumer no longer interested in receiving updates to RearDisplayStatus
+     */
+    public void removeRearDisplayStatusListener(
+            @NonNull Consumer<@WindowAreaStatus Integer> consumer) {
+        synchronized (mLock) {
+            mRearDisplayStatusListeners.remove(consumer);
+        }
+    }
+
+    /**
+     * Creates and starts a rear display session and provides updates to the
+     * callback provided. Because this is being called from the OEM provided
+     * extensions, we will post the result of the listener on the executor
+     * provided by the developer at the initial call site.
+     *
+     * When we enable rear display mode, we submit a request to {@link DeviceStateManager}
+     * to override the device state to the state that corresponds to RearDisplay
+     * mode. When the {@link DeviceStateRequest} is activated, we let the
+     * consumer know that the session is active by sending
+     * {@link WindowAreaComponent#SESSION_STATE_ACTIVE}.
+     *
+     * @param activity to provide updates to the client on
+     * the status of the Session
+     * @param rearDisplaySessionCallback to provide updates to the client on
+     * the status of the Session
+     */
+    public void startRearDisplaySession(@NonNull Activity activity,
+            @NonNull Consumer<@WindowAreaSessionState Integer> rearDisplaySessionCallback) {
+        synchronized (mLock) {
+            if (mDeviceStateRequest != null) {
+                // Rear display session is already active
+                throw new IllegalStateException(
+                        "Unable to start new rear display session as one is already active");
+            }
+            mDeviceStateRequest = DeviceStateRequest.newBuilder(mRearDisplayState).build();
+            mDeviceStateManager.requestState(
+                    mDeviceStateRequest,
+                    mExecutor,
+                    new DeviceStateRequestCallbackAdapter(rearDisplaySessionCallback)
+            );
+        }
+    }
+
+    /**
+     * Ends the current rear display session and provides updates to the
+     * callback provided. Because this is being called from the OEM provided
+     * extensions, we will post the result of the listener on the executor
+     * provided by the developer.
+     */
+    public void endRearDisplaySession() {
+        synchronized (mLock) {
+            if (mDeviceStateRequest != null || isRearDisplayActive()) {
+                mDeviceStateRequest = null;
+                mDeviceStateManager.cancelStateRequest();
+            } else {
+                throw new IllegalStateException(
+                        "Unable to cancel a rear display session as there is no active session");
+            }
+        }
+    }
+
+    @Override
+    public void onBaseStateChanged(int state) {
+        synchronized (mLock) {
+            mCurrentDeviceBaseState = state;
+            if (state == mCurrentDeviceState) {
+                updateStatusConsumers(getCurrentStatus());
+            }
+        }
+    }
+
+    @Override
+    public void onStateChanged(int state) {
+        synchronized (mLock) {
+            mCurrentDeviceState = state;
+            updateStatusConsumers(getCurrentStatus());
+        }
+    }
+
+    @GuardedBy("mLock")
+    private int getCurrentStatus() {
+        if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE
+                || isRearDisplayActive()) {
+            return WindowAreaComponent.STATUS_UNAVAILABLE;
+        }
+        return WindowAreaComponent.STATUS_AVAILABLE;
+    }
+
+    /**
+     * Helper method to determine if a rear display session is currently active by checking
+     * if the current device configuration matches that of rear display. This would be true
+     * if there is a device override currently active (base state != current state) and the current
+     * state is that which corresponds to {@code mRearDisplayState}
+     * @return {@code true} if the device is in rear display mode and {@code false} if not
+     */
+    @GuardedBy("mLock")
+    private boolean isRearDisplayActive() {
+        return (mCurrentDeviceState != mCurrentDeviceBaseState) && (mCurrentDeviceState
+                == mRearDisplayState);
+    }
+
+    @GuardedBy("mLock")
+    private void updateStatusConsumers(@WindowAreaStatus int windowAreaStatus) {
+        synchronized (mLock) {
+            for (int i = 0; i < mRearDisplayStatusListeners.size(); i++) {
+                mRearDisplayStatusListeners.valueAt(i).accept(windowAreaStatus);
+            }
+        }
+    }
+
+    /**
+     * Callback for the {@link DeviceStateRequest} to be notified of when the request has been
+     * activated or cancelled. This callback provides information to the client library
+     * on the status of the RearDisplay session through {@code mRearDisplaySessionCallback}
+     */
+    private class DeviceStateRequestCallbackAdapter implements DeviceStateRequest.Callback {
+
+        private final Consumer<Integer> mRearDisplaySessionCallback;
+
+        DeviceStateRequestCallbackAdapter(@NonNull Consumer<Integer> callback) {
+            mRearDisplaySessionCallback = callback;
+        }
+
+        @Override
+        public void onRequestActivated(@NonNull DeviceStateRequest request) {
+            synchronized (mLock) {
+                if (request.equals(mDeviceStateRequest)) {
+                    mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_ACTIVE;
+                    mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus);
+                    updateStatusConsumers(getCurrentStatus());
+                }
+            }
+        }
+
+        @Override
+        public void onRequestCanceled(DeviceStateRequest request) {
+            synchronized (mLock) {
+                if (request.equals(mDeviceStateRequest)) {
+                    mDeviceStateRequest = null;
+                }
+                mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE;
+                mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus);
+                updateStatusConsumers(getCurrentStatus());
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index 6bfb16a..f24401f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -20,21 +20,26 @@
 
 import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT;
 import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
+import static androidx.window.util.ExtensionHelper.isZero;
 import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
 import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
 
-import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityClient;
 import android.app.Application;
 import android.app.WindowConfiguration;
+import android.content.ComponentCallbacks;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.util.ArrayMap;
+import android.window.WindowContext;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiContext;
 import androidx.window.common.CommonFoldingFeature;
 import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
 import androidx.window.common.EmptyLifecycleCallbacksAdapter;
@@ -58,11 +63,14 @@
 public class WindowLayoutComponentImpl implements WindowLayoutComponent {
     private static final String TAG = "SampleExtension";
 
-    private final Map<Activity, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners =
+    private final Map<Context, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners =
             new ArrayMap<>();
 
     private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
 
+    private final Map<IBinder, WindowContextConfigListener> mWindowContextConfigListeners =
+            new ArrayMap<>();
+
     public WindowLayoutComponentImpl(@NonNull Context context) {
         ((Application) context.getApplicationContext())
                 .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
@@ -78,14 +86,42 @@
      * @param activity hosting a {@link android.view.Window}
      * @param consumer interested in receiving updates to {@link WindowLayoutInfo}
      */
+    @Override
     public void addWindowLayoutInfoListener(@NonNull Activity activity,
             @NonNull Consumer<WindowLayoutInfo> consumer) {
+        addWindowLayoutInfoListener((Context) activity, consumer);
+    }
+
+    /**
+     * Similar to {@link #addWindowLayoutInfoListener(Activity, Consumer)}, but takes a UI Context
+     * as a parameter.
+     */
+    // TODO(b/204073440): Add @Override to hook the API in WM extensions library.
+    public void addWindowLayoutInfoListener(@NonNull @UiContext Context context,
+            @NonNull Consumer<WindowLayoutInfo> consumer) {
+        if (mWindowLayoutChangeListeners.containsKey(context)
+                || mWindowLayoutChangeListeners.containsValue(consumer)) {
+            // Early return if the listener or consumer has been registered.
+            return;
+        }
+        if (!context.isUiContext()) {
+            throw new IllegalArgumentException("Context must be a UI Context, which should be"
+                    + " an Activity or a WindowContext");
+        }
         mFoldingFeatureProducer.getData((features) -> {
             // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
-            WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(activity, features);
+            WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features);
             consumer.accept(newWindowLayout);
         });
-        mWindowLayoutChangeListeners.put(activity, consumer);
+        mWindowLayoutChangeListeners.put(context, consumer);
+
+        if (context instanceof WindowContext) {
+            final IBinder windowContextToken = context.getWindowContextToken();
+            final WindowContextConfigListener listener =
+                    new WindowContextConfigListener(windowContextToken);
+            context.registerComponentCallbacks(listener);
+            mWindowContextConfigListeners.put(windowContextToken, listener);
+        }
     }
 
     /**
@@ -93,18 +129,30 @@
      *
      * @param consumer no longer interested in receiving updates to {@link WindowLayoutInfo}
      */
+    @Override
     public void removeWindowLayoutInfoListener(@NonNull Consumer<WindowLayoutInfo> consumer) {
+        for (Context context : mWindowLayoutChangeListeners.keySet()) {
+            if (!mWindowLayoutChangeListeners.get(context).equals(consumer)) {
+                continue;
+            }
+            if (context instanceof WindowContext) {
+                final IBinder token = context.getWindowContextToken();
+                context.unregisterComponentCallbacks(mWindowContextConfigListeners.get(token));
+                mWindowContextConfigListeners.remove(token);
+            }
+            break;
+        }
         mWindowLayoutChangeListeners.values().remove(consumer);
     }
 
     @NonNull
-    Set<Activity> getActivitiesListeningForLayoutChanges() {
+    Set<Context> getContextsListeningForLayoutChanges() {
         return mWindowLayoutChangeListeners.keySet();
     }
 
     private boolean isListeningForLayoutChanges(IBinder token) {
-        for (Activity activity: getActivitiesListeningForLayoutChanges()) {
-            if (token.equals(activity.getWindow().getAttributes().token)) {
+        for (Context context: getContextsListeningForLayoutChanges()) {
+            if (token.equals(Context.getToken(context))) {
                 return true;
             }
         }
@@ -138,10 +186,10 @@
     }
 
     private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
-        for (Activity activity : getActivitiesListeningForLayoutChanges()) {
+        for (Context context : getContextsListeningForLayoutChanges()) {
             // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
-            Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(activity);
-            WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(activity, storedFeatures);
+            Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(context);
+            WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, storedFeatures);
             layoutConsumer.accept(newWindowLayout);
         }
     }
@@ -149,11 +197,12 @@
     /**
      * Translates the {@link DisplayFeature} into a {@link WindowLayoutInfo} when a
      * valid state is found.
-     * @param activity a proxy for the {@link android.view.Window} that contains the
+     * @param context a proxy for the {@link android.view.Window} that contains the
+     * {@link DisplayFeature}.
      */
-    private WindowLayoutInfo getWindowLayoutInfo(
-            @NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) {
-        List<DisplayFeature> displayFeatureList = getDisplayFeatures(activity, storedFeatures);
+    private WindowLayoutInfo getWindowLayoutInfo(@NonNull @UiContext Context context,
+            List<CommonFoldingFeature> storedFeatures) {
+        List<DisplayFeature> displayFeatureList = getDisplayFeatures(context, storedFeatures);
         return new WindowLayoutInfo(displayFeatureList);
     }
 
@@ -170,18 +219,18 @@
      * bounds are not valid, constructing a {@link FoldingFeature} will throw an
      * {@link IllegalArgumentException} since this can cause negative UI effects down stream.
      *
-     * @param activity a proxy for the {@link android.view.Window} that contains the
+     * @param context a proxy for the {@link android.view.Window} that contains the
      * {@link DisplayFeature}.
      * are within the {@link android.view.Window} of the {@link Activity}
      */
     private List<DisplayFeature> getDisplayFeatures(
-            @NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) {
+            @NonNull @UiContext Context context, List<CommonFoldingFeature> storedFeatures) {
         List<DisplayFeature> features = new ArrayList<>();
-        if (!shouldReportDisplayFeatures(activity)) {
+        if (!shouldReportDisplayFeatures(context)) {
             return features;
         }
 
-        int displayId = activity.getDisplay().getDisplayId();
+        int displayId = context.getDisplay().getDisplayId();
         for (CommonFoldingFeature baseFeature : storedFeatures) {
             Integer state = convertToExtensionState(baseFeature.getState());
             if (state == null) {
@@ -189,9 +238,9 @@
             }
             Rect featureRect = baseFeature.getRect();
             rotateRectToDisplayRotation(displayId, featureRect);
-            transformToWindowSpaceRect(activity, featureRect);
+            transformToWindowSpaceRect(context, featureRect);
 
-            if (!isRectZero(featureRect)) {
+            if (!isZero(featureRect)) {
                 // TODO(b/228641877): Remove guarding when fixed.
                 features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
             }
@@ -203,15 +252,21 @@
      * Checks whether display features should be reported for the activity.
      * TODO(b/238948678): Support reporting display features in all windowing modes.
      */
-    private boolean shouldReportDisplayFeatures(@NonNull Activity activity) {
-        int displayId = activity.getDisplay().getDisplayId();
+    private boolean shouldReportDisplayFeatures(@NonNull @UiContext Context context) {
+        int displayId = context.getDisplay().getDisplayId();
         if (displayId != DEFAULT_DISPLAY) {
             // Display features are not supported on secondary displays.
             return false;
         }
-        final int taskWindowingMode = ActivityClient.getInstance().getTaskWindowingMode(
-                activity.getActivityToken());
-        if (taskWindowingMode == -1) {
+        final int windowingMode;
+        if (context instanceof Activity) {
+            windowingMode = ActivityClient.getInstance().getTaskWindowingMode(
+                    context.getActivityToken());
+        } else {
+            windowingMode = context.getResources().getConfiguration().windowConfiguration
+                    .getWindowingMode();
+        }
+        if (windowingMode == -1) {
             // If we cannot determine the task windowing mode for any reason, it is likely that we
             // won't be able to determine its position correctly as well. DisplayFeatures' bounds
             // in this case can't be computed correctly, so we should skip.
@@ -219,36 +274,43 @@
         }
         // It is recommended not to report any display features in multi-window mode, since it
         // won't be possible to synchronize the display feature positions with window movement.
-        return !WindowConfiguration.inMultiWindowMode(taskWindowingMode);
+        return !WindowConfiguration.inMultiWindowMode(windowingMode);
     }
 
-    /**
-     * Returns {@link true} if a {@link Rect} has zero width and zero height,
-     * {@code false} otherwise.
-     */
-    private boolean isRectZero(Rect rect) {
-        return rect.width() == 0 && rect.height() == 0;
+    private void onDisplayFeaturesChangedIfListening(@NonNull IBinder token) {
+        if (isListeningForLayoutChanges(token)) {
+            mFoldingFeatureProducer.getData(
+                    WindowLayoutComponentImpl.this::onDisplayFeaturesChanged);
+        }
     }
 
     private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter {
         @Override
         public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
             super.onActivityCreated(activity, savedInstanceState);
-            onDisplayFeaturesChangedIfListening(activity);
+            onDisplayFeaturesChangedIfListening(activity.getActivityToken());
         }
 
         @Override
         public void onActivityConfigurationChanged(Activity activity) {
             super.onActivityConfigurationChanged(activity);
-            onDisplayFeaturesChangedIfListening(activity);
+            onDisplayFeaturesChangedIfListening(activity.getActivityToken());
+        }
+    }
+
+    private final class WindowContextConfigListener implements ComponentCallbacks {
+        final IBinder mToken;
+
+        WindowContextConfigListener(IBinder token) {
+            mToken = token;
         }
 
-        private void onDisplayFeaturesChangedIfListening(Activity activity) {
-            IBinder token = activity.getWindow().getAttributes().token;
-            if (token == null || isListeningForLayoutChanges(token)) {
-                mFoldingFeatureProducer.getData(
-                        WindowLayoutComponentImpl.this::onDisplayFeaturesChanged);
-            }
+        @Override
+        public void onConfigurationChanged(@NonNull Configuration newConfig) {
+            onDisplayFeaturesChangedIfListening(mToken);
         }
+
+        @Override
+        public void onLowMemory() {}
     }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
index 0da44ac..cbaa277 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
@@ -16,6 +16,7 @@
 
 package androidx.window.util;
 
+import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
 
 import java.util.LinkedHashSet;
@@ -25,25 +26,45 @@
 
 /**
  * Base class that provides the implementation for the callback mechanism of the
- * {@link DataProducer} API.
+ * {@link DataProducer} API.  This class is thread safe for adding, removing, and notifying
+ * consumers.
  *
  * @param <T> The type of data this producer returns through {@link DataProducer#getData}.
  */
 public abstract class BaseDataProducer<T> implements DataProducer<T> {
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
     private final Set<Consumer<T>> mCallbacks = new LinkedHashSet<>();
 
+    /**
+     * Adds a callback to the set of callbacks listening for data. Data is delivered through
+     * {@link BaseDataProducer#notifyDataChanged(Object)}. This method is thread safe. Callers
+     * should ensure that callbacks are thread safe.
+     * @param callback that will receive data from the producer.
+     */
     @Override
     public final void addDataChangedCallback(@NonNull Consumer<T> callback) {
-        mCallbacks.add(callback);
-        Optional<T> currentData = getCurrentData();
-        currentData.ifPresent(callback);
-        onListenersChanged(mCallbacks);
+        synchronized (mLock) {
+            mCallbacks.add(callback);
+            Optional<T> currentData = getCurrentData();
+            currentData.ifPresent(callback);
+            onListenersChanged(mCallbacks);
+        }
     }
 
+    /**
+     * Removes a callback to the set of callbacks listening for data. This method is thread safe
+     * for adding.
+     * @param callback that was registered in
+     * {@link BaseDataProducer#addDataChangedCallback(Consumer)}.
+     */
     @Override
     public final void removeDataChangedCallback(@NonNull Consumer<T> callback) {
-        mCallbacks.remove(callback);
-        onListenersChanged(mCallbacks);
+        synchronized (mLock) {
+            mCallbacks.remove(callback);
+            onListenersChanged(mCallbacks);
+        }
     }
 
     protected void onListenersChanged(Set<Consumer<T>> callbacks) {}
@@ -56,11 +77,14 @@
 
     /**
      * Called to notify all registered consumers that the data provided
-     * by {@link DataProducer#getData} has changed.
+     * by {@link DataProducer#getData} has changed. Calls to this are thread save but callbacks need
+     * to ensure thread safety.
      */
     protected void notifyDataChanged(T value) {
-        for (Consumer<T> callback : mCallbacks) {
-            callback.accept(value);
+        synchronized (mLock) {
+            for (Consumer<T> callback : mCallbacks) {
+                callback.accept(value);
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
index 2a593f1..31bf963 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
@@ -21,14 +21,15 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
-import android.app.Activity;
+import android.content.Context;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManagerGlobal;
 import android.view.DisplayInfo;
 import android.view.Surface;
+import android.view.WindowManager;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
+import androidx.annotation.UiContext;
 
 /**
  * Util class for both Sidecar and Extensions.
@@ -86,12 +87,9 @@
     }
 
     /** Transforms rectangle from absolute coordinate space to the window coordinate space. */
-    public static void transformToWindowSpaceRect(Activity activity, Rect inOutRect) {
-        Rect windowRect = getWindowBounds(activity);
-        if (windowRect == null) {
-            inOutRect.setEmpty();
-            return;
-        }
+    public static void transformToWindowSpaceRect(@NonNull @UiContext Context context,
+            Rect inOutRect) {
+        Rect windowRect = getWindowBounds(context);
         if (!Rect.intersects(inOutRect, windowRect)) {
             inOutRect.setEmpty();
             return;
@@ -103,9 +101,9 @@
     /**
      * Gets the current window bounds in absolute coordinates.
      */
-    @Nullable
-    private static Rect getWindowBounds(@NonNull Activity activity) {
-        return activity.getWindowManager().getCurrentWindowMetrics().getBounds();
+    @NonNull
+    private static Rect getWindowBounds(@NonNull @UiContext Context context) {
+        return context.getSystemService(WindowManager.class).getCurrentWindowMetrics().getBounds();
     }
 
     /**
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 918e514..e9a1721 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index 8533a59..b0dab90 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -16,92 +16,98 @@
 -->
 <!-- Layout for TvPipMenuView -->
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-            android:id="@+id/tv_pip_menu"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:gravity="center|top">
+                android:id="@+id/tv_pip_menu"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:gravity="center|top">
 
     <!-- Matches the PiP app content -->
-    <View
+    <FrameLayout
         android:id="@+id/tv_pip"
         android:layout_width="0dp"
         android:layout_height="0dp"
-        android:alpha="0"
-        android:background="@color/tv_pip_menu_background"
         android:layout_marginTop="@dimen/pip_menu_outer_space"
         android:layout_marginStart="@dimen/pip_menu_outer_space"
-        android:layout_marginEnd="@dimen/pip_menu_outer_space"/>
+        android:layout_marginEnd="@dimen/pip_menu_outer_space">
 
-    <ScrollView
-        android:id="@+id/tv_pip_menu_scroll"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_alignTop="@+id/tv_pip"
-        android:layout_alignStart="@+id/tv_pip"
-        android:layout_alignEnd="@+id/tv_pip"
-        android:layout_alignBottom="@+id/tv_pip"
-        android:scrollbars="none"
-        android:visibility="gone"/>
+        <View
+            android:id="@+id/tv_pip_menu_background"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="@color/tv_pip_menu_background"
+            android:alpha="0"/>
 
-    <HorizontalScrollView
-        android:id="@+id/tv_pip_menu_horizontal_scroll"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_alignTop="@+id/tv_pip"
-        android:layout_alignStart="@+id/tv_pip"
-        android:layout_alignEnd="@+id/tv_pip"
-        android:layout_alignBottom="@+id/tv_pip"
-        android:scrollbars="none">
+        <View
+            android:id="@+id/tv_pip_menu_dim_layer"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="@color/tv_pip_menu_dim_layer"
+            android:alpha="0"/>
 
-        <LinearLayout
-            android:id="@+id/tv_pip_menu_action_buttons"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal"
-            android:alpha="0">
+        <ScrollView
+            android:id="@+id/tv_pip_menu_scroll"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scrollbars="none"
+            android:visibility="gone"/>
 
-            <Space
-                android:layout_width="@dimen/pip_menu_button_wrapper_margin"
-                android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
+        <HorizontalScrollView
+            android:id="@+id/tv_pip_menu_horizontal_scroll"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scrollbars="none">
 
-            <com.android.wm.shell.common.TvWindowMenuActionButton
-                android:id="@+id/tv_pip_menu_fullscreen_button"
+            <LinearLayout
+                android:id="@+id/tv_pip_menu_action_buttons"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:src="@drawable/pip_ic_fullscreen_white"
-                android:text="@string/pip_fullscreen" />
+                android:orientation="horizontal"
+                android:alpha="0">
 
-            <com.android.wm.shell.common.TvWindowMenuActionButton
-                android:id="@+id/tv_pip_menu_close_button"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:src="@drawable/pip_ic_close_white"
-                android:text="@string/pip_close" />
+                <Space
+                    android:layout_width="@dimen/pip_menu_button_wrapper_margin"
+                    android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
 
-            <!-- More TvPipMenuActionButtons may be added here at runtime. -->
+                <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+                    android:id="@+id/tv_pip_menu_fullscreen_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/pip_ic_fullscreen_white"
+                    android:text="@string/pip_fullscreen" />
 
-            <com.android.wm.shell.common.TvWindowMenuActionButton
-                android:id="@+id/tv_pip_menu_move_button"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:src="@drawable/pip_ic_move_white"
-                android:text="@string/pip_move" />
+                <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+                    android:id="@+id/tv_pip_menu_close_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/pip_ic_close_white"
+                    android:text="@string/pip_close" />
 
-            <com.android.wm.shell.common.TvWindowMenuActionButton
-                android:id="@+id/tv_pip_menu_expand_button"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:src="@drawable/pip_ic_collapse"
-                android:visibility="gone"
-                android:text="@string/pip_collapse" />
+                <!-- More TvPipMenuActionButtons may be added here at runtime. -->
 
-            <Space
-                android:layout_width="@dimen/pip_menu_button_wrapper_margin"
-                android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
+                <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+                    android:id="@+id/tv_pip_menu_move_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/pip_ic_move_white"
+                    android:text="@string/pip_move" />
 
-        </LinearLayout>
-    </HorizontalScrollView>
+                <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+                    android:id="@+id/tv_pip_menu_expand_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/pip_ic_collapse"
+                    android:visibility="gone"
+                    android:text="@string/pip_collapse" />
 
+                <Space
+                    android:layout_width="@dimen/pip_menu_button_wrapper_margin"
+                    android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
+
+            </LinearLayout>
+        </HorizontalScrollView>
+    </FrameLayout>
+
+    <!-- Frame around the content, just overlapping the corners to make them round -->
     <View
         android:id="@+id/tv_pip_border"
         android:layout_width="0dp"
@@ -111,6 +117,7 @@
         android:layout_marginEnd="@dimen/pip_menu_outer_space_frame"
         android:background="@drawable/tv_pip_menu_border"/>
 
+    <!-- Temporarily extending the background to show an edu text hint for opening the menu -->
     <FrameLayout
         android:id="@+id/tv_pip_menu_edu_text_container"
         android:layout_width="match_parent"
@@ -138,6 +145,7 @@
             android:textAppearance="@style/TvPipEduText"/>
     </FrameLayout>
 
+    <!-- Frame around the PiP content + edu text hint - used to highlight open menu -->
     <View
         android:id="@+id/tv_pip_menu_frame"
         android:layout_width="match_parent"
@@ -145,7 +153,8 @@
         android:layout_margin="@dimen/pip_menu_outer_space_frame"
         android:background="@drawable/tv_pip_menu_border"/>
 
-    <com.android.wm.shell.common.TvWindowMenuActionButton
+    <!-- Move menu -->
+    <com.android.wm.shell.pip.tv.TvPipMenuActionButton
         android:id="@+id/tv_pip_menu_done_button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml
index 3e71c10..e6933ca 100644
--- a/libs/WindowManager/Shell/res/values/colors_tv.xml
+++ b/libs/WindowManager/Shell/res/values/colors_tv.xml
@@ -25,6 +25,7 @@
     <color name="tv_window_menu_icon_bg_unfocused">#990E0E0F</color>
 
     <color name="tv_pip_menu_focus_border">#E8EAED</color>
+    <color name="tv_pip_menu_dim_layer">#990E0E0F</color>
     <color name="tv_pip_menu_background">#1E232C</color>
 
     <color name="tv_pip_edu_text">#99D2E3FC</color>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
new file mode 100644
index 0000000..cc4db93
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.activityembedding;
+
+import static android.graphics.Matrix.MSCALE_X;
+import static android.graphics.Matrix.MTRANS_X;
+import static android.graphics.Matrix.MTRANS_Y;
+
+import android.annotation.CallSuper;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+import android.window.TransitionInfo;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Wrapper to handle the ActivityEmbedding animation update in one
+ * {@link SurfaceControl.Transaction}.
+ */
+class ActivityEmbeddingAnimationAdapter {
+
+    /**
+     * If {@link #mOverrideLayer} is set to this value, we don't want to override the surface layer.
+     */
+    private static final int LAYER_NO_OVERRIDE = -1;
+
+    final Animation mAnimation;
+    final TransitionInfo.Change mChange;
+    final SurfaceControl mLeash;
+
+    final Transformation mTransformation = new Transformation();
+    final float[] mMatrix = new float[9];
+    final float[] mVecs = new float[4];
+    final Rect mRect = new Rect();
+    private boolean mIsFirstFrame = true;
+    private int mOverrideLayer = LAYER_NO_OVERRIDE;
+
+    ActivityEmbeddingAnimationAdapter(@NonNull Animation animation,
+            @NonNull TransitionInfo.Change change) {
+        this(animation, change, change.getLeash());
+    }
+
+    /**
+     * @param leash the surface to animate, which is not necessary the same as
+     * {@link TransitionInfo.Change#getLeash()}, it can be a screenshot for example.
+     */
+    ActivityEmbeddingAnimationAdapter(@NonNull Animation animation,
+            @NonNull TransitionInfo.Change change, @NonNull SurfaceControl leash) {
+        mAnimation = animation;
+        mChange = change;
+        mLeash = leash;
+    }
+
+    /**
+     * Surface layer to be set at the first frame of the animation. We will not set the layer if it
+     * is set to {@link #LAYER_NO_OVERRIDE}.
+     */
+    final void overrideLayer(int layer) {
+        mOverrideLayer = layer;
+    }
+
+    /** Called on frame update. */
+    final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) {
+        if (mIsFirstFrame) {
+            t.show(mLeash);
+            if (mOverrideLayer != LAYER_NO_OVERRIDE) {
+                t.setLayer(mLeash, mOverrideLayer);
+            }
+            mIsFirstFrame = false;
+        }
+
+        // Extract the transformation to the current time.
+        mAnimation.getTransformation(Math.min(currentPlayTime, mAnimation.getDuration()),
+                mTransformation);
+        t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
+        onAnimationUpdateInner(t);
+    }
+
+    /** To be overridden by subclasses to adjust the animation surface change. */
+    void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+        final Point offset = mChange.getEndRelOffset();
+        mTransformation.getMatrix().postTranslate(offset.x, offset.y);
+        t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+        t.setAlpha(mLeash, mTransformation.getAlpha());
+        // Get current animation position.
+        final int positionX = Math.round(mMatrix[MTRANS_X]);
+        final int positionY = Math.round(mMatrix[MTRANS_Y]);
+        // The exiting surface starts at position: Change#getEndRelOffset() and moves with
+        // positionX varying. Offset our crop region by the amount we have slided so crop
+        // regions stays exactly on the original container in split.
+        final int cropOffsetX = offset.x - positionX;
+        final int cropOffsetY = offset.y - positionY;
+        final Rect cropRect = new Rect();
+        cropRect.set(mChange.getEndAbsBounds());
+        // Because window crop uses absolute position.
+        cropRect.offsetTo(0, 0);
+        cropRect.offset(cropOffsetX, cropOffsetY);
+        t.setCrop(mLeash, cropRect);
+    }
+
+    /** Called after animation finished. */
+    @CallSuper
+    void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
+        onAnimationUpdate(t, mAnimation.getDuration());
+    }
+
+    final long getDurationHint() {
+        return mAnimation.computeDurationHint();
+    }
+
+    /**
+     * Should be used when the {@link TransitionInfo.Change} is in split with others, and wants to
+     * animate together as one. This adapter will offset the animation leash to make the animate of
+     * two windows look like a single window.
+     */
+    static class SplitAdapter extends ActivityEmbeddingAnimationAdapter {
+        private final boolean mIsLeftHalf;
+        private final int mWholeAnimationWidth;
+
+        /**
+         * @param isLeftHalf whether this is the left half of the animation.
+         * @param wholeAnimationWidth the whole animation windows width.
+         */
+        SplitAdapter(@NonNull Animation animation, @NonNull TransitionInfo.Change change,
+                boolean isLeftHalf, int wholeAnimationWidth) {
+            super(animation, change);
+            mIsLeftHalf = isLeftHalf;
+            mWholeAnimationWidth = wholeAnimationWidth;
+            if (wholeAnimationWidth == 0) {
+                throw new IllegalArgumentException("SplitAdapter must provide wholeAnimationWidth");
+            }
+        }
+
+        @Override
+        void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+            final Point offset = mChange.getEndRelOffset();
+            float posX = offset.x;
+            final float posY = offset.y;
+            // This window is half of the whole animation window. Offset left/right to make it
+            // look as one with the other half.
+            mTransformation.getMatrix().getValues(mMatrix);
+            final int changeWidth = mChange.getEndAbsBounds().width();
+            final float scaleX = mMatrix[MSCALE_X];
+            final float totalOffset = mWholeAnimationWidth * (1 - scaleX) / 2;
+            final float curOffset = changeWidth * (1 - scaleX) / 2;
+            final float offsetDiff = totalOffset - curOffset;
+            if (mIsLeftHalf) {
+                posX += offsetDiff;
+            } else {
+                posX -= offsetDiff;
+            }
+            mTransformation.getMatrix().postTranslate(posX, posY);
+            t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+            t.setAlpha(mLeash, mTransformation.getAlpha());
+        }
+    }
+
+    /**
+     * Should be used for the animation of the snapshot of a {@link TransitionInfo.Change} that has
+     * size change.
+     */
+    static class SnapshotAdapter extends ActivityEmbeddingAnimationAdapter {
+
+        SnapshotAdapter(@NonNull Animation animation, @NonNull TransitionInfo.Change change,
+                @NonNull SurfaceControl snapshotLeash) {
+            super(animation, change, snapshotLeash);
+        }
+
+        @Override
+        void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+            // Snapshot should always be placed at the top left of the animation leash.
+            mTransformation.getMatrix().postTranslate(0, 0);
+            t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+            t.setAlpha(mLeash, mTransformation.getAlpha());
+        }
+
+        @Override
+        void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
+            super.onAnimationEnd(t);
+            // Remove the screenshot leash after animation is finished.
+            t.remove(mLeash);
+        }
+    }
+
+    /**
+     * Should be used for the animation of the {@link TransitionInfo.Change} that has size change.
+     */
+    static class BoundsChangeAdapter extends ActivityEmbeddingAnimationAdapter {
+
+        BoundsChangeAdapter(@NonNull Animation animation, @NonNull TransitionInfo.Change change) {
+            super(animation, change);
+        }
+
+        @Override
+        void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+            final Point offset = mChange.getEndRelOffset();
+            mTransformation.getMatrix().postTranslate(offset.x, offset.y);
+            t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+            t.setAlpha(mLeash, mTransformation.getAlpha());
+
+            // The following applies an inverse scale to the clip-rect so that it crops "after" the
+            // scale instead of before.
+            mVecs[1] = mVecs[2] = 0;
+            mVecs[0] = mVecs[3] = 1;
+            mTransformation.getMatrix().mapVectors(mVecs);
+            mVecs[0] = 1.f / mVecs[0];
+            mVecs[3] = 1.f / mVecs[3];
+            final Rect clipRect = mTransformation.getClipRect();
+            mRect.left = (int) (clipRect.left * mVecs[0] + 0.5f);
+            mRect.right = (int) (clipRect.right * mVecs[0] + 0.5f);
+            mRect.top = (int) (clipRect.top * mVecs[3] + 0.5f);
+            mRect.bottom = (int) (clipRect.bottom * mVecs[3] + 0.5f);
+            t.setCrop(mLeash, mRect);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
new file mode 100644
index 0000000..7e0795d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.activityembedding;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.common.ScreenshotUtils;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+
+/** To run the ActivityEmbedding animations. */
+class ActivityEmbeddingAnimationRunner {
+
+    private static final String TAG = "ActivityEmbeddingAnimR";
+
+    private final ActivityEmbeddingController mController;
+    @VisibleForTesting
+    final ActivityEmbeddingAnimationSpec mAnimationSpec;
+
+    ActivityEmbeddingAnimationRunner(@NonNull Context context,
+            @NonNull ActivityEmbeddingController controller) {
+        mController = controller;
+        mAnimationSpec = new ActivityEmbeddingAnimationSpec(context);
+    }
+
+    /** Creates and starts animation for ActivityEmbedding transition. */
+    void startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction) {
+        final Animator animator = createAnimator(info, startTransaction, finishTransaction,
+                () -> mController.onAnimationFinished(transition));
+        startTransaction.apply();
+        animator.start();
+    }
+
+    /**
+     * Sets transition animation scale settings value.
+     * @param scale The setting value of transition animation scale.
+     */
+    void setAnimScaleSetting(float scale) {
+        mAnimationSpec.setAnimScaleSetting(scale);
+    }
+
+    /** Creates the animator for the given {@link TransitionInfo}. */
+    @VisibleForTesting
+    @NonNull
+    Animator createAnimator(@NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Runnable animationFinishCallback) {
+        final List<ActivityEmbeddingAnimationAdapter> adapters =
+                createAnimationAdapters(info, startTransaction);
+        long duration = 0;
+        for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
+            duration = Math.max(duration, adapter.getDurationHint());
+        }
+        final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
+        animator.setDuration(duration);
+        animator.addUpdateListener((anim) -> {
+            // Update all adapters in the same transaction.
+            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
+                adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
+            }
+            t.apply();
+        });
+        animator.addListener(new Animator.AnimatorListener() {
+            @Override
+            public void onAnimationStart(Animator animation) {}
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+                for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
+                    adapter.onAnimationEnd(t);
+                }
+                t.apply();
+                animationFinishCallback.run();
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {}
+
+            @Override
+            public void onAnimationRepeat(Animator animation) {}
+        });
+        return animator;
+    }
+
+    /**
+     * Creates list of {@link ActivityEmbeddingAnimationAdapter} to handle animations on all window
+     * changes.
+     */
+    @NonNull
+    private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters(
+            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
+        for (TransitionInfo.Change change : info.getChanges()) {
+            if (change.getMode() == TRANSIT_CHANGE
+                    && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
+                return createChangeAnimationAdapters(info, startTransaction);
+            }
+        }
+        if (Transitions.isClosingType(info.getType())) {
+            return createCloseAnimationAdapters(info);
+        }
+        return createOpenAnimationAdapters(info);
+    }
+
+    @NonNull
+    private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters(
+            @NonNull TransitionInfo info) {
+        return createOpenCloseAnimationAdapters(info, true /* isOpening */,
+                mAnimationSpec::loadOpenAnimation);
+    }
+
+    @NonNull
+    private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters(
+            @NonNull TransitionInfo info) {
+        return createOpenCloseAnimationAdapters(info, false /* isOpening */,
+                mAnimationSpec::loadCloseAnimation);
+    }
+
+    /**
+     * Creates {@link ActivityEmbeddingAnimationAdapter} for OPEN and CLOSE types of transition.
+     * @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type.
+     */
+    @NonNull
+    private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters(
+            @NonNull TransitionInfo info, boolean isOpening,
+            @NonNull BiFunction<TransitionInfo.Change, Rect, Animation> animationProvider) {
+        // We need to know if the change window is only a partial of the whole animation screen.
+        // If so, we will need to adjust it to make the whole animation screen looks like one.
+        final List<TransitionInfo.Change> openingChanges = new ArrayList<>();
+        final List<TransitionInfo.Change> closingChanges = new ArrayList<>();
+        final Rect openingWholeScreenBounds = new Rect();
+        final Rect closingWholeScreenBounds = new Rect();
+        for (TransitionInfo.Change change : info.getChanges()) {
+            final Rect bounds = new Rect(change.getEndAbsBounds());
+            final Point offset = change.getEndRelOffset();
+            bounds.offsetTo(offset.x, offset.y);
+            if (Transitions.isOpeningType(change.getMode())) {
+                openingChanges.add(change);
+                openingWholeScreenBounds.union(bounds);
+            } else {
+                closingChanges.add(change);
+                closingWholeScreenBounds.union(bounds);
+            }
+        }
+
+        // For OPEN transition, open windows should be above close windows.
+        // For CLOSE transition, open windows should be below close windows.
+        int offsetLayer = TYPE_LAYER_OFFSET;
+        final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
+        for (TransitionInfo.Change change : openingChanges) {
+            final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
+                    change, animationProvider, openingWholeScreenBounds);
+            if (isOpening) {
+                adapter.overrideLayer(offsetLayer++);
+            }
+            adapters.add(adapter);
+        }
+        for (TransitionInfo.Change change : closingChanges) {
+            final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
+                    change, animationProvider, closingWholeScreenBounds);
+            if (!isOpening) {
+                adapter.overrideLayer(offsetLayer++);
+            }
+            adapters.add(adapter);
+        }
+        return adapters;
+    }
+
+    @NonNull
+    private ActivityEmbeddingAnimationAdapter createOpenCloseAnimationAdapter(
+            @NonNull TransitionInfo.Change change,
+            @NonNull BiFunction<TransitionInfo.Change, Rect, Animation> animationProvider,
+            @NonNull Rect wholeAnimationBounds) {
+        final Animation animation = animationProvider.apply(change, wholeAnimationBounds);
+        final Rect bounds = new Rect(change.getEndAbsBounds());
+        final Point offset = change.getEndRelOffset();
+        bounds.offsetTo(offset.x, offset.y);
+        if (bounds.left == wholeAnimationBounds.left
+                && bounds.right != wholeAnimationBounds.right) {
+            // This is the left split of the whole animation window.
+            return new ActivityEmbeddingAnimationAdapter.SplitAdapter(animation, change,
+                    true /* isLeftHalf */, wholeAnimationBounds.width());
+        } else if (bounds.left != wholeAnimationBounds.left
+                && bounds.right == wholeAnimationBounds.right) {
+            // This is the right split of the whole animation window.
+            return new ActivityEmbeddingAnimationAdapter.SplitAdapter(animation, change,
+                    false /* isLeftHalf */, wholeAnimationBounds.width());
+        }
+        // Open/close window that fills the whole animation.
+        return new ActivityEmbeddingAnimationAdapter(animation, change);
+    }
+
+    @NonNull
+    private List<ActivityEmbeddingAnimationAdapter> createChangeAnimationAdapters(
+            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
+        final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
+        for (TransitionInfo.Change change : info.getChanges()) {
+            if (change.getMode() == TRANSIT_CHANGE
+                    && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
+                // This is the window with bounds change.
+                final WindowContainerToken parentToken = change.getParent();
+                final Rect parentBounds;
+                if (parentToken != null) {
+                    TransitionInfo.Change parentChange = info.getChange(parentToken);
+                    parentBounds = parentChange != null
+                            ? parentChange.getEndAbsBounds()
+                            : change.getEndAbsBounds();
+                } else {
+                    parentBounds = change.getEndAbsBounds();
+                }
+                final Animation[] animations =
+                        mAnimationSpec.createChangeBoundsChangeAnimations(change, parentBounds);
+                // Adapter for the starting screenshot leash.
+                final SurfaceControl screenshotLeash = createScreenshot(change, startTransaction);
+                if (screenshotLeash != null) {
+                    // The screenshot leash will be removed in SnapshotAdapter#onAnimationEnd
+                    adapters.add(new ActivityEmbeddingAnimationAdapter.SnapshotAdapter(
+                            animations[0], change, screenshotLeash));
+                } else {
+                    Log.e(TAG, "Failed to take screenshot for change=" + change);
+                }
+                // Adapter for the ending bounds changed leash.
+                adapters.add(new ActivityEmbeddingAnimationAdapter.BoundsChangeAdapter(
+                        animations[1], change));
+                continue;
+            }
+
+            // These are the other windows that don't have bounds change in the same transition.
+            final Animation animation;
+            if (!TransitionInfo.isIndependent(change, info)) {
+                // No-op if it will be covered by the changing parent window.
+                animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
+            } else if (Transitions.isClosingType(change.getMode())) {
+                animation = mAnimationSpec.createChangeBoundsCloseAnimation(change);
+            } else {
+                animation = mAnimationSpec.createChangeBoundsOpenAnimation(change);
+            }
+            adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change));
+        }
+        return adapters;
+    }
+
+    /** Takes a screenshot of the given {@link TransitionInfo.Change} surface. */
+    @Nullable
+    private SurfaceControl createScreenshot(@NonNull TransitionInfo.Change change,
+            @NonNull SurfaceControl.Transaction startTransaction) {
+        final Rect cropBounds = new Rect(change.getStartAbsBounds());
+        cropBounds.offsetTo(0, 0);
+        return ScreenshotUtils.takeScreenshot(startTransaction, change.getLeash(), cropBounds,
+                Integer.MAX_VALUE);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
new file mode 100644
index 0000000..6f06f28
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.activityembedding;
+
+
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.ClipRectAnimation;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.TranslateAnimation;
+import android.window.TransitionInfo;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+import com.android.internal.policy.TransitionAnimation;
+import com.android.wm.shell.transition.Transitions;
+
+/** Animation spec for ActivityEmbedding transition. */
+// TODO(b/206557124): provide an easier way to customize animation
+class ActivityEmbeddingAnimationSpec {
+
+    private static final String TAG = "ActivityEmbeddingAnimSpec";
+    private static final int CHANGE_ANIMATION_DURATION = 517;
+    private static final int CHANGE_ANIMATION_FADE_DURATION = 80;
+    private static final int CHANGE_ANIMATION_FADE_OFFSET = 30;
+
+    private final Context mContext;
+    private final TransitionAnimation mTransitionAnimation;
+    private final Interpolator mFastOutExtraSlowInInterpolator;
+    private final LinearInterpolator mLinearInterpolator;
+    private float mTransitionAnimationScaleSetting;
+
+    ActivityEmbeddingAnimationSpec(@NonNull Context context) {
+        mContext = context;
+        mTransitionAnimation = new TransitionAnimation(mContext, false /* debug */, TAG);
+        mFastOutExtraSlowInInterpolator = AnimationUtils.loadInterpolator(
+                mContext, android.R.interpolator.fast_out_extra_slow_in);
+        mLinearInterpolator = new LinearInterpolator();
+    }
+
+    /**
+     * Sets transition animation scale settings value.
+     * @param scale The setting value of transition animation scale.
+     */
+    void setAnimScaleSetting(float scale) {
+        mTransitionAnimationScaleSetting = scale;
+    }
+
+    /** For window that doesn't need to be animated. */
+    @NonNull
+    static Animation createNoopAnimation(@NonNull TransitionInfo.Change change) {
+        // Noop but just keep the window showing/hiding.
+        final float alpha = Transitions.isClosingType(change.getMode()) ? 0f : 1f;
+        return new AlphaAnimation(alpha, alpha);
+    }
+
+    /** Animation for window that is opening in a change transition. */
+    @NonNull
+    Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change) {
+        final Rect bounds = change.getEndAbsBounds();
+        final Point offset = change.getEndRelOffset();
+        // The window will be animated in from left or right depends on its position.
+        final int startLeft = offset.x == 0 ? -bounds.width() : bounds.width();
+
+        // The position should be 0-based as we will post translate in
+        // ActivityEmbeddingAnimationAdapter#onAnimationUpdate
+        final Animation animation = new TranslateAnimation(startLeft, 0, 0, 0);
+        animation.setInterpolator(mFastOutExtraSlowInInterpolator);
+        animation.setDuration(CHANGE_ANIMATION_DURATION);
+        animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
+        animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+        return animation;
+    }
+
+    /** Animation for window that is closing in a change transition. */
+    @NonNull
+    Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change) {
+        final Rect bounds = change.getEndAbsBounds();
+        final Point offset = change.getEndRelOffset();
+        // The window will be animated out to left or right depends on its position.
+        final int endLeft = offset.x == 0 ? -bounds.width() : bounds.width();
+
+        // The position should be 0-based as we will post translate in
+        // ActivityEmbeddingAnimationAdapter#onAnimationUpdate
+        final Animation animation = new TranslateAnimation(0, endLeft, 0, 0);
+        animation.setInterpolator(mFastOutExtraSlowInInterpolator);
+        animation.setDuration(CHANGE_ANIMATION_DURATION);
+        animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
+        animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+        return animation;
+    }
+
+    /**
+     * Animation for window that is changing (bounds change) in a change transition.
+     * @return the return array always has two elements. The first one is for the start leash, and
+     *         the second one is for the end leash.
+     */
+    @NonNull
+    Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo.Change change,
+            @NonNull Rect parentBounds) {
+        // Both start bounds and end bounds are in screen coordinates. We will post translate
+        // to the local coordinates in ActivityEmbeddingAnimationAdapter#onAnimationUpdate
+        final Rect startBounds = change.getStartAbsBounds();
+        final Rect endBounds = change.getEndAbsBounds();
+        float scaleX = ((float) startBounds.width()) / endBounds.width();
+        float scaleY = ((float) startBounds.height()) / endBounds.height();
+        // Start leash is a child of the end leash. Reverse the scale so that the start leash won't
+        // be scaled up with its parent.
+        float startScaleX = 1.f / scaleX;
+        float startScaleY = 1.f / scaleY;
+
+        // The start leash will be fade out.
+        final AnimationSet startSet = new AnimationSet(false /* shareInterpolator */);
+        final Animation startAlpha = new AlphaAnimation(1f, 0f);
+        startAlpha.setInterpolator(mLinearInterpolator);
+        startAlpha.setDuration(CHANGE_ANIMATION_FADE_DURATION);
+        startAlpha.setStartOffset(CHANGE_ANIMATION_FADE_OFFSET);
+        startSet.addAnimation(startAlpha);
+        final Animation startScale = new ScaleAnimation(startScaleX, startScaleX, startScaleY,
+                startScaleY);
+        startScale.setInterpolator(mFastOutExtraSlowInInterpolator);
+        startScale.setDuration(CHANGE_ANIMATION_DURATION);
+        startSet.addAnimation(startScale);
+        startSet.initialize(startBounds.width(), startBounds.height(), endBounds.width(),
+                endBounds.height());
+        startSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+
+        // The end leash will be moved into the end position while scaling.
+        final AnimationSet endSet = new AnimationSet(true /* shareInterpolator */);
+        endSet.setInterpolator(mFastOutExtraSlowInInterpolator);
+        final Animation endScale = new ScaleAnimation(scaleX, 1, scaleY, 1);
+        endScale.setDuration(CHANGE_ANIMATION_DURATION);
+        endSet.addAnimation(endScale);
+        // The position should be 0-based as we will post translate in
+        // ActivityEmbeddingAnimationAdapter#onAnimationUpdate
+        final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0,
+                0, 0);
+        endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
+        endSet.addAnimation(endTranslate);
+        // The end leash is resizing, we should update the window crop based on the clip rect.
+        final Rect startClip = new Rect(startBounds);
+        final Rect endClip = new Rect(endBounds);
+        startClip.offsetTo(0, 0);
+        endClip.offsetTo(0, 0);
+        final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
+        clipAnim.setDuration(CHANGE_ANIMATION_DURATION);
+        endSet.addAnimation(clipAnim);
+        endSet.initialize(startBounds.width(), startBounds.height(), parentBounds.width(),
+                parentBounds.height());
+        endSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+
+        return new Animation[]{startSet, endSet};
+    }
+
+    @NonNull
+    Animation loadOpenAnimation(@NonNull TransitionInfo.Change change,
+            @NonNull Rect wholeAnimationBounds) {
+        final boolean isEnter = Transitions.isOpeningType(change.getMode());
+        final Animation animation;
+        // TODO(b/207070762):
+        // 1. Implement clearTop version: R.anim.task_fragment_clear_top_close_enter/exit
+        // 2. Implement edgeExtension version
+        animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+                ? R.anim.task_fragment_open_enter
+                : R.anim.task_fragment_open_exit);
+        final Rect bounds = change.getEndAbsBounds();
+        animation.initialize(bounds.width(), bounds.height(),
+                wholeAnimationBounds.width(), wholeAnimationBounds.height());
+        animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+        return animation;
+    }
+
+    @NonNull
+    Animation loadCloseAnimation(@NonNull TransitionInfo.Change change,
+            @NonNull Rect wholeAnimationBounds) {
+        final boolean isEnter = Transitions.isOpeningType(change.getMode());
+        final Animation animation;
+        // TODO(b/207070762):
+        // 1. Implement clearTop version: R.anim.task_fragment_clear_top_close_enter/exit
+        // 2. Implement edgeExtension version
+        animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+                ? R.anim.task_fragment_close_enter
+                : R.anim.task_fragment_close_exit);
+        final Rect bounds = change.getEndAbsBounds();
+        animation.initialize(bounds.width(), bounds.height(),
+                wholeAnimationBounds.width(), wholeAnimationBounds.height());
+        animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+        return animation;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index b305897..e0004fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -18,8 +18,11 @@
 
 import static android.window.TransitionInfo.FLAG_IS_EMBEDDED;
 
+import static java.util.Objects.requireNonNull;
+
 import android.content.Context;
 import android.os.IBinder;
+import android.util.ArrayMap;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
@@ -28,6 +31,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
@@ -37,15 +41,37 @@
 public class ActivityEmbeddingController implements Transitions.TransitionHandler {
 
     private final Context mContext;
-    private final Transitions mTransitions;
+    @VisibleForTesting
+    final Transitions mTransitions;
+    @VisibleForTesting
+    final ActivityEmbeddingAnimationRunner mAnimationRunner;
 
-    public ActivityEmbeddingController(Context context, ShellInit shellInit,
-            Transitions transitions) {
-        mContext = context;
-        mTransitions = transitions;
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            shellInit.addInitCallback(this::onInit, this);
-        }
+    /**
+     * Keeps track of the currently-running transition callback associated with each transition
+     * token.
+     */
+    private final ArrayMap<IBinder, Transitions.TransitionFinishCallback> mTransitionCallbacks =
+            new ArrayMap<>();
+
+    private ActivityEmbeddingController(@NonNull Context context, @NonNull ShellInit shellInit,
+            @NonNull Transitions transitions) {
+        mContext = requireNonNull(context);
+        mTransitions = requireNonNull(transitions);
+        mAnimationRunner = new ActivityEmbeddingAnimationRunner(context, this);
+
+        shellInit.addInitCallback(this::onInit, this);
+    }
+
+    /**
+     * Creates {@link ActivityEmbeddingController}, returns {@code null} if the feature is not
+     * supported.
+     */
+    @Nullable
+    public static ActivityEmbeddingController create(@NonNull Context context,
+            @NonNull ShellInit shellInit, @NonNull Transitions transitions) {
+        return Transitions.ENABLE_SHELL_TRANSITIONS
+                ? new ActivityEmbeddingController(context, shellInit, transitions)
+                : null;
     }
 
     /** Registers to handle transitions. */
@@ -66,9 +92,9 @@
             }
         }
 
-        // TODO(b/207070762) Implement AE animation.
-        startTransaction.apply();
-        finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+        // Start ActivityEmbedding animation.
+        mTransitionCallbacks.put(transition, finishCallback);
+        mAnimationRunner.startAnimation(transition, info, startTransaction, finishTransaction);
         return true;
     }
 
@@ -79,6 +105,21 @@
         return null;
     }
 
+    @Override
+    public void setAnimScaleSetting(float scale) {
+        mAnimationRunner.setAnimScaleSetting(scale);
+    }
+
+    /** Called when the animation is finished. */
+    void onAnimationFinished(@NonNull IBinder transition) {
+        final Transitions.TransitionFinishCallback callback =
+                mTransitionCallbacks.remove(transition);
+        if (callback == null) {
+            throw new IllegalStateException("No finish callback found");
+        }
+        callback.onTransitionFinished(null /* wct */, null /* wctCB */);
+    }
+
     private static boolean isEmbedded(@NonNull TransitionInfo.Change change) {
         return (change.getFlags() & FLAG_IS_EMBEDDED) != 0;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 2c02006..99b8885 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -821,7 +821,7 @@
     /**
      * Description of current bubble state.
      */
-    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+    public void dump(@NonNull PrintWriter pw) {
         pw.print("key: "); pw.println(mKey);
         pw.print("  showInShade:   "); pw.println(showInShade());
         pw.print("  showDot:       "); pw.println(showDot());
@@ -831,7 +831,7 @@
         pw.print("  suppressNotif: "); pw.println(shouldSuppressNotification());
         pw.print("  autoExpand:    "); pw.println(shouldAutoExpand());
         if (mExpandedView != null) {
-            mExpandedView.dump(pw, args);
+            mExpandedView.dump(pw);
         }
     }
 
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 de26b549..dcbb272 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
@@ -72,7 +72,6 @@
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.util.Log;
 import android.util.Pair;
-import android.util.Slog;
 import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewGroup;
@@ -100,6 +99,7 @@
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
@@ -159,6 +159,7 @@
     private final TaskViewTransitions mTaskViewTransitions;
     private final SyncTransactionQueue mSyncQueue;
     private final ShellController mShellController;
+    private final ShellCommandHandler mShellCommandHandler;
 
     // Used to post to main UI thread
     private final ShellExecutor mMainExecutor;
@@ -229,6 +230,7 @@
   
     public BubbleController(Context context,
             ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
             ShellController shellController,
             BubbleData data,
             @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
@@ -252,6 +254,7 @@
             TaskViewTransitions taskViewTransitions,
             SyncTransactionQueue syncQueue) {
         mContext = context;
+        mShellCommandHandler = shellCommandHandler;
         mShellController = shellController;
         mLauncherApps = launcherApps;
         mBarService = statusBarService == null
@@ -431,6 +434,7 @@
         mCurrentProfiles = userProfiles;
 
         mShellController.addConfigurationChangeListener(this);
+        mShellCommandHandler.addDumpCallback(this::dump, this);
     }
 
     @VisibleForTesting
@@ -538,7 +542,6 @@
 
         if (mNotifEntryToExpandOnShadeUnlock != null) {
             expandStackAndSelectBubble(mNotifEntryToExpandOnShadeUnlock);
-            mNotifEntryToExpandOnShadeUnlock = null;
         }
 
         updateStack();
@@ -925,15 +928,6 @@
         return (isSummary && isSuppressedSummary) || isSuppressedBubble;
     }
 
-    private void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback) {
-        if (mBubbleData.isSummarySuppressed(groupKey)) {
-            mBubbleData.removeSuppressedSummary(groupKey);
-            if (callback != null) {
-                callback.accept(mBubbleData.getSummaryKey(groupKey));
-            }
-        }
-    }
-
     /** Promote the provided bubble from the overflow view. */
     public void promoteBubbleFromOverflow(Bubble bubble) {
         mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK);
@@ -1519,14 +1513,15 @@
     /**
      * Description of current bubble state.
      */
-    private void dump(PrintWriter pw, String[] args) {
+    private void dump(PrintWriter pw, String prefix) {
         pw.println("BubbleController state:");
-        mBubbleData.dump(pw, args);
+        mBubbleData.dump(pw);
         pw.println();
         if (mStackView != null) {
-            mStackView.dump(pw, args);
+            mStackView.dump(pw);
         }
         pw.println();
+        mImpl.mCachedState.dump(pw);
     }
 
     /**
@@ -1711,28 +1706,12 @@
         }
 
         @Override
-        public boolean isStackExpanded() {
-            return mCachedState.isStackExpanded();
-        }
-
-        @Override
         @Nullable
         public Bubble getBubbleWithShortcutId(String shortcutId) {
             return mCachedState.getBubbleWithShortcutId(shortcutId);
         }
 
         @Override
-        public void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback,
-                Executor callbackExecutor) {
-            mMainExecutor.execute(() -> {
-                Consumer<String> cb = callback != null
-                        ? (key) -> callbackExecutor.execute(() -> callback.accept(key))
-                        : null;
-                BubbleController.this.removeSuppressedSummaryIfNecessary(groupKey, cb);
-            });
-        }
-
-        @Override
         public void collapseStack() {
             mMainExecutor.execute(() -> {
                 BubbleController.this.collapseStack();
@@ -1761,13 +1740,6 @@
         }
 
         @Override
-        public void openBubbleOverflow() {
-            mMainExecutor.execute(() -> {
-                BubbleController.this.openBubbleOverflow();
-            });
-        }
-
-        @Override
         public boolean handleDismissalInterception(BubbleEntry entry,
                 @Nullable List<BubbleEntry> children, IntConsumer removeCallback,
                 Executor callbackExecutor) {
@@ -1882,18 +1854,6 @@
             mMainExecutor.execute(
                     () -> BubbleController.this.onNotificationPanelExpandedChanged(expanded));
         }
-
-        @Override
-        public void dump(PrintWriter pw, String[] args) {
-            try {
-                mMainExecutor.executeBlocking(() -> {
-                    BubbleController.this.dump(pw, args);
-                    mCachedState.dump(pw);
-                });
-            } catch (InterruptedException e) {
-                Slog.e(TAG, "Failed to dump BubbleController in 2s");
-            }
-        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index fa86c84..c64133f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -1136,7 +1136,7 @@
     /**
      * Description of current bubble data state.
      */
-    public void dump(PrintWriter pw, String[] args) {
+    public void dump(PrintWriter pw) {
         pw.print("selected: ");
         pw.println(mSelectedBubble != null
                 ? mSelectedBubble.getKey()
@@ -1147,13 +1147,13 @@
         pw.print("stack bubble count:    ");
         pw.println(mBubbles.size());
         for (Bubble bubble : mBubbles) {
-            bubble.dump(pw, args);
+            bubble.dump(pw);
         }
 
         pw.print("overflow bubble count:    ");
         pw.println(mOverflowBubbles.size());
         for (Bubble bubble : mOverflowBubbles) {
-            bubble.dump(pw, args);
+            bubble.dump(pw);
         }
 
         pw.print("summaryKeys: ");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 2666a0e..cfbe1b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -1044,7 +1044,7 @@
     /**
      * Description of current expanded view state.
      */
-    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+    public void dump(@NonNull PrintWriter pw) {
         pw.print("BubbleExpandedView");
         pw.print("  taskId:               "); pw.println(mTaskId);
         pw.print("  stackView:            "); pw.println(mStackView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 2d0be06..aeaf6ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -111,6 +111,9 @@
     public static final boolean HOME_GESTURE_ENABLED =
             SystemProperties.getBoolean("persist.wm.debug.bubbles_home_gesture", true);
 
+    public static final boolean ENABLE_FLING_TO_DISMISS_BUBBLE =
+            SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_bubble", true);
+
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES;
 
     /** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */
@@ -299,7 +302,7 @@
     private BubblesNavBarGestureTracker mBubblesNavBarGestureTracker;
 
     /** Description of current animation controller state. */
-    public void dump(PrintWriter pw, String[] args) {
+    public void dump(PrintWriter pw) {
         pw.println("Stack view state:");
 
         String bubblesOnScreen = BubbleDebugConfig.formatBubblesString(
@@ -313,8 +316,8 @@
         pw.print("  expandedContainerMatrix: ");
         pw.println(mExpandedViewContainer.getAnimationMatrix());
 
-        mStackAnimationController.dump(pw, args);
-        mExpandedAnimationController.dump(pw, args);
+        mStackAnimationController.dump(pw);
+        mExpandedAnimationController.dump(pw);
 
         if (mExpandedBubble != null) {
             pw.println("Expanded bubble state:");
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 37b96ff..0e97e9e 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
@@ -35,7 +35,6 @@
 
 import com.android.wm.shell.common.annotations.ExternalThread;
 
-import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 import java.util.HashMap;
@@ -91,18 +90,6 @@
      */
     boolean isBubbleExpanded(String key);
 
-    /** @return {@code true} if stack of bubbles is expanded or not. */
-    boolean isStackExpanded();
-
-    /**
-     * Removes a group key indicating that the summary for this group should no longer be
-     * suppressed.
-     *
-     * @param callback If removed, this callback will be called with the summary key of the group
-     */
-    void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback,
-            Executor callbackExecutor);
-
     /** Tell the stack of bubbles to collapse. */
     void collapseStack();
 
@@ -130,9 +117,6 @@
     /** Called for any taskbar changes. */
     void onTaskbarChanged(Bundle b);
 
-    /** Open the overflow view. */
-    void openBubbleOverflow();
-
     /**
      * We intercept notification entries (including group summaries) dismissed by the user when
      * there is an active bubble associated with it. We do this so that developers can still
@@ -252,9 +236,6 @@
      */
     void onUserRemoved(int removedUserId);
 
-    /** Description of current bubble state. */
-    void dump(PrintWriter pw, String[] args);
-
     /** Listener to find out about stack expansion / collapse events. */
     interface BubbleExpandListener {
         /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index b521cb6a..b91062f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -19,6 +19,7 @@
 import static android.view.View.LAYOUT_DIRECTION_RTL;
 
 import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.bubbles.BubbleStackView.ENABLE_FLING_TO_DISMISS_BUBBLE;
 import static com.android.wm.shell.bubbles.BubbleStackView.HOME_GESTURE_ENABLED;
 
 import android.content.res.Resources;
@@ -366,6 +367,7 @@
         mMagnetizedBubbleDraggingOut.setMagnetListener(listener);
         mMagnetizedBubbleDraggingOut.setHapticsEnabled(true);
         mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+        mMagnetizedBubbleDraggingOut.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_BUBBLE);
     }
 
     private void springBubbleTo(View bubble, float x, float y) {
@@ -468,7 +470,7 @@
     }
 
     /** Description of current animation controller state. */
-    public void dump(PrintWriter pw, String[] args) {
+    public void dump(PrintWriter pw) {
         pw.println("ExpandedAnimationController state:");
         pw.print("  isActive:          "); pw.println(isActiveController());
         pw.print("  animatingExpand:   "); pw.println(mAnimatingExpand);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 0a1b4d7..961722b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.bubbles.animation;
 
 import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.bubbles.BubbleStackView.ENABLE_FLING_TO_DISMISS_BUBBLE;
 
 import android.content.ContentResolver;
 import android.content.res.Resources;
@@ -431,7 +432,7 @@
     }
 
     /** Description of current animation controller state. */
-    public void dump(PrintWriter pw, String[] args) {
+    public void dump(PrintWriter pw) {
         pw.println("StackAnimationController state:");
         pw.print("  isActive:             "); pw.println(isActiveController());
         pw.print("  restingStackPos:      ");
@@ -1028,6 +1029,7 @@
             };
             mMagnetizedStack.setHapticsEnabled(true);
             mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+            mMagnetizedStack.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_BUBBLE);
         }
 
         final ContentResolver contentResolver = mLayout.getContext().getContentResolver();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 85c8ebf..83ba909 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -80,7 +80,10 @@
     public static final int PARALLAX_DISMISSING = 1;
     public static final int PARALLAX_ALIGN_CENTER = 2;
 
-    private static final int FLING_ANIMATION_DURATION = 250;
+    private static final int FLING_RESIZE_DURATION = 250;
+    private static final int FLING_SWITCH_DURATION = 350;
+    private static final int FLING_ENTER_DURATION = 350;
+    private static final int FLING_EXIT_DURATION = 350;
 
     private final int mDividerWindowWidth;
     private final int mDividerInsets;
@@ -93,6 +96,9 @@
     private final Rect mBounds1 = new Rect();
     // Bounds2 final position should be always at bottom or right
     private final Rect mBounds2 = new Rect();
+    // The temp bounds outside of display bounds for side stage when split screen inactive to avoid
+    // flicker next time active split screen.
+    private final Rect mInvisibleBounds = new Rect();
     private final Rect mWinBounds1 = new Rect();
     private final Rect mWinBounds2 = new Rect();
     private final SplitLayoutHandler mSplitLayoutHandler;
@@ -141,6 +147,10 @@
         resetDividerPosition();
 
         mDimNonImeSide = resources.getBoolean(R.bool.config_dimNonImeAttachedSide);
+
+        mInvisibleBounds.set(mRootBounds);
+        mInvisibleBounds.offset(isLandscape() ? mRootBounds.right : 0,
+                isLandscape() ? 0 : mRootBounds.bottom);
     }
 
     private int getDividerInsets(Resources resources, Display display) {
@@ -239,6 +249,12 @@
         rect.offset(-mRootBounds.left, -mRootBounds.top);
     }
 
+    /** Gets bounds size equal to root bounds but outside of screen, used for position side stage
+     * when split inactive to avoid flicker when next time active. */
+    public void getInvisibleBounds(Rect rect) {
+        rect.set(mInvisibleBounds);
+    }
+
     /** Returns leash of the current divider bar. */
     @Nullable
     public SurfaceControl getDividerLeash() {
@@ -284,6 +300,10 @@
         mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
         initDividerPosition(mTempRect);
 
+        mInvisibleBounds.set(mRootBounds);
+        mInvisibleBounds.offset(isLandscape() ? mRootBounds.right : 0,
+                isLandscape() ? 0 : mRootBounds.bottom);
+
         return true;
     }
 
@@ -405,6 +425,13 @@
         mFreezeDividerWindow = freezeDividerWindow;
     }
 
+    /** Update current layout as divider put on start or end position. */
+    public void setDividerAtBorder(boolean start) {
+        final int pos = start ? mDividerSnapAlgorithm.getDismissStartTarget().position
+                : mDividerSnapAlgorithm.getDismissEndTarget().position;
+        setDividePosition(pos, false /* applyLayoutChange */);
+    }
+
     /**
      * Updates bounds with the passing position. Usually used to update recording bounds while
      * performing animation or dragging divider bar to resize the splits.
@@ -449,17 +476,17 @@
     public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) {
         switch (snapTarget.flag) {
             case FLAG_DISMISS_START:
-                flingDividePosition(currentPosition, snapTarget.position,
+                flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
                         () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */,
                                 EXIT_REASON_DRAG_DIVIDER));
                 break;
             case FLAG_DISMISS_END:
-                flingDividePosition(currentPosition, snapTarget.position,
+                flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
                         () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */,
                                 EXIT_REASON_DRAG_DIVIDER));
                 break;
             default:
-                flingDividePosition(currentPosition, snapTarget.position,
+                flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
                         () -> setDividePosition(snapTarget.position, true /* applyLayoutChange */));
                 break;
         }
@@ -516,12 +543,20 @@
     public void flingDividerToDismiss(boolean toEnd, int reason) {
         final int target = toEnd ? mDividerSnapAlgorithm.getDismissEndTarget().position
                 : mDividerSnapAlgorithm.getDismissStartTarget().position;
-        flingDividePosition(getDividePosition(), target,
+        flingDividePosition(getDividePosition(), target, FLING_EXIT_DURATION,
                 () -> mSplitLayoutHandler.onSnappedToDismiss(toEnd, reason));
     }
 
+    /** Fling divider from current position to center position. */
+    public void flingDividerToCenter() {
+        final int pos = mDividerSnapAlgorithm.getMiddleTarget().position;
+        flingDividePosition(getDividePosition(), pos, FLING_ENTER_DURATION,
+                () -> setDividePosition(pos, true /* applyLayoutChange */));
+    }
+
     @VisibleForTesting
-    void flingDividePosition(int from, int to, @Nullable Runnable flingFinishedCallback) {
+    void flingDividePosition(int from, int to, int duration,
+            @Nullable Runnable flingFinishedCallback) {
         if (from == to) {
             // No animation run, still callback to stop resizing.
             mSplitLayoutHandler.onLayoutSizeChanged(this);
@@ -531,7 +566,7 @@
         }
         ValueAnimator animator = ValueAnimator
                 .ofInt(from, to)
-                .setDuration(FLING_ANIMATION_DURATION);
+                .setDuration(duration);
         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
         animator.addUpdateListener(
                 animation -> updateDivideBounds((int) animation.getAnimatedValue()));
@@ -588,7 +623,7 @@
 
         AnimatorSet set = new AnimatorSet();
         set.playTogether(animator1, animator2, animator3);
-        set.setDuration(FLING_ANIMATION_DURATION);
+        set.setDuration(FLING_SWITCH_DURATION);
         set.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index e22c951..8022e9b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -66,6 +66,7 @@
     @Provides
     static Optional<Pip> providePip(
             Context context,
+            ShellInit shellInit,
             ShellController shellController,
             TvPipBoundsState tvPipBoundsState,
             TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
@@ -84,6 +85,7 @@
         return Optional.of(
                 TvPipController.create(
                         context,
+                        shellInit,
                         shellController,
                         tvPipBoundsState,
                         tvPipBoundsAlgorithm,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index a6a04cf..7a736cc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -85,6 +85,7 @@
 import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
 import com.android.wm.shell.unfold.UnfoldAnimationController;
 import com.android.wm.shell.unfold.UnfoldTransitionHandler;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import java.util.Optional;
 
@@ -294,25 +295,33 @@
     // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
     @BindsOptionalOf
     @DynamicOverride
-    abstract FullscreenTaskListener optionalFullscreenTaskListener();
+    abstract FullscreenTaskListener<?> optionalFullscreenTaskListener();
 
     @WMSingleton
     @Provides
-    static FullscreenTaskListener provideFullscreenTaskListener(
-            @DynamicOverride Optional<FullscreenTaskListener> fullscreenTaskListener,
+    static FullscreenTaskListener<?> provideFullscreenTaskListener(
+            @DynamicOverride Optional<FullscreenTaskListener<?>> fullscreenTaskListener,
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
             SyncTransactionQueue syncQueue,
-            Optional<RecentTasksController> recentTasksOptional) {
+            Optional<RecentTasksController> recentTasksOptional,
+            Optional<WindowDecorViewModel<?>> windowDecorViewModelOptional) {
         if (fullscreenTaskListener.isPresent()) {
             return fullscreenTaskListener.get();
         } else {
             return new FullscreenTaskListener(shellInit, shellTaskOrganizer, syncQueue,
-                    recentTasksOptional);
+                    recentTasksOptional, windowDecorViewModelOptional);
         }
     }
 
     //
+    // Window Decoration
+    //
+
+    @BindsOptionalOf
+    abstract WindowDecorViewModel<?> optionalWindowDecorViewModel();
+
+    //
     // Unfold transition
     //
 
@@ -627,11 +636,12 @@
 
     @WMSingleton
     @Provides
-    static ActivityEmbeddingController provideActivityEmbeddingController(
+    static Optional<ActivityEmbeddingController> provideActivityEmbeddingController(
             Context context,
             ShellInit shellInit,
             Transitions transitions) {
-        return new ActivityEmbeddingController(context, shellInit, transitions);
+        return Optional.ofNullable(
+                ActivityEmbeddingController.create(context, shellInit, transitions));
     }
 
     //
@@ -679,14 +689,14 @@
             Optional<SplitScreenController> splitScreenOptional,
             Optional<Pip> pipOptional,
             Optional<PipTouchHandler> pipTouchHandlerOptional,
-            FullscreenTaskListener fullscreenTaskListener,
+            FullscreenTaskListener<?> fullscreenTaskListener,
             Optional<UnfoldAnimationController> unfoldAnimationController,
             Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
             Optional<FreeformComponents> freeformComponents,
             Optional<RecentTasksController> recentTasksOptional,
             Optional<OneHandedController> oneHandedControllerOptional,
             Optional<HideDisplayCutoutController> hideDisplayCutoutControllerOptional,
-            ActivityEmbeddingController activityEmbeddingOptional,
+            Optional<ActivityEmbeddingController> activityEmbeddingOptional,
             Transitions transitions,
             StartingWindowController startingWindow,
             @ShellCreateTriggerOverride Optional<Object> overriddenCreateTrigger) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 2bcc134..c64d1134 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -55,6 +55,7 @@
 import com.android.wm.shell.freeform.FreeformComponents;
 import com.android.wm.shell.freeform.FreeformTaskListener;
 import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
+import com.android.wm.shell.fullscreen.FullscreenTaskListener;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
@@ -145,6 +146,7 @@
     @Provides
     static BubbleController provideBubbleController(Context context,
             ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
             ShellController shellController,
             BubbleData data,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -165,7 +167,7 @@
             @ShellBackgroundThread ShellExecutor bgExecutor,
             TaskViewTransitions taskViewTransitions,
             SyncTransactionQueue syncQueue) {
-        return new BubbleController(context, shellInit, shellController, data,
+        return new BubbleController(context, shellInit, shellCommandHandler, shellController, data,
                 null /* synchronizer */, floatingContentCoordinator,
                 new BubbleDataRepository(context, launcherApps, mainExecutor),
                 statusBarService, windowManager, windowManagerShellWrapper, userManager,
@@ -232,6 +234,7 @@
             ShellInit shellInit,
             Transitions transitions,
             WindowDecorViewModel<?> windowDecorViewModel,
+            FullscreenTaskListener<?> fullscreenTaskListener,
             FreeformTaskListener<?> freeformTaskListener) {
         // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
         //                    override for this controller from the base module
@@ -239,7 +242,7 @@
                 ? shellInit
                 : null;
         return new FreeformTaskTransitionHandler(init, transitions,
-                windowDecorViewModel, freeformTaskListener);
+                windowDecorViewModel, fullscreenTaskListener, freeformTaskListener);
     }
 
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index ab66107..8dcdda1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -187,12 +187,14 @@
             RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
-        T windowDecor = mWindowDecorOfVanishedTasks.get(taskInfo.taskId);
-        mWindowDecorOfVanishedTasks.remove(taskInfo.taskId);
+        T windowDecor;
         final State<T> state = mTasks.get(taskInfo.taskId);
         if (state != null) {
-            windowDecor = windowDecor == null ? state.mWindowDecoration : windowDecor;
+            windowDecor = state.mWindowDecoration;
             state.mWindowDecoration = null;
+        } else {
+            windowDecor =
+                    mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId);
         }
         mWindowDecorationViewModel.setupWindowDecorationForTransition(
                 taskInfo, startT, finishT, windowDecor);
@@ -231,7 +233,8 @@
         if (mWindowDecorOfVanishedTasks.size() == 0) {
             return;
         }
-        Log.w(TAG, "Clearing window decors of vanished tasks. There could be visual defects "
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                "Clearing window decors of vanished tasks. There could be visual defects "
                 + "if any of them is used later in transitions.");
         for (int i = 0; i < mWindowDecorOfVanishedTasks.size(); ++i) {
             releaseWindowDecor(mWindowDecorOfVanishedTasks.valueAt(i));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index a1e9f93..30f625e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -32,6 +32,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.wm.shell.fullscreen.FullscreenTaskListener;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -50,6 +51,7 @@
 
     private final Transitions mTransitions;
     private final FreeformTaskListener<?> mFreeformTaskListener;
+    private final FullscreenTaskListener<?> mFullscreenTaskListener;
     private final WindowDecorViewModel<?> mWindowDecorViewModel;
 
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
@@ -58,8 +60,10 @@
             ShellInit shellInit,
             Transitions transitions,
             WindowDecorViewModel<?> windowDecorViewModel,
+            FullscreenTaskListener<?> fullscreenTaskListener,
             FreeformTaskListener<?> freeformTaskListener) {
         mTransitions = transitions;
+        mFullscreenTaskListener = fullscreenTaskListener;
         mFreeformTaskListener = freeformTaskListener;
         mWindowDecorViewModel = windowDecorViewModel;
         if (shellInit != null && Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -150,10 +154,16 @@
             TransitionInfo.Change change,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
-        if (change.getTaskInfo().getWindowingMode() != WINDOWING_MODE_FREEFORM) {
-            return false;
+        switch (change.getTaskInfo().getWindowingMode()){
+            case WINDOWING_MODE_FREEFORM:
+                mFreeformTaskListener.createWindowDecoration(change, startT, finishT);
+                break;
+            case WINDOWING_MODE_FULLSCREEN:
+                mFullscreenTaskListener.createWindowDecoration(change, startT, finishT);
+                break;
+            default:
+                return false;
         }
-        mFreeformTaskListener.createWindowDecoration(change, startT, finishT);
 
         // Intercepted transition to manage the window decorations. Let other handlers animate.
         return false;
@@ -164,15 +174,22 @@
             ArrayList<AutoCloseable> windowDecors,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
-        if (change.getTaskInfo().getWindowingMode() != WINDOWING_MODE_FREEFORM) {
-            return false;
+        final AutoCloseable windowDecor;
+        switch (change.getTaskInfo().getWindowingMode()) {
+            case WINDOWING_MODE_FREEFORM:
+                windowDecor = mFreeformTaskListener.giveWindowDecoration(change.getTaskInfo(),
+                        startT, finishT);
+                break;
+            case WINDOWING_MODE_FULLSCREEN:
+                windowDecor = mFullscreenTaskListener.giveWindowDecoration(change.getTaskInfo(),
+                        startT, finishT);
+                break;
+            default:
+                windowDecor = null;
         }
-        final AutoCloseable windowDecor =
-                mFreeformTaskListener.giveWindowDecoration(change.getTaskInfo(), startT, finishT);
         if (windowDecor != null) {
             windowDecors.add(windowDecor);
         }
-
         // Intercepted transition to manage the window decorations. Let other handlers animate.
         return false;
     }
@@ -197,24 +214,29 @@
         }
 
         boolean handled = false;
+        boolean adopted = false;
         final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
         if (type == Transitions.TRANSIT_MAXIMIZE
                 && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
             handled = true;
             windowDecor = mFreeformTaskListener.giveWindowDecoration(
                     change.getTaskInfo(), startT, finishT);
-            // TODO(b/235638450): Let fullscreen task listener adopt the window decor.
+            adopted = mFullscreenTaskListener.adoptWindowDecoration(change,
+                    startT, finishT, windowDecor);
         }
 
         if (type == Transitions.TRANSIT_RESTORE_FROM_MAXIMIZE
                 && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
             handled = true;
-            // TODO(b/235638450): Let fullscreen task listener transfer the window decor.
-            mFreeformTaskListener.adoptWindowDecoration(change, startT, finishT, windowDecor);
+            windowDecor = mFullscreenTaskListener.giveWindowDecoration(
+                    change.getTaskInfo(), startT, finishT);
+            adopted = mFreeformTaskListener.adoptWindowDecoration(change,
+                    startT, finishT, windowDecor);
         }
 
-        releaseWindowDecor(windowDecor);
-
+        if (!adopted) {
+            releaseWindowDecor(windowDecor);
+        }
         return handled;
     }
 
@@ -225,7 +247,7 @@
             releaseWindowDecor(windowDecor);
         }
         mFreeformTaskListener.onTaskTransitionFinished();
-        // TODO(b/235638450): Dispatch it to fullscreen task listener.
+        mFullscreenTaskListener.onTaskTransitionFinished();
         finishCallback.onTransitionFinished(null, null);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 0ba4afc..0d75bc4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -16,16 +16,21 @@
 
 package com.android.wm.shell.fullscreen;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
 import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
 
+import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.graphics.Point;
-import android.util.Slog;
+import android.util.Log;
 import android.util.SparseArray;
 import android.view.SurfaceControl;
+import android.window.TransitionInfo;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -34,36 +39,49 @@
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import java.io.PrintWriter;
 import java.util.Optional;
 
 /**
   * Organizes tasks presented in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN}.
+ * @param <T> the type of window decoration instance
   */
-public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
+public class FullscreenTaskListener<T extends AutoCloseable>
+        implements ShellTaskOrganizer.TaskListener {
     private static final String TAG = "FullscreenTaskListener";
 
     private final ShellTaskOrganizer mShellTaskOrganizer;
+
+    private final SparseArray<State<T>> mTasks = new SparseArray<>();
+    private final SparseArray<T> mWindowDecorOfVanishedTasks = new SparseArray<>();
+
+    private static class State<T extends AutoCloseable> {
+        RunningTaskInfo mTaskInfo;
+        SurfaceControl mLeash;
+        T mWindowDecoration;
+    }
     private final SyncTransactionQueue mSyncQueue;
     private final Optional<RecentTasksController> mRecentTasksOptional;
-
-    private final SparseArray<TaskData> mDataByTaskId = new SparseArray<>();
-
+    private final Optional<WindowDecorViewModel<T>> mWindowDecorViewModelOptional;
     /**
      * This constructor is used by downstream products.
      */
     public FullscreenTaskListener(SyncTransactionQueue syncQueue) {
-        this(null /* shellInit */, null /* shellTaskOrganizer */, syncQueue, Optional.empty());
+        this(null /* shellInit */, null /* shellTaskOrganizer */, syncQueue, Optional.empty(),
+                Optional.empty());
     }
 
     public FullscreenTaskListener(ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
             SyncTransactionQueue syncQueue,
-            Optional<RecentTasksController> recentTasksOptional) {
+            Optional<RecentTasksController> recentTasksOptional,
+            Optional<WindowDecorViewModel<T>> windowDecorViewModelOptional) {
         mShellTaskOrganizer = shellTaskOrganizer;
         mSyncQueue = syncQueue;
         mRecentTasksOptional = recentTasksOptional;
+        mWindowDecorViewModelOptional = windowDecorViewModelOptional;
         // Note: Some derivative FullscreenTaskListener implementations do not use ShellInit
         if (shellInit != null) {
             shellInit.addInitCallback(this::onInit, this);
@@ -76,55 +94,204 @@
 
     @Override
     public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
-        if (mDataByTaskId.get(taskInfo.taskId) != null) {
+        if (mTasks.get(taskInfo.taskId) != null) {
             throw new IllegalStateException("Task appeared more than once: #" + taskInfo.taskId);
         }
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d",
                 taskInfo.taskId);
         final Point positionInParent = taskInfo.positionInParent;
-        mDataByTaskId.put(taskInfo.taskId, new TaskData(leash, positionInParent));
-
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
-        mSyncQueue.runInSync(t -> {
-            // Reset several properties back to fullscreen (PiP, for example, leaves all these
-            // properties in a bad state).
-            t.setWindowCrop(leash, null);
-            t.setPosition(leash, positionInParent.x, positionInParent.y);
-            t.setAlpha(leash, 1f);
-            t.setMatrix(leash, 1, 0, 0, 1);
-            t.show(leash);
-        });
+        final State<T> state = new State();
+        state.mLeash = leash;
+        state.mTaskInfo = taskInfo;
+        mTasks.put(taskInfo.taskId, state);
 
         updateRecentsForVisibleFullscreenTask(taskInfo);
-    }
-
-    @Override
-    public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
         if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
-
-        updateRecentsForVisibleFullscreenTask(taskInfo);
-
-        final TaskData data = mDataByTaskId.get(taskInfo.taskId);
-        final Point positionInParent = taskInfo.positionInParent;
-        if (!positionInParent.equals(data.positionInParent)) {
-            data.positionInParent.set(positionInParent.x, positionInParent.y);
+        if (shouldShowWindowDecor(taskInfo) && mWindowDecorViewModelOptional.isPresent()) {
+            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            state.mWindowDecoration =
+                    mWindowDecorViewModelOptional.get().createWindowDecoration(taskInfo,
+                            leash, t, t);
+            t.apply();
+        } else {
             mSyncQueue.runInSync(t -> {
-                t.setPosition(data.surface, positionInParent.x, positionInParent.y);
+                // Reset several properties back to fullscreen (PiP, for example, leaves all these
+                // properties in a bad state).
+                t.setWindowCrop(leash, null);
+                t.setPosition(leash, positionInParent.x, positionInParent.y);
+                t.setAlpha(leash, 1f);
+                t.setMatrix(leash, 1, 0, 0, 1);
+                t.show(leash);
             });
         }
     }
 
     @Override
-    public void onTaskVanished(RunningTaskInfo taskInfo) {
-        if (mDataByTaskId.get(taskInfo.taskId) == null) {
-            Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
+    public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+        final State<T> state = mTasks.get(taskInfo.taskId);
+        final Point oldPositionInParent = state.mTaskInfo.positionInParent;
+        state.mTaskInfo = taskInfo;
+        if (state.mWindowDecoration != null) {
+            mWindowDecorViewModelOptional.get().onTaskInfoChanged(
+                    state.mTaskInfo, state.mWindowDecoration);
+        }
+        updateRecentsForVisibleFullscreenTask(taskInfo);
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
+
+        final Point positionInParent = state.mTaskInfo.positionInParent;
+        if (!oldPositionInParent.equals(state.mTaskInfo.positionInParent)) {
+            mSyncQueue.runInSync(t -> {
+                t.setPosition(state.mLeash, positionInParent.x, positionInParent.y);
+            });
+        }
+    }
+
+    @Override
+    public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+        final State<T> state = mTasks.get(taskInfo.taskId);
+        if (state == null) {
+            // This is possible if the transition happens before this method.
+            return;
+        }
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d",
+                taskInfo.taskId);
+        mTasks.remove(taskInfo.taskId);
+
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            // Save window decorations of closing tasks so that we can hand them over to the
+            // transition system if this method happens before the transition. In case where the
+            // transition didn't happen, it'd be cleared when the next transition finished.
+            if (state.mWindowDecoration != null) {
+                mWindowDecorOfVanishedTasks.put(taskInfo.taskId, state.mWindowDecoration);
+            }
+            return;
+        }
+        releaseWindowDecor(state.mWindowDecoration);
+    }
+
+    /**
+     * Creates a window decoration for a transition.
+     *
+     * @param change the change of this task transition that needs to have the task layer as the
+     *               leash
+     */
+    public void createWindowDecoration(TransitionInfo.Change change,
+            SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
+        final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
+        if (!mWindowDecorViewModelOptional.isPresent()
+                || !shouldShowWindowDecor(state.mTaskInfo)) {
             return;
         }
 
-        mDataByTaskId.remove(taskInfo.taskId);
+        state.mWindowDecoration = mWindowDecorViewModelOptional.get().createWindowDecoration(
+                state.mTaskInfo, state.mLeash, startT, finishT);
+    }
 
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d",
-                taskInfo.taskId);
+    /**
+     * Adopt the incoming window decoration and lets the window decoration prepare for a transition.
+     *
+     * @param change the change of this task transition that needs to have the task layer as the
+     *               leash
+     * @param startT the start transaction of this transition
+     * @param finishT the finish transaction of this transition
+     * @param windowDecor the window decoration to adopt
+     * @return {@code true} if it adopts the window decoration; {@code false} otherwise
+     */
+    public boolean adoptWindowDecoration(
+            TransitionInfo.Change change,
+            SurfaceControl.Transaction startT,
+            SurfaceControl.Transaction finishT,
+            @Nullable AutoCloseable windowDecor) {
+        if (!mWindowDecorViewModelOptional.isPresent()
+                || !shouldShowWindowDecor(change.getTaskInfo())) {
+            return false;
+        }
+        final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
+        state.mWindowDecoration = mWindowDecorViewModelOptional.get().adoptWindowDecoration(
+                windowDecor);
+        if (state.mWindowDecoration != null) {
+            mWindowDecorViewModelOptional.get().setupWindowDecorationForTransition(
+                    state.mTaskInfo, startT, finishT, state.mWindowDecoration);
+            return true;
+        } else {
+            state.mWindowDecoration = mWindowDecorViewModelOptional.get().createWindowDecoration(
+                    state.mTaskInfo, state.mLeash, startT, finishT);
+            return false;
+        }
+    }
+
+    /**
+     * Clear window decors of vanished tasks.
+     */
+    public void onTaskTransitionFinished() {
+        if (mWindowDecorOfVanishedTasks.size() == 0) {
+            return;
+        }
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                "Clearing window decors of vanished tasks. There could be visual defects "
+                + "if any of them is used later in transitions.");
+        for (int i = 0; i < mWindowDecorOfVanishedTasks.size(); ++i) {
+            releaseWindowDecor(mWindowDecorOfVanishedTasks.valueAt(i));
+        }
+        mWindowDecorOfVanishedTasks.clear();
+    }
+
+    /**
+     * Gives out the ownership of the task's window decoration. The given task is leaving (of has
+     * left) this task listener. This is the transition system asking for the ownership.
+     *
+     * @param taskInfo the maximizing task
+     * @return the window decor of the maximizing task if any
+     */
+    public T giveWindowDecoration(
+            ActivityManager.RunningTaskInfo taskInfo,
+            SurfaceControl.Transaction startT,
+            SurfaceControl.Transaction finishT) {
+        T windowDecor;
+        final State<T> state = mTasks.get(taskInfo.taskId);
+        if (state != null) {
+            windowDecor = state.mWindowDecoration;
+            state.mWindowDecoration = null;
+        } else {
+            windowDecor =
+                    mWindowDecorOfVanishedTasks.removeReturnOld(taskInfo.taskId);
+        }
+        if (mWindowDecorViewModelOptional.isPresent()) {
+            mWindowDecorViewModelOptional.get().setupWindowDecorationForTransition(
+                    taskInfo, startT, finishT, windowDecor);
+        }
+
+        return windowDecor;
+    }
+
+    private State<T> createOrUpdateTaskState(ActivityManager.RunningTaskInfo taskInfo,
+            SurfaceControl leash) {
+        State<T> state = mTasks.get(taskInfo.taskId);
+        if (state != null) {
+            updateTaskInfo(taskInfo);
+            return state;
+        }
+
+        state = new State<T>();
+        state.mTaskInfo = taskInfo;
+        state.mLeash = leash;
+        mTasks.put(taskInfo.taskId, state);
+
+        return state;
+    }
+
+    private State<T> updateTaskInfo(ActivityManager.RunningTaskInfo taskInfo) {
+        final State<T> state = mTasks.get(taskInfo.taskId);
+        state.mTaskInfo = taskInfo;
+        return state;
+    }
+
+    private void releaseWindowDecor(T windowDecor) {
+        try {
+            windowDecor.close();
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to release window decoration.", e);
+        }
     }
 
     private void updateRecentsForVisibleFullscreenTask(RunningTaskInfo taskInfo) {
@@ -148,17 +315,17 @@
     }
 
     private SurfaceControl findTaskSurface(int taskId) {
-        if (!mDataByTaskId.contains(taskId)) {
+        if (!mTasks.contains(taskId)) {
             throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
         }
-        return mDataByTaskId.get(taskId).surface;
+        return mTasks.get(taskId).mLeash;
     }
 
     @Override
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + this);
-        pw.println(innerPrefix + mDataByTaskId.size() + " Tasks");
+        pw.println(innerPrefix + mTasks.size() + " Tasks");
     }
 
     @Override
@@ -166,16 +333,10 @@
         return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN);
     }
 
-    /**
-     * Per-task data for each managed task.
-     */
-    private static class TaskData {
-        public final SurfaceControl surface;
-        public final Point positionInParent;
-
-        public TaskData(SurfaceControl surface, Point positionInParent) {
-            this.surface = surface;
-            this.positionInParent = positionInParent;
-        }
+    private static boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
+        return taskInfo.getConfiguration().windowConfiguration.getDisplayWindowingMode()
+                == WINDOWING_MODE_FREEFORM;
     }
+
+
 }
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 76c0f41..7129165 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
@@ -37,16 +37,6 @@
     }
 
     /**
-     * Return one handed settings enabled or not.
-     */
-    boolean isOneHandedEnabled();
-
-    /**
-     * Return swipe to notification settings enabled or not.
-     */
-    boolean isSwipeToNotificationEnabled();
-
-    /**
      * Enters one handed mode.
      */
     void startOneHanded();
@@ -80,9 +70,4 @@
      * transition start or finish
      */
     void registerTransitionCallback(OneHandedTransitionCallback callback);
-
-    /**
-     * Notifies when user switch complete
-     */
-    void onUserSwitch(int userId);
 }
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 9149204..e0c4fe8 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
@@ -59,6 +59,7 @@
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.UserChangeListener;
 
 import java.io.PrintWriter;
 
@@ -67,7 +68,7 @@
  */
 public class OneHandedController implements RemoteCallable<OneHandedController>,
         DisplayChangeController.OnDisplayChangingListener, ConfigurationChangeListener,
-        KeyguardChangeListener {
+        KeyguardChangeListener, UserChangeListener {
     private static final String TAG = "OneHandedController";
 
     private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
@@ -76,8 +77,8 @@
 
     public static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
 
-    private volatile boolean mIsOneHandedEnabled;
-    private volatile boolean mIsSwipeToNotificationEnabled;
+    private boolean mIsOneHandedEnabled;
+    private boolean mIsSwipeToNotificationEnabled;
     private boolean mIsShortcutEnabled;
     private boolean mTaskChangeToExit;
     private boolean mLockedDisabled;
@@ -294,6 +295,7 @@
         mState.addSListeners(mTutorialHandler);
         mShellController.addConfigurationChangeListener(this);
         mShellController.addKeyguardChangeListener(this);
+        mShellController.addUserChangeListener(this);
     }
 
     public OneHanded asOneHanded() {
@@ -627,7 +629,8 @@
         stopOneHanded();
     }
 
-    private void onUserSwitch(int newUserId) {
+    @Override
+    public void onUserChanged(int newUserId, @NonNull Context userContext) {
         unregisterSettingObservers();
         mUserId = newUserId;
         registerSettingObservers(newUserId);
@@ -718,18 +721,6 @@
         }
 
         @Override
-        public boolean isOneHandedEnabled() {
-            // This is volatile so return directly
-            return mIsOneHandedEnabled;
-        }
-
-        @Override
-        public boolean isSwipeToNotificationEnabled() {
-            // This is volatile so return directly
-            return mIsSwipeToNotificationEnabled;
-        }
-
-        @Override
         public void startOneHanded() {
             mMainExecutor.execute(() -> {
                 OneHandedController.this.startOneHanded();
@@ -770,13 +761,6 @@
                 OneHandedController.this.registerTransitionCallback(callback);
             });
         }
-
-        @Override
-        public void onUserSwitch(int userId) {
-            mMainExecutor.execute(() -> {
-                OneHandedController.this.onUserSwitch(userId);
-            });
-        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 93172f8..c06881a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -51,12 +51,6 @@
     }
 
     /**
-     * Registers the session listener for the current user.
-     */
-    default void registerSessionListenerForCurrentUser() {
-    }
-
-    /**
      * Sets both shelf visibility and its height.
      *
      * @param visible visibility of shelf.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index fc97f31..ac3407d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -92,6 +92,7 @@
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.UserChangeListener;
 import com.android.wm.shell.transition.Transitions;
 
 import java.io.PrintWriter;
@@ -105,7 +106,8 @@
  * Manages the picture-in-picture (PIP) UI and states for Phones.
  */
 public class PipController implements PipTransitionController.PipTransitionCallback,
-        RemoteCallable<PipController>, ConfigurationChangeListener, KeyguardChangeListener {
+        RemoteCallable<PipController>, ConfigurationChangeListener, KeyguardChangeListener,
+        UserChangeListener {
     private static final String TAG = "PipController";
 
     private Context mContext;
@@ -528,7 +530,7 @@
                 });
 
         mOneHandedController.ifPresent(controller -> {
-            controller.asOneHanded().registerTransitionCallback(
+            controller.registerTransitionCallback(
                     new OneHandedTransitionCallback() {
                         @Override
                         public void onStartFinished(Rect bounds) {
@@ -542,8 +544,11 @@
                     });
         });
 
+        mMediaController.registerSessionListenerForCurrentUser();
+
         mShellController.addConfigurationChangeListener(this);
         mShellController.addKeyguardChangeListener(this);
+        mShellController.addUserChangeListener(this);
     }
 
     @Override
@@ -557,6 +562,12 @@
     }
 
     @Override
+    public void onUserChanged(int newUserId, @NonNull Context userContext) {
+        // Re-register the media session listener when switching users
+        mMediaController.registerSessionListenerForCurrentUser();
+    }
+
+    @Override
     public void onConfigurationChanged(Configuration newConfig) {
         mPipBoundsAlgorithm.onConfigurationChanged(mContext);
         mTouchHandler.onConfigurationChanged();
@@ -644,10 +655,6 @@
         }
     }
 
-    private void registerSessionListenerForCurrentUser() {
-        mMediaController.registerSessionListenerForCurrentUser();
-    }
-
     private void onSystemUiStateChanged(boolean isValidState, int flag) {
         mTouchHandler.onSystemUiStateChanged(isValidState);
     }
@@ -968,13 +975,6 @@
         }
 
         @Override
-        public void registerSessionListenerForCurrentUser() {
-            mMainExecutor.execute(() -> {
-                PipController.this.registerSessionListenerForCurrentUser();
-            });
-        }
-
-        @Override
         public void setShelfHeight(boolean visible, int height) {
             mMainExecutor.execute(() -> {
                 PipController.this.setShelfHeight(visible, height);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 44d2202..afb64c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -33,6 +33,7 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Debug;
+import android.os.SystemProperties;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
@@ -58,6 +59,8 @@
 public class PipMotionHelper implements PipAppOpsListener.Callback,
         FloatingContentCoordinator.FloatingContent {
 
+    public static final boolean ENABLE_FLING_TO_DISMISS_PIP =
+            SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_pip", true);
     private static final String TAG = "PipMotionHelper";
     private static final boolean DEBUG = false;
 
@@ -704,6 +707,7 @@
                     loc[1] = animatedPipBounds.top;
                 }
             };
+            mMagnetizedPip.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_PIP);
         }
 
         return mMagnetizedPip;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index a24d9618..4e1b046 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -32,6 +32,8 @@
 import android.os.RemoteException;
 import android.view.Gravity;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.WindowManagerShellWrapper;
@@ -51,6 +53,8 @@
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ConfigurationChangeListener;
 import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.UserChangeListener;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -64,7 +68,7 @@
 public class TvPipController implements PipTransitionController.PipTransitionCallback,
         TvPipBoundsController.PipBoundsListener, TvPipMenuController.Delegate,
         TvPipNotificationController.Delegate, DisplayController.OnDisplaysChangedListener,
-        ConfigurationChangeListener {
+        ConfigurationChangeListener, UserChangeListener {
     private static final String TAG = "TvPipController";
     static final boolean DEBUG = false;
 
@@ -105,6 +109,11 @@
     private final PipMediaController mPipMediaController;
     private final TvPipNotificationController mPipNotificationController;
     private final TvPipMenuController mTvPipMenuController;
+    private final PipTransitionController mPipTransitionController;
+    private final TaskStackListenerImpl mTaskStackListener;
+    private final PipParamsChangedForwarder mPipParamsChangedForwarder;
+    private final DisplayController mDisplayController;
+    private final WindowManagerShellWrapper mWmShellWrapper;
     private final ShellExecutor mMainExecutor;
     private final TvPipImpl mImpl = new TvPipImpl();
 
@@ -121,6 +130,7 @@
 
     public static Pip create(
             Context context,
+            ShellInit shellInit,
             ShellController shellController,
             TvPipBoundsState tvPipBoundsState,
             TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
@@ -138,6 +148,7 @@
             ShellExecutor mainExecutor) {
         return new TvPipController(
                 context,
+                shellInit,
                 shellController,
                 tvPipBoundsState,
                 tvPipBoundsAlgorithm,
@@ -157,6 +168,7 @@
 
     private TvPipController(
             Context context,
+            ShellInit shellInit,
             ShellController shellController,
             TvPipBoundsState tvPipBoundsState,
             TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
@@ -170,11 +182,12 @@
             TaskStackListenerImpl taskStackListener,
             PipParamsChangedForwarder pipParamsChangedForwarder,
             DisplayController displayController,
-            WindowManagerShellWrapper wmShell,
+            WindowManagerShellWrapper wmShellWrapper,
             ShellExecutor mainExecutor) {
         mContext = context;
         mMainExecutor = mainExecutor;
         mShellController = shellController;
+        mDisplayController = displayController;
 
         mTvPipBoundsState = tvPipBoundsState;
         mTvPipBoundsState.setDisplayId(context.getDisplayId());
@@ -193,16 +206,32 @@
 
         mAppOpsListener = pipAppOpsListener;
         mPipTaskOrganizer = pipTaskOrganizer;
-        pipTransitionController.registerPipTransitionCallback(this);
+        mPipTransitionController = pipTransitionController;
+        mPipParamsChangedForwarder = pipParamsChangedForwarder;
+        mTaskStackListener = taskStackListener;
+        mWmShellWrapper = wmShellWrapper;
+        shellInit.addInitCallback(this::onInit, this);
+    }
+
+    private void onInit() {
+        mPipTransitionController.registerPipTransitionCallback(this);
 
         loadConfigurations();
 
-        registerPipParamsChangedListener(pipParamsChangedForwarder);
-        registerTaskStackListenerCallback(taskStackListener);
-        registerWmShellPinnedStackListener(wmShell);
-        displayController.addDisplayWindowListener(this);
+        registerPipParamsChangedListener(mPipParamsChangedForwarder);
+        registerTaskStackListenerCallback(mTaskStackListener);
+        registerWmShellPinnedStackListener(mWmShellWrapper);
+        registerSessionListenerForCurrentUser();
+        mDisplayController.addDisplayWindowListener(this);
 
         mShellController.addConfigurationChangeListener(this);
+        mShellController.addUserChangeListener(this);
+    }
+
+    @Override
+    public void onUserChanged(int newUserId, @NonNull Context userContext) {
+        // Re-register the media session listener when switching users
+        registerSessionListenerForCurrentUser();
     }
 
     @Override
@@ -679,11 +708,6 @@
     }
 
     private class TvPipImpl implements Pip {
-        @Override
-        public void registerSessionListenerForCurrentUser() {
-            mMainExecutor.execute(() -> {
-                TvPipController.this.registerSessionListenerForCurrentUser();
-            });
-        }
+        // Not used
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 4d7c846..97e017a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -97,6 +97,9 @@
     private final ImageView mArrowLeft;
     private final TvWindowMenuActionButton mA11yDoneButton;
 
+    private final View mPipBackground;
+    private final View mDimLayer;
+
     private final ScrollView mScrollView;
     private final HorizontalScrollView mHorizontalScrollView;
     private View mFocusedButton;
@@ -148,6 +151,9 @@
         mExpandButton = findViewById(R.id.tv_pip_menu_expand_button);
         mExpandButton.setOnClickListener(this);
 
+        mPipBackground = findViewById(R.id.tv_pip_menu_background);
+        mDimLayer = findViewById(R.id.tv_pip_menu_dim_layer);
+
         mScrollView = findViewById(R.id.tv_pip_menu_scroll);
         mHorizontalScrollView = findViewById(R.id.tv_pip_menu_horizontal_scroll);
 
@@ -231,7 +237,7 @@
                     mCurrentPipBounds.width() / (float) mCurrentPipBounds.height(),
                     finishBounds.width() / (float) finishBounds.height());
             if (ratioChanged) {
-                mPipView.animate()
+                mPipBackground.animate()
                         .alpha(1f)
                         .setInterpolator(TvPipInterpolators.EXIT)
                         .setDuration(mResizeAnimationDuration / 2)
@@ -272,7 +278,7 @@
                 "%s: onPipTransitionFinished()", TAG);
 
         // Fade in content by fading out view on top.
-        mPipView.animate()
+        mPipBackground.animate()
                 .alpha(0f)
                 .setDuration(mResizeAnimationDuration / 2)
                 .setInterpolator(TvPipInterpolators.ENTER)
@@ -770,6 +776,7 @@
             refocusPreviousButton();
         }
         animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
+        animateAlphaTo(show ? 1 : 0, mDimLayer);
     }
 
     private void setFrameHighlighted(boolean highlighted) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 7e83d2f..21fc01e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -25,6 +25,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
@@ -488,13 +489,6 @@
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
 
-        // If split still not active, apply windows bounds first to avoid surface reset to
-        // wrong pos by SurfaceAnimator from wms.
-        // TODO(b/223325631): check  is it still necessary after improve enter transition done.
-        if (!mMainStage.isActive()) {
-            updateWindowBounds(mSplitLayout, wct);
-        }
-
         wct.sendPendingIntent(intent, fillInIntent, options);
         mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
     }
@@ -641,7 +635,7 @@
             wct.startTask(sideTaskId, sideOptions);
         }
         // Using legacy transitions, so we can't use blast sync since it conflicts.
-        mTaskOrganizer.applyTransaction(wct);
+        mSyncQueue.queue(wct);
         mSyncQueue.runInSync(t -> {
             setDividerVisibility(true, t);
             updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
@@ -893,10 +887,13 @@
         mShouldUpdateRecents = false;
         mIsDividerRemoteAnimating = false;
 
+        mSplitLayout.getInvisibleBounds(mTempRect1);
         if (childrenToTop == null) {
             mSideStage.removeAllTasks(wct, false /* toTop */);
             mMainStage.deactivate(wct, false /* toTop */);
             wct.reorder(mRootTaskInfo.token, false /* onTop */);
+            wct.setForceTranslucent(mRootTaskInfo.token, true);
+            wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
             onTransitionAnimationComplete();
         } else {
             // Expand to top side split as full screen for fading out decor animation and dismiss
@@ -907,27 +904,32 @@
                     ? mSideStage : mMainStage;
             tempFullStage.resetBounds(wct);
             wct.setSmallestScreenWidthDp(tempFullStage.mRootTaskInfo.token,
-                    mRootTaskInfo.configuration.smallestScreenWidthDp);
+                    SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
             dismissStage.dismiss(wct, false /* toTop */);
         }
         mSyncQueue.queue(wct);
         mSyncQueue.runInSync(t -> {
             t.setWindowCrop(mMainStage.mRootLeash, null)
                     .setWindowCrop(mSideStage.mRootLeash, null);
-            t.setPosition(mMainStage.mRootLeash, 0, 0)
-                    .setPosition(mSideStage.mRootLeash, 0, 0);
             t.hide(mMainStage.mDimLayer).hide(mSideStage.mDimLayer);
             setDividerVisibility(false, t);
 
-            // In this case, exit still under progress, fade out the split decor after first WCT
-            // done and do remaining WCT after animation finished.
-            if (childrenToTop != null) {
+            if (childrenToTop == null) {
+                t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.right);
+            } else {
+                // In this case, exit still under progress, fade out the split decor after first WCT
+                // done and do remaining WCT after animation finished.
                 childrenToTop.fadeOutDecor(() -> {
                     WindowContainerTransaction finishedWCT = new WindowContainerTransaction();
                     mIsExiting = false;
                     childrenToTop.dismiss(finishedWCT, true /* toTop */);
                     finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
-                    mTaskOrganizer.applyTransaction(finishedWCT);
+                    finishedWCT.setForceTranslucent(mRootTaskInfo.token, true);
+                    finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
+                    mSyncQueue.queue(finishedWCT);
+                    mSyncQueue.runInSync(at -> {
+                        at.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.right);
+                    });
                     onTransitionAnimationComplete();
                 });
             }
@@ -996,6 +998,7 @@
         mMainStage.activate(wct, true /* includingTopTask */);
         updateWindowBounds(mSplitLayout, wct);
         wct.reorder(mRootTaskInfo.token, true);
+        wct.setForceTranslucent(mRootTaskInfo.token, false);
     }
 
     void finishEnterSplitScreen(SurfaceControl.Transaction t) {
@@ -1221,7 +1224,13 @@
         // Make the stages adjacent to each other so they occlude what's behind them.
         wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
         wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
-        mTaskOrganizer.applyTransaction(wct);
+        wct.setForceTranslucent(mRootTaskInfo.token, true);
+        mSplitLayout.getInvisibleBounds(mTempRect1);
+        wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
+        mSyncQueue.queue(wct);
+        mSyncQueue.runInSync(t -> {
+            t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.top);
+        });
     }
 
     private void onRootTaskVanished() {
@@ -1377,10 +1386,17 @@
             // TODO (b/238697912) : Add the validation to prevent entering non-recovered status
             final WindowContainerTransaction wct = new WindowContainerTransaction();
             mSplitLayout.init();
-            prepareEnterSplitScreen(wct);
+            mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+            mMainStage.activate(wct, true /* includingTopTask */);
+            updateWindowBounds(mSplitLayout, wct);
+            wct.reorder(mRootTaskInfo.token, true);
+            wct.setForceTranslucent(mRootTaskInfo.token, false);
             mSyncQueue.queue(wct);
-            mSyncQueue.runInSync(t ->
-                    updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
+            mSyncQueue.runInSync(t -> {
+                updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+
+                mSplitLayout.flingDividerToCenter();
+            });
         }
         if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
             mShouldUpdateRecents = true;
@@ -1822,6 +1838,7 @@
             // properly for the animation itself.
             mSplitLayout.release();
             mSplitLayout.resetDividerPosition();
+            mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
             mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java
index 1c0b358..9df8631 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java
@@ -21,13 +21,13 @@
  */
 public interface KeyguardChangeListener {
     /**
-     * Notifies the Shell that the keyguard is showing (and if so, whether it is occluded).
+     * Called when the keyguard is showing (and if so, whether it is occluded).
      */
     default void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
             boolean animatingDismiss) {}
 
     /**
-     * Notifies the Shell when the keyguard dismiss animation has finished.
+     * Called when the keyguard dismiss animation has finished.
      *
      * TODO(b/206741900) deprecate this path once we're able to animate the PiP window as part of
      * keyguard dismiss animation.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index 52ffb46..5799394 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -25,7 +25,9 @@
 
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SYSUI_EVENTS;
 
+import android.content.Context;
 import android.content.pm.ActivityInfo;
+import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 
 import androidx.annotation.NonNull;
@@ -36,6 +38,7 @@
 import com.android.wm.shell.common.annotations.ExternalThread;
 
 import java.io.PrintWriter;
+import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
@@ -53,6 +56,9 @@
             new CopyOnWriteArrayList<>();
     private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners =
             new CopyOnWriteArrayList<>();
+    private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners =
+            new CopyOnWriteArrayList<>();
+
     private Configuration mLastConfiguration;
 
 
@@ -102,6 +108,22 @@
         mKeyguardChangeListeners.remove(listener);
     }
 
+    /**
+     * Adds a new user-change listener. The user change callbacks are not made in any
+     * particular order.
+     */
+    public void addUserChangeListener(UserChangeListener listener) {
+        mUserChangeListeners.remove(listener);
+        mUserChangeListeners.add(listener);
+    }
+
+    /**
+     * Removes an existing user-change listener.
+     */
+    public void removeUserChangeListener(UserChangeListener listener) {
+        mUserChangeListeners.remove(listener);
+    }
+
     @VisibleForTesting
     void onConfigurationChanged(Configuration newConfig) {
         // The initial config is send on startup and doesn't trigger listener callbacks
@@ -144,6 +166,8 @@
 
     @VisibleForTesting
     void onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss) {
+        ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Keyguard visibility changed: visible=%b "
+                + "occluded=%b animatingDismiss=%b", visible, occluded, animatingDismiss);
         for (KeyguardChangeListener listener : mKeyguardChangeListeners) {
             listener.onKeyguardVisibilityChanged(visible, occluded, animatingDismiss);
         }
@@ -151,17 +175,35 @@
 
     @VisibleForTesting
     void onKeyguardDismissAnimationFinished() {
+        ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Keyguard dismiss animation finished");
         for (KeyguardChangeListener listener : mKeyguardChangeListeners) {
             listener.onKeyguardDismissAnimationFinished();
         }
     }
 
+    @VisibleForTesting
+    void onUserChanged(int newUserId, @NonNull Context userContext) {
+        ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "User changed: id=%d", newUserId);
+        for (UserChangeListener listener : mUserChangeListeners) {
+            listener.onUserChanged(newUserId, userContext);
+        }
+    }
+
+    @VisibleForTesting
+    void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {
+        ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "User profiles changed");
+        for (UserChangeListener listener : mUserChangeListeners) {
+            listener.onUserProfilesChanged(profiles);
+        }
+    }
+
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
         pw.println(innerPrefix + "mConfigChangeListeners=" + mConfigChangeListeners.size());
         pw.println(innerPrefix + "mLastConfiguration=" + mLastConfiguration);
         pw.println(innerPrefix + "mKeyguardChangeListeners=" + mKeyguardChangeListeners.size());
+        pw.println(innerPrefix + "mUserChangeListeners=" + mUserChangeListeners.size());
     }
 
     /**
@@ -220,5 +262,17 @@
             mMainExecutor.execute(() ->
                     ShellController.this.onKeyguardDismissAnimationFinished());
         }
+
+        @Override
+        public void onUserChanged(int newUserId, @NonNull Context userContext) {
+            mMainExecutor.execute(() ->
+                    ShellController.this.onUserChanged(newUserId, userContext));
+        }
+
+        @Override
+        public void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {
+            mMainExecutor.execute(() ->
+                    ShellController.this.onUserProfilesChanged(profiles));
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
index 254c253..2108c82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
@@ -16,9 +16,14 @@
 
 package com.android.wm.shell.sysui;
 
+import android.content.Context;
+import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 
+import androidx.annotation.NonNull;
+
 import java.io.PrintWriter;
+import java.util.List;
 
 /**
  * General interface for notifying the Shell of common SysUI events like configuration or keyguard
@@ -59,4 +64,14 @@
      * Notifies the Shell when the keyguard dismiss animation has finished.
      */
     default void onKeyguardDismissAnimationFinished() {}
+
+    /**
+     * Notifies the Shell when the user changes.
+     */
+    default void onUserChanged(int newUserId, @NonNull Context userContext) {}
+
+    /**
+     * Notifies the Shell when a profile belonging to the user changes.
+     */
+    default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {}
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/UserChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/UserChangeListener.java
new file mode 100644
index 0000000..3d0909f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/UserChangeListener.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sysui;
+
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+
+/**
+ * Callbacks for when the user or user's profiles changes.
+ */
+public interface UserChangeListener {
+    /**
+     * Called when the current (parent) user changes.
+     */
+    default void onUserChanged(int newUserId, @NonNull Context userContext) {}
+
+    /**
+     * Called when a profile belonging to the user changes.
+     */
+    default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 6c65966..cff60f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -44,6 +44,8 @@
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.transitTypeToString;
+import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
+import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
@@ -903,11 +905,10 @@
     private void attachThumbnail(@NonNull ArrayList<Animator> animations,
             @NonNull Runnable finishCallback, TransitionInfo.Change change,
             TransitionInfo.AnimationOptions options, float cornerRadius) {
-        final boolean isTask = change.getTaskInfo() != null;
         final boolean isOpen = Transitions.isOpeningType(change.getMode());
         final boolean isClose = Transitions.isClosingType(change.getMode());
         if (isOpen) {
-            if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS && isTask) {
+            if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) {
                 attachCrossProfileThumbnailAnimation(animations, finishCallback, change,
                         cornerRadius);
             } else if (options.getType() == ANIM_THUMBNAIL_SCALE_UP) {
@@ -922,8 +923,13 @@
             @NonNull Runnable finishCallback, TransitionInfo.Change change, float cornerRadius) {
         final Rect bounds = change.getEndAbsBounds();
         // Show the right drawable depending on the user we're transitioning to.
-        final Drawable thumbnailDrawable = change.getTaskInfo().userId == mCurrentUserId
-                ? mContext.getDrawable(R.drawable.ic_account_circle) : mEnterpriseThumbnailDrawable;
+        final Drawable thumbnailDrawable = change.hasFlags(FLAG_CROSS_PROFILE_OWNER_THUMBNAIL)
+                        ? mContext.getDrawable(R.drawable.ic_account_circle)
+                        : change.hasFlags(FLAG_CROSS_PROFILE_WORK_THUMBNAIL)
+                                ? mEnterpriseThumbnailDrawable : null;
+        if (thumbnailDrawable == null) {
+            return;
+        }
         final HardwareBuffer thumbnail = mTransitionAnimation.createCrossProfileAppsThumbnail(
                 thumbnailDrawable, bounds);
         if (thumbnail == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
index bdcdb63..cc4d268 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
@@ -34,4 +34,9 @@
      * Unregisters a remote transition handler.
      */
     oneway void unregisterRemote(in RemoteTransition remoteTransition) = 2;
+
+    /**
+     * Retrieves the apply-token used by transactions in Shell
+     */
+    IBinder getShellApplyToken() = 3;
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 26d0ec6..d2e8624 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -219,6 +219,8 @@
                     + "use ShellInit callbacks to ensure proper ordering");
         }
         mHandlers.add(handler);
+        // Set initial scale settings.
+        handler.setAnimScaleSetting(mTransitionAnimationScaleSetting);
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: %s",
                 handler.getClass().getSimpleName());
     }
@@ -956,6 +958,11 @@
                         transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition);
                     });
         }
+
+        @Override
+        public IBinder getShellApplyToken() {
+            return SurfaceControl.Transaction.getDefaultApplyToken();
+        }
     }
 
     private class SettingsObserver extends ContentObserver {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index dc3deb1..8b13721 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -142,7 +142,7 @@
             return;
         }
 
-        if (oldDecorationSurface != mDecorationContainerSurface) {
+        if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) {
             closeDragResizeListener();
             mDragResizeListener = new DragResizeInputListener(
                     mContext,
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index 0a54b8c..a8154e8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -32,7 +32,6 @@
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd
 import com.android.server.wm.flicker.taskBarWindowIsAlwaysVisible
-import com.android.server.wm.traces.common.ComponentMatcher
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import org.junit.Assume
 import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 330c9c9..cb74315 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -20,6 +20,7 @@
 import android.view.Surface
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.traces.layers.LayerTraceEntrySubject
 import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
 import com.android.server.wm.traces.common.IComponentMatcher
 import com.android.server.wm.traces.common.region.Region
@@ -94,25 +95,27 @@
 
 fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible(
     component: IComponentMatcher,
-    splitLeftTop: Boolean
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean
 ) {
     assertLayers {
-        this.isInvisible(component)
+        this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
             .then()
-            .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
-            .isVisible(component)
+            .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
             .then()
-            .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
-            .splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+            .splitAppLayerBoundsSnapToDivider(
+                component, landscapePosLeft, portraitPosTop, endRotation)
     }
 }
 
 fun FlickerTestParameter.splitAppLayerBoundsBecomesInvisible(
     component: IComponentMatcher,
-    splitLeftTop: Boolean
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean
 ) {
     assertLayers {
-        this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+        this.splitAppLayerBoundsSnapToDivider(
+                component, landscapePosLeft, portraitPosTop, endRotation)
             .then()
             .isVisible(component, true)
             .then()
@@ -122,58 +125,96 @@
 
 fun FlickerTestParameter.splitAppLayerBoundsIsVisibleAtEnd(
     component: IComponentMatcher,
-    splitLeftTop: Boolean
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean
 ) {
     assertLayersEnd {
-        val dividerRegion = layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
-        visibleRegion(component).coversAtMost(
-            if (splitLeftTop) {
-                getSplitLeftTopRegion(dividerRegion, endRotation)
-            } else {
-                getSplitRightBottomRegion(dividerRegion, endRotation)
-            }
-        )
+        splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, endRotation)
     }
 }
 
 fun FlickerTestParameter.splitAppLayerBoundsKeepVisible(
     component: IComponentMatcher,
-    splitLeftTop: Boolean
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean
 ) {
     assertLayers {
-        this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+        splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, endRotation)
     }
 }
 
 fun FlickerTestParameter.splitAppLayerBoundsChanges(
     component: IComponentMatcher,
-    splitLeftTop: Boolean
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean
 ) {
     assertLayers {
-        if (splitLeftTop) {
-            this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+        if (landscapePosLeft) {
+            this.splitAppLayerBoundsSnapToDivider(
+                    component, landscapePosLeft, portraitPosTop, endRotation)
                 .then()
                 .isInvisible(component)
                 .then()
-                .splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+                .splitAppLayerBoundsSnapToDivider(
+                    component, landscapePosLeft, portraitPosTop, endRotation)
         } else {
-            this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+            this.splitAppLayerBoundsSnapToDivider(
+                component, landscapePosLeft, portraitPosTop, endRotation)
         }
     }
 }
 
 fun LayersTraceSubject.splitAppLayerBoundsSnapToDivider(
     component: IComponentMatcher,
-    splitLeftTop: Boolean,
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean,
     rotation: Int
 ): LayersTraceSubject {
     return invoke("splitAppLayerBoundsSnapToDivider") {
-        val dividerRegion = it.layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
-        it.visibleRegion(component).coversAtMost(
-            if (splitLeftTop) {
-                getSplitLeftTopRegion(dividerRegion, rotation)
+        it.splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, rotation)
+    }
+}
+
+fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
+    component: IComponentMatcher,
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean,
+    rotation: Int
+): LayerTraceEntrySubject {
+    val displayBounds = WindowUtils.getDisplayBounds(rotation)
+    return invoke {
+        val dividerRegion = layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
+        visibleRegion(component).coversAtMost(
+            if (displayBounds.width > displayBounds.height) {
+                if (landscapePosLeft) {
+                    Region.from(
+                        0,
+                        0,
+                        (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
+                        displayBounds.bounds.bottom)
+                } else {
+                    Region.from(
+                        (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
+                        0,
+                        displayBounds.bounds.right,
+                        displayBounds.bounds.bottom
+                    )
+                }
             } else {
-                getSplitRightBottomRegion(dividerRegion, rotation)
+                if (portraitPosTop) {
+                    Region.from(
+                        0,
+                        0,
+                        displayBounds.bounds.right,
+                        (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2)
+                } else {
+                    Region.from(
+                        0,
+                        (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2,
+                        displayBounds.bounds.right,
+                        displayBounds.bounds.bottom
+                    )
+                }
             }
         )
     }
@@ -185,6 +226,10 @@
     assertWm {
         this.isAppWindowInvisible(component)
             .then()
+            .notContains(component, isOptional = true)
+            .then()
+            .isAppWindowInvisible(component, isOptional = true)
+            .then()
             .isAppWindowVisible(component)
     }
 }
@@ -208,7 +253,7 @@
 }
 
 fun FlickerTestParameter.appWindowKeepVisible(
-        component: IComponentMatcher
+    component: IComponentMatcher
 ) {
     assertWm {
         this.isAppWindowVisible(component)
@@ -316,39 +361,3 @@
         )
     }
 }
-
-fun getSplitLeftTopRegion(dividerRegion: Region, rotation: Int): Region {
-    val displayBounds = WindowUtils.getDisplayBounds(rotation)
-    return if (displayBounds.width > displayBounds.height) {
-        Region.from(
-            0,
-            0,
-            (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
-            displayBounds.bounds.bottom)
-    } else {
-        Region.from(
-            0,
-            0,
-            displayBounds.bounds.right,
-            (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2)
-    }
-}
-
-fun getSplitRightBottomRegion(dividerRegion: Region, rotation: Int): Region {
-    val displayBounds = WindowUtils.getDisplayBounds(rotation)
-    return if (displayBounds.width > displayBounds.height) {
-        Region.from(
-            (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
-            0,
-            displayBounds.bounds.right,
-            displayBounds.bounds.bottom
-        )
-    } else {
-        Region.from(
-            0,
-            (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2,
-            displayBounds.bounds.right,
-            displayBounds.bounds.bottom
-        )
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
index 8b717a0..06361f9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
@@ -17,10 +17,10 @@
 @file:JvmName("CommonConstants")
 package com.android.wm.shell.flicker
 
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 
 const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
-val APP_PAIR_SPLIT_DIVIDER_COMPONENT = ComponentMatcher("", "AppPairSplitDivider#")
-val DOCKED_STACK_DIVIDER_COMPONENT = ComponentMatcher("", "DockedStackDivider#")
-val SPLIT_SCREEN_DIVIDER_COMPONENT = ComponentMatcher("", "StageCoordinatorSplitDivider#")
-val SPLIT_DECOR_MANAGER = ComponentMatcher("", "SplitDecorManager#")
+val APP_PAIR_SPLIT_DIVIDER_COMPONENT = ComponentNameMatcher("", "AppPairSplitDivider#")
+val DOCKED_STACK_DIVIDER_COMPONENT = ComponentNameMatcher("", "DockedStackDivider#")
+val SPLIT_SCREEN_DIVIDER_COMPONENT = ComponentNameMatcher("", "StageCoordinatorSplitDivider#")
+val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
index 1950e48..3ad92f8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
@@ -21,6 +21,7 @@
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiObject2
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
@@ -58,26 +59,27 @@
             setup {
                 test {
                     for (i in 1..3) {
-                        val addBubbleBtn = waitAndGetAddBubbleBtn()
-                        addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Add Bubble not found")
+                        val addBubbleBtn = waitAndGetAddBubbleBtn() ?: error("Add Bubble not found")
+                        addBubbleBtn.click()
+                        SystemClock.sleep(1000)
                     }
                     val showBubble = device.wait(
                         Until.findObject(
                             By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)
                         ), FIND_OBJECT_TIMEOUT
-                    )
-                    showBubble?.run { showBubble.click() } ?: error("Show bubble not found")
+                    ) ?: error("Show bubble not found")
+                    showBubble.click()
                     SystemClock.sleep(1000)
                 }
             }
             transitions {
-                val bubbles = device.wait(
+                val bubbles: List<UiObject2> = device.wait(
                     Until.findObjects(
                         By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)
                     ), FIND_OBJECT_TIMEOUT
                 ) ?: error("No bubbles found")
                 for (entry in bubbles) {
-                    entry?.run { entry.click() } ?: error("Bubble not found")
+                    entry.click()
                     SystemClock.sleep(1000)
                 }
             }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
index ffbac39..826cc2e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
@@ -17,10 +17,10 @@
 package com.android.wm.shell.flicker.helpers
 
 import android.app.Instrumentation
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 
 class AppPairsHelper(
     instrumentation: Instrumentation,
     activityLabel: String,
-    component: IComponentMatcher
+    component: ComponentNameMatcher
 ) : BaseAppHelper(instrumentation, activityLabel, component)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
index c4379e9..01ba990 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
@@ -26,13 +26,13 @@
 import androidx.test.uiautomator.Until
 import com.android.compatibility.common.util.SystemUtil
 import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.IComponentNameMatcher
 import java.io.IOException
 
 abstract class BaseAppHelper(
     instrumentation: Instrumentation,
     launcherName: String,
-    component: IComponentMatcher
+    component: IComponentNameMatcher
 ) : StandardAppHelper(
     instrumentation,
     launcherName,
@@ -46,9 +46,6 @@
             hasSystemFeature(FEATURE_LEANBACK) || hasSystemFeature(FEATURE_LEANBACK_ONLY)
         }
 
-    val defaultWindowName: String
-        get() = toWindowName()
-
     val ui: UiObject2?
         get() = uiDevice.findObject(appSelector)
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt
index 92b1d21..245a82f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt
@@ -19,12 +19,12 @@
 import android.app.Instrumentation
 import android.content.Context
 import android.provider.Settings
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 
 class MultiWindowHelper(
     instrumentation: Instrumentation,
     activityLabel: String,
-    componentsInfo: IComponentMatcher
+    componentsInfo: ComponentNameMatcher
 ) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) {
 
     companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
index a1226e68..e7f9d9a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
@@ -28,6 +28,7 @@
 import androidx.test.uiautomator.Until
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.IComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import com.android.wm.shell.flicker.SPLIT_DECOR_MANAGER
@@ -37,8 +38,8 @@
 class SplitScreenHelper(
     instrumentation: Instrumentation,
     activityLabel: String,
-    componentsInfo: IComponentMatcher
-) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) {
+    componentInfo: IComponentNameMatcher
+) : BaseAppHelper(instrumentation, activityLabel, componentInfo) {
 
     companion object {
         const val TEST_REPETITIONS = 1
@@ -104,6 +105,25 @@
                 .waitForAndVerify()
         }
 
+        fun splitFromOverview(tapl: LauncherInstrumentation) {
+            // Note: The initial split position in landscape is different between tablet and phone.
+            // In landscape, tablet will let the first app split to right side, and phone will
+            // split to left side.
+            if (tapl.isTablet) {
+                tapl.workspace.switchToOverview().overviewActions
+                    .clickSplit()
+                    .currentTask
+                    .open()
+            } else {
+                tapl.workspace.switchToOverview().currentTask
+                    .tapMenu()
+                    .tapSplitMenuItem()
+                    .currentTask
+                    .open()
+            }
+            SystemClock.sleep(TIMEOUT_MS)
+        }
+
         fun dragFromNotificationToSplit(
             instrumentation: Instrumentation,
             device: UiDevice,
@@ -280,12 +300,12 @@
         fun copyContentFromLeftToRight(
             instrumentation: Instrumentation,
             device: UiDevice,
-            sourceApp: IComponentMatcher,
-            destinationApp: IComponentMatcher,
+            sourceApp: IComponentNameMatcher,
+            destinationApp: IComponentNameMatcher,
         ) {
             // Copy text from sourceApp
             val textView = device.wait(Until.findObject(
-                By.res(sourceApp.packageNames.firstOrNull(), "SplitScreenTest")), TIMEOUT_MS)
+                By.res(sourceApp.packageName, "SplitScreenTest")), TIMEOUT_MS)
             longPress(instrumentation, textView.getVisibleCenter())
 
             val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
@@ -293,7 +313,7 @@
 
             // Paste text to destinationApp
             val editText = device.wait(Until.findObject(
-                By.res(destinationApp.packageNames.firstOrNull(), "plain_text_input")), TIMEOUT_MS)
+                By.res(destinationApp.packageName, "plain_text_input")), TIMEOUT_MS)
             longPress(instrumentation, editText.getVisibleCenter())
 
             val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
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 0450224..d194472 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
@@ -25,7 +25,7 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -155,9 +155,9 @@
     @Test
     fun launcherLayerBecomesVisible() {
         testSpec.assertLayers {
-            isInvisible(ComponentMatcher.LAUNCHER)
+            isInvisible(ComponentNameMatcher.LAUNCHER)
                 .then()
-                .isVisible(ComponentMatcher.LAUNCHER)
+                .isVisible(ComponentNameMatcher.LAUNCHER)
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index dff447b..507562b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -30,7 +30,6 @@
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
-import com.android.server.wm.traces.common.ComponentMatcher
 import com.android.wm.shell.flicker.helpers.FixedAppHelper
 import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
 import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
index 33f7871..fd1fe65 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
@@ -23,7 +23,7 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.traces.common.ComponentMatcher.Companion.LAUNCHER
+import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.LAUNCHER
 import org.junit.Test
 
 /**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index 5b5b9fc..31a39c1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -24,7 +24,7 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -63,9 +63,9 @@
                 val pipCenterY = pipRegion.centerY()
                 val displayCenterX = device.displayWidth / 2
                 val barComponent = if (testSpec.isTablet) {
-                    ComponentMatcher.TASK_BAR
+                    ComponentNameMatcher.TASK_BAR
                 } else {
-                    ComponentMatcher.NAV_BAR
+                    ComponentNameMatcher.NAV_BAR
                 }
                 val barLayerHeight = wmHelper.currentState.layerState
                     .getLayerWithBuffer(barComponent)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index 1c0bd0c..fd661cf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -25,7 +25,7 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -151,7 +151,7 @@
     @Test
     fun launcherIsAlwaysVisible() {
         testSpec.assertLayers {
-            isVisible(ComponentMatcher.LAUNCHER)
+            isVisible(ComponentNameMatcher.LAUNCHER)
         }
     }
 
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 911d402..454927e 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
@@ -28,7 +28,7 @@
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.wm.shell.flicker.helpers.ImeAppHelper
 import org.junit.Assume.assumeFalse
 import org.junit.Before
@@ -105,7 +105,7 @@
     @Test
     open fun pipIsAboveAppWindow() {
         testSpec.assertWmTag(TAG_IME_VISIBLE) {
-            isAboveWindow(ComponentMatcher.IME, pipApp)
+            isAboveWindow(ComponentNameMatcher.IME, pipApp)
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index f69107e..d238814 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -92,12 +92,12 @@
     @Presubmit
     @Test
     fun primaryAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
-        primaryApp, splitLeftTop = true)
+        primaryApp, landscapePosLeft = true, portraitPosTop = true)
 
     @Presubmit
     @Test
     fun textEditAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
-        textEditApp, splitLeftTop = false)
+        textEditApp, landscapePosLeft = false, portraitPosTop = false)
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index cd92db7..ba40c27 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -98,7 +98,7 @@
     @Presubmit
     @Test
     fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
-        primaryApp, splitLeftTop = false)
+        primaryApp, landscapePosLeft = false, portraitPosTop = false)
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 127ac1e..6828589 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -96,12 +96,12 @@
     @Presubmit
     @Test
     fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
-        primaryApp, splitLeftTop = false)
+        primaryApp, landscapePosLeft = false, portraitPosTop = false)
 
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
-        secondaryApp, splitLeftTop = true)
+        secondaryApp, landscapePosLeft = true, portraitPosTop = true)
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 0f4d98d..9ac7c23 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -108,12 +108,12 @@
     @Presubmit
     @Test
     fun primaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
-        primaryApp, splitLeftTop = false)
+        primaryApp, landscapePosLeft = false, portraitPosTop = false)
 
     @Presubmit
     @Test
     fun secondaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
-        secondaryApp, splitLeftTop = true)
+        secondaryApp, landscapePosLeft = true, portraitPosTop = true)
 
     /** {@inheritDoc} */
     @Postsubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 9564d97..8401c1a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -94,12 +94,12 @@
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        primaryApp, splitLeftTop = false)
+        primaryApp, landscapePosLeft = false, portraitPosTop = false)
 
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
-        secondaryApp, splitLeftTop = true)
+        secondaryApp, landscapePosLeft = true, portraitPosTop = true)
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index 3b59716..168afda 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -109,12 +109,12 @@
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        primaryApp, splitLeftTop = false)
+        primaryApp, landscapePosLeft = false, portraitPosTop = false)
 
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
-        sendNotificationApp, splitLeftTop = true)
+        sendNotificationApp, landscapePosLeft = true, portraitPosTop = true)
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 3de9872..c1fce5f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -97,12 +97,12 @@
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        primaryApp, splitLeftTop = false)
+        primaryApp, landscapePosLeft = false, portraitPosTop = false)
 
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
-        secondaryApp, splitLeftTop = true)
+        secondaryApp, landscapePosLeft = true, portraitPosTop = true)
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
new file mode 100644
index 0000000..8cb5d7c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.flicker.splitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test enter split screen from Overview.
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenFromOverview`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                eachRun {
+                    tapl.workspace.switchToOverview().dismissAllTasks()
+                    primaryApp.launchViaIntent(wmHelper)
+                    secondaryApp.launchViaIntent(wmHelper)
+                    tapl.goHome()
+                    wmHelper.StateSyncBuilder()
+                        .withAppTransitionIdle()
+                        .withHomeActivityVisible()
+                        .waitForAndVerify()
+                }
+            }
+            transitions {
+                SplitScreenHelper.splitFromOverview(tapl)
+                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+        }
+
+    @Presubmit
+    @Test
+    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+    @Presubmit
+    @Test
+    fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+
+    @Presubmit
+    @Test
+    fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+
+    @Presubmit
+    @Test
+    fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
+
+    @Presubmit
+    @Test
+    fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
+        secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
+
+    @Presubmit
+    @Test
+    fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+
+    @Presubmit
+    @Test
+    fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun entireScreenCovered() =
+        super.entireScreenCovered()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() =
+        super.navBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() =
+        super.navBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarWindowIsAlwaysVisible() =
+        super.navBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() =
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() =
+        super.statusBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() =
+        super.statusBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() =
+        super.taskBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() =
+        super.taskBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTestParameter> {
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+                repetitions = SplitScreenHelper.TEST_REPETITIONS)
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index bdfd9c7..1530561 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -94,12 +94,12 @@
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        primaryApp, splitLeftTop = true)
+        primaryApp, landscapePosLeft = true, portraitPosTop = true)
 
     @Presubmit
     @Test
     fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        secondaryApp, splitLeftTop = false)
+        secondaryApp, landscapePosLeft = false, portraitPosTop = false)
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index da954d9..20544bd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -98,12 +98,12 @@
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        primaryApp, splitLeftTop = false)
+        primaryApp, landscapePosLeft = false, portraitPosTop = false)
 
     @Presubmit
     @Test
     fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        secondaryApp, splitLeftTop = true)
+        secondaryApp, landscapePosLeft = true, portraitPosTop = true)
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index db89ff5..5a8604f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -97,12 +97,12 @@
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        primaryApp, splitLeftTop = false)
+        primaryApp, landscapePosLeft = false, portraitPosTop = false)
 
     @Presubmit
     @Test
     fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        secondaryApp, splitLeftTop = true)
+        secondaryApp, landscapePosLeft = true, portraitPosTop = true)
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index c23cdb6..adea66a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -99,12 +99,12 @@
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        primaryApp, splitLeftTop = false)
+        primaryApp, landscapePosLeft = false, portraitPosTop = false)
 
     @Presubmit
     @Test
     fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        secondaryApp, splitLeftTop = true)
+        secondaryApp, landscapePosLeft = true, portraitPosTop = true)
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
new file mode 100644
index 0000000..b2e45a6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.activityembedding;
+
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.window.TransitionInfo.FLAG_IS_EMBEDDED;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.window.TransitionInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+/**
+ * Tests for {@link ActivityEmbeddingAnimationRunner}.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:ActivityEmbeddingAnimationRunnerTests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnimationTestBase {
+
+    @Before
+    public void setup() {
+        super.setUp();
+        doNothing().when(mController).onAnimationFinished(any());
+    }
+
+    @Test
+    public void testStartAnimation() {
+        final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
+        final TransitionInfo.Change embeddingChange = createChange();
+        embeddingChange.setFlags(FLAG_IS_EMBEDDED);
+        info.addChange(embeddingChange);
+        doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any());
+
+        mAnimRunner.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction);
+
+        final ArgumentCaptor<Runnable> finishCallback = ArgumentCaptor.forClass(Runnable.class);
+        verify(mAnimRunner).createAnimator(eq(info), eq(mStartTransaction), eq(mFinishTransaction),
+                finishCallback.capture());
+        verify(mStartTransaction).apply();
+        verify(mAnimator).start();
+        verifyNoMoreInteractions(mFinishTransaction);
+        verify(mController, never()).onAnimationFinished(any());
+
+        // Call onAnimationFinished() when the animation is finished.
+        finishCallback.getValue().run();
+
+        verify(mController).onAnimationFinished(mTransition);
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
new file mode 100644
index 0000000..84befdd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.activityembedding;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.Mockito.mock;
+
+import android.animation.Animator;
+import android.annotation.CallSuper;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+
+import org.junit.Before;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** TestBase for ActivityEmbedding animation. */
+abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase {
+
+    @Mock
+    ShellInit mShellInit;
+    @Mock
+    Transitions mTransitions;
+    @Mock
+    IBinder mTransition;
+    @Mock
+    SurfaceControl.Transaction mStartTransaction;
+    @Mock
+    SurfaceControl.Transaction mFinishTransaction;
+    @Mock
+    Transitions.TransitionFinishCallback mFinishCallback;
+    @Mock
+    Animator mAnimator;
+
+    ActivityEmbeddingController mController;
+    ActivityEmbeddingAnimationRunner mAnimRunner;
+    ActivityEmbeddingAnimationSpec mAnimSpec;
+
+    @CallSuper
+    @Before
+    public void setUp() {
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+        MockitoAnnotations.initMocks(this);
+        mController = ActivityEmbeddingController.create(mContext, mShellInit, mTransitions);
+        assertNotNull(mController);
+        mAnimRunner = mController.mAnimationRunner;
+        assertNotNull(mAnimRunner);
+        mAnimSpec = mAnimRunner.mAnimationSpec;
+        assertNotNull(mAnimSpec);
+        spyOn(mController);
+        spyOn(mAnimRunner);
+        spyOn(mAnimSpec);
+    }
+
+    /** Creates a mock {@link TransitionInfo.Change}. */
+    static TransitionInfo.Change createChange() {
+        return new TransitionInfo.Change(mock(WindowContainerToken.class),
+                mock(SurfaceControl.class));
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index bfe3b54..cf43b00 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -16,52 +16,117 @@
 
 package com.android.wm.shell.activityembedding;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.window.TransitionInfo.FLAG_IS_EMBEDDED;
 
-import static org.junit.Assume.assumeTrue;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
-import android.content.Context;
+import android.window.TransitionInfo;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 
 /**
- * Tests for the activity embedding controller.
+ * Tests for {@link ActivityEmbeddingController}.
  *
  * Build/Install/Run:
  *  atest WMShellUnitTests:ActivityEmbeddingControllerTests
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class ActivityEmbeddingControllerTests extends ShellTestCase {
-
-    private @Mock Context mContext;
-    private @Mock ShellInit mShellInit;
-    private @Mock Transitions mTransitions;
-    private ActivityEmbeddingController mController;
+public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimationTestBase {
 
     @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mController = spy(new ActivityEmbeddingController(mContext, mShellInit, mTransitions));
+    public void setup() {
+        super.setUp();
+        doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any());
     }
 
     @Test
-    public void instantiate_addInitCallback() {
-        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
-        verify(mShellInit, times(1)).addInitCallback(any(), any());
+    public void testInstantiate() {
+        verify(mShellInit).addInitCallback(any(), any());
+    }
+
+    @Test
+    public void testOnInit() {
+        mController.onInit();
+
+        verify(mTransitions).addHandler(mController);
+    }
+
+    @Test
+    public void testSetAnimScaleSetting() {
+        mController.setAnimScaleSetting(1.0f);
+
+        verify(mAnimRunner).setAnimScaleSetting(1.0f);
+        verify(mAnimSpec).setAnimScaleSetting(1.0f);
+    }
+
+    @Test
+    public void testStartAnimation_containsNonActivityEmbeddingChange() {
+        final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
+        final TransitionInfo.Change embeddingChange = createChange();
+        embeddingChange.setFlags(FLAG_IS_EMBEDDED);
+        final TransitionInfo.Change nonEmbeddingChange = createChange();
+        info.addChange(embeddingChange);
+        info.addChange(nonEmbeddingChange);
+
+        // No-op
+        assertFalse(mController.startAnimation(mTransition, info, mStartTransaction,
+                mFinishTransaction, mFinishCallback));
+        verify(mAnimRunner, never()).startAnimation(any(), any(), any(), any());
+        verifyNoMoreInteractions(mStartTransaction);
+        verifyNoMoreInteractions(mFinishTransaction);
+        verifyNoMoreInteractions(mFinishCallback);
+    }
+
+    @Test
+    public void testStartAnimation_onlyActivityEmbeddingChange() {
+        final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
+        final TransitionInfo.Change embeddingChange = createChange();
+        embeddingChange.setFlags(FLAG_IS_EMBEDDED);
+        info.addChange(embeddingChange);
+
+        // No-op
+        assertTrue(mController.startAnimation(mTransition, info, mStartTransaction,
+                mFinishTransaction, mFinishCallback));
+        verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction,
+                mFinishTransaction);
+        verify(mStartTransaction).apply();
+        verifyNoMoreInteractions(mFinishTransaction);
+    }
+
+    @Test
+    public void testOnAnimationFinished() {
+        // Should not call finish when there is no transition.
+        assertThrows(IllegalStateException.class,
+                () -> mController.onAnimationFinished(mTransition));
+
+        final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
+        final TransitionInfo.Change embeddingChange = createChange();
+        embeddingChange.setFlags(FLAG_IS_EMBEDDED);
+        info.addChange(embeddingChange);
+        mController.startAnimation(mTransition, info, mStartTransaction,
+                mFinishTransaction, mFinishCallback);
+
+        verify(mFinishCallback, never()).onTransitionFinished(any(), any());
+        mController.onAnimationFinished(mTransition);
+        verify(mFinishCallback).onTransitionFinished(any(), any());
+
+        // Should not call finish when the finish has already been called.
+        assertThrows(IllegalStateException.class,
+                () -> mController.onAnimationFinished(mTransition));
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 95725bb..695550d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -159,7 +159,8 @@
     }
 
     private void waitDividerFlingFinished() {
-        verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), mRunnableCaptor.capture());
+        verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), anyInt(),
+                mRunnableCaptor.capture());
         mRunnableCaptor.getValue().run();
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index 90645ce..cf8297e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -171,6 +171,11 @@
     }
 
     @Test
+    public void testControllerRegistersUserChangeListener() {
+        verify(mMockShellController, times(1)).addUserChangeListener(any());
+    }
+
+    @Test
     public void testDefaultShouldNotInOneHanded() {
         // Assert default transition state is STATE_NONE
         assertThat(mSpiedTransitionState.getState()).isEqualTo(STATE_NONE);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 9ed8d84..eb5726b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -77,9 +78,9 @@
 public class PipControllerTest extends ShellTestCase {
     private PipController mPipController;
     private ShellInit mShellInit;
+    private ShellController mShellController;
 
     @Mock private ShellCommandHandler mMockShellCommandHandler;
-    @Mock private ShellController mMockShellController;
     @Mock private DisplayController mMockDisplayController;
     @Mock private PhonePipMenuController mMockPhonePipMenuController;
     @Mock private PipAppOpsListener mMockPipAppOpsListener;
@@ -110,8 +111,10 @@
             return null;
         }).when(mMockExecutor).execute(any());
         mShellInit = spy(new ShellInit(mMockExecutor));
+        mShellController = spy(new ShellController(mShellInit, mMockShellCommandHandler,
+                mMockExecutor));
         mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
-                mMockShellController, mMockDisplayController, mMockPipAppOpsListener,
+                mShellController, mMockDisplayController, mMockPipAppOpsListener,
                 mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
                 mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
                 mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
@@ -135,12 +138,22 @@
 
     @Test
     public void instantiatePipController_registerConfigChangeListener() {
-        verify(mMockShellController, times(1)).addConfigurationChangeListener(any());
+        verify(mShellController, times(1)).addConfigurationChangeListener(any());
     }
 
     @Test
     public void instantiatePipController_registerKeyguardChangeListener() {
-        verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
+        verify(mShellController, times(1)).addKeyguardChangeListener(any());
+    }
+
+    @Test
+    public void instantiatePipController_registerUserChangeListener() {
+        verify(mShellController, times(1)).addUserChangeListener(any());
+    }
+
+    @Test
+    public void instantiatePipController_registerMediaListener() {
+        verify(mMockPipMediaController, times(1)).registerSessionListenerForCurrentUser();
     }
 
     @Test
@@ -167,7 +180,7 @@
 
         ShellInit shellInit = new ShellInit(mMockExecutor);
         assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler,
-                mMockShellController, mMockDisplayController, mMockPipAppOpsListener,
+                mShellController, mMockDisplayController, mMockPipAppOpsListener,
                 mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
                 mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
                 mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
@@ -264,4 +277,11 @@
 
         verify(mMockPipBoundsState).setKeepClearAreas(Set.of(keepClearArea), Set.of());
     }
+
+    @Test
+    public void onUserChangeRegisterMediaListener() {
+        reset(mMockPipMediaController);
+        mShellController.asShell().onUserChanged(100, mContext);
+        verify(mMockPipMediaController, times(1)).registerSessionListenerForCurrentUser();
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index 39e58ff..d6ddba9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -17,11 +17,15 @@
 package com.android.wm.shell.sysui;
 
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
 
+import android.content.Context;
+import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
@@ -35,6 +39,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Locale;
 
 @SmallTest
@@ -42,22 +48,29 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class ShellControllerTest extends ShellTestCase {
 
+    private static final int TEST_USER_ID = 100;
+
     @Mock
     private ShellInit mShellInit;
     @Mock
     private ShellCommandHandler mShellCommandHandler;
     @Mock
     private ShellExecutor mExecutor;
+    @Mock
+    private Context mTestUserContext;
 
     private ShellController mController;
     private TestConfigurationChangeListener mConfigChangeListener;
     private TestKeyguardChangeListener mKeyguardChangeListener;
+    private TestUserChangeListener mUserChangeListener;
+
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mKeyguardChangeListener = new TestKeyguardChangeListener();
         mConfigChangeListener = new TestConfigurationChangeListener();
+        mUserChangeListener = new TestUserChangeListener();
         mController = new ShellController(mShellInit, mShellCommandHandler, mExecutor);
         mController.onConfigurationChanged(getConfigurationCopy());
     }
@@ -68,6 +81,46 @@
     }
 
     @Test
+    public void testAddUserChangeListener_ensureCallback() {
+        mController.addUserChangeListener(mUserChangeListener);
+
+        mController.onUserChanged(TEST_USER_ID, mTestUserContext);
+        assertTrue(mUserChangeListener.userChanged == 1);
+        assertTrue(mUserChangeListener.lastUserContext == mTestUserContext);
+    }
+
+    @Test
+    public void testDoubleAddUserChangeListener_ensureSingleCallback() {
+        mController.addUserChangeListener(mUserChangeListener);
+        mController.addUserChangeListener(mUserChangeListener);
+
+        mController.onUserChanged(TEST_USER_ID, mTestUserContext);
+        assertTrue(mUserChangeListener.userChanged == 1);
+        assertTrue(mUserChangeListener.lastUserContext == mTestUserContext);
+    }
+
+    @Test
+    public void testAddRemoveUserChangeListener_ensureNoCallback() {
+        mController.addUserChangeListener(mUserChangeListener);
+        mController.removeUserChangeListener(mUserChangeListener);
+
+        mController.onUserChanged(TEST_USER_ID, mTestUserContext);
+        assertTrue(mUserChangeListener.userChanged == 0);
+        assertTrue(mUserChangeListener.lastUserContext == null);
+    }
+
+    @Test
+    public void testUserProfilesChanged() {
+        mController.addUserChangeListener(mUserChangeListener);
+
+        ArrayList<UserInfo> profiles = new ArrayList<>();
+        profiles.add(mock(UserInfo.class));
+        profiles.add(mock(UserInfo.class));
+        mController.onUserProfilesChanged(profiles);
+        assertTrue(mUserChangeListener.lastUserProfiles.equals(profiles));
+    }
+
+    @Test
     public void testAddKeyguardChangeListener_ensureCallback() {
         mController.addKeyguardChangeListener(mKeyguardChangeListener);
 
@@ -332,4 +385,27 @@
             dismissAnimationFinished++;
         }
     }
+
+    private class TestUserChangeListener implements UserChangeListener {
+        // Counts of number of times each of the callbacks are called
+        public int userChanged;
+        public int lastUserId;
+        public Context lastUserContext;
+        public int userProfilesChanged;
+        public List<? extends UserInfo> lastUserProfiles;
+
+
+        @Override
+        public void onUserChanged(int newUserId, @NonNull Context userContext) {
+            userChanged++;
+            lastUserId = newUserId;
+            lastUserContext = userContext;
+        }
+
+        @Override
+        public void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {
+            userProfilesChanged++;
+            lastUserProfiles = profiles;
+        }
+    }
 }
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 9a4bda2..3c67edc 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -125,9 +125,14 @@
 }
 
 Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::FontFamily>>&& families,
-                                       int weight, int italic) {
+                                       int weight, int italic, const Typeface* fallback) {
     Typeface* result = new Typeface;
-    result->fFontCollection = minikin::FontCollection::create(families);
+    if (fallback == nullptr) {
+        result->fFontCollection = minikin::FontCollection::create(std::move(families));
+    } else {
+        result->fFontCollection =
+                fallback->fFontCollection->createCollectionWithFamilies(std::move(families));
+    }
 
     if (weight == RESOLVE_BY_FONT_TABLE || italic == RESOLVE_BY_FONT_TABLE) {
         int weightFromFont;
diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h
index 0c3ef01..565136e 100644
--- a/libs/hwui/hwui/Typeface.h
+++ b/libs/hwui/hwui/Typeface.h
@@ -78,7 +78,8 @@
             Typeface* src, const std::vector<minikin::FontVariation>& variations);
 
     static Typeface* createFromFamilies(
-            std::vector<std::shared_ptr<minikin::FontFamily>>&& families, int weight, int italic);
+            std::vector<std::shared_ptr<minikin::FontFamily>>&& families, int weight, int italic,
+            const Typeface* fallback);
 
     static void setDefault(const Typeface* face);
 
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index f5ed568..209b35c 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -109,27 +109,14 @@
 static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArray,
                                       jlong fallbackPtr, int weight, int italic) {
     ScopedLongArrayRO families(env, familyArray);
-    std::vector<std::shared_ptr<minikin::FontFamily>> familyVec;
     Typeface* typeface = (fallbackPtr == 0) ? nullptr : toTypeface(fallbackPtr);
-    if (typeface != nullptr) {
-        const std::shared_ptr<minikin::FontCollection>& fallbackCollection =
-                toTypeface(fallbackPtr)->fFontCollection;
-        familyVec.reserve(families.size() + fallbackCollection->getFamilyCount());
-        for (size_t i = 0; i < families.size(); i++) {
-            FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]);
-            familyVec.emplace_back(family->family);
-        }
-        for (size_t i = 0; i < fallbackCollection->getFamilyCount(); i++) {
-            familyVec.emplace_back(fallbackCollection->getFamilyAt(i));
-        }
-    } else {
-        familyVec.reserve(families.size());
-        for (size_t i = 0; i < families.size(); i++) {
-            FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]);
-            familyVec.emplace_back(family->family);
-        }
+    std::vector<std::shared_ptr<minikin::FontFamily>> familyVec;
+    familyVec.reserve(families.size());
+    for (size_t i = 0; i < families.size(); i++) {
+        FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]);
+        familyVec.emplace_back(family->family);
     }
-    return toJLong(Typeface::createFromFamilies(std::move(familyVec), weight, italic));
+    return toJLong(Typeface::createFromFamilies(std::move(familyVec), weight, italic, typeface));
 }
 
 // CriticalNative
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 81bcb4e..c5196ee 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -54,8 +54,6 @@
 #include <SkColorSpace.h>
 #include <SkRefCnt.h>
 
-class GrVkExtensions;
-
 namespace android {
 namespace uirenderer {
 namespace renderthread {
diff --git a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
index a0bc5aa..2aeb42c 100644
--- a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
@@ -16,7 +16,8 @@
 
 #include "TestSceneBase.h"
 
-#include <SkColorMatrixFilter.h>
+#include <SkColorFilter.h>
+#include <SkColorMatrix.h>
 #include <SkGradientShader.h>
 
 class SimpleColorMatrixAnimation;
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index 25cc8ca..499afa0 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -73,7 +73,8 @@
 
 TEST(TypefaceTest, resolveDefault_and_setDefaultTest) {
     std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
-            makeSingleFamlyVector(kRobotoVariable), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+            makeSingleFamlyVector(kRobotoVariable), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
+            nullptr /* fallback */));
     EXPECT_EQ(regular.get(), Typeface::resolveDefault(regular.get()));
 
     // Keep the original to restore it later.
@@ -351,24 +352,24 @@
 TEST(TypefaceTest, createFromFamilies_Single) {
     // In Java, new
     // Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(false).build();
-    std::unique_ptr<Typeface> regular(
-            Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 400, false));
+    std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
+            makeSingleFamlyVector(kRobotoVariable), 400, false, nullptr /* fallback */));
     EXPECT_EQ(400, regular->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
     EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
 
     // In Java, new
     // Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(false).build();
-    std::unique_ptr<Typeface> bold(
-            Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 700, false));
+    std::unique_ptr<Typeface> bold(Typeface::createFromFamilies(
+            makeSingleFamlyVector(kRobotoVariable), 700, false, nullptr /* fallback */));
     EXPECT_EQ(700, bold->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java, new
     // Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(true).build();
-    std::unique_ptr<Typeface> italic(
-            Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 400, true));
+    std::unique_ptr<Typeface> italic(Typeface::createFromFamilies(
+            makeSingleFamlyVector(kRobotoVariable), 400, true, nullptr /* fallback */));
     EXPECT_EQ(400, italic->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
@@ -376,8 +377,8 @@
     // In Java,
     // new
     // Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(true).build();
-    std::unique_ptr<Typeface> boldItalic(
-            Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 700, true));
+    std::unique_ptr<Typeface> boldItalic(Typeface::createFromFamilies(
+            makeSingleFamlyVector(kRobotoVariable), 700, true, nullptr /* fallback */));
     EXPECT_EQ(700, boldItalic->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
@@ -385,8 +386,8 @@
     // In Java,
     // new
     // Typeface.Builder("Roboto-Regular.ttf").setWeight(1100).setItalic(false).build();
-    std::unique_ptr<Typeface> over1000(
-            Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 1100, false));
+    std::unique_ptr<Typeface> over1000(Typeface::createFromFamilies(
+            makeSingleFamlyVector(kRobotoVariable), 1100, false, nullptr /* fallback */));
     EXPECT_EQ(1000, over1000->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, over1000->fStyle.slant());
     EXPECT_EQ(Typeface::kBold, over1000->fAPIStyle);
@@ -394,30 +395,33 @@
 
 TEST(TypefaceTest, createFromFamilies_Single_resolveByTable) {
     // In Java, new Typeface.Builder("Family-Regular.ttf").build();
-    std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
-            makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+    std::unique_ptr<Typeface> regular(
+            Typeface::createFromFamilies(makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE,
+                                         RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
     EXPECT_EQ(400, regular->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
     EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
 
     // In Java, new Typeface.Builder("Family-Bold.ttf").build();
-    std::unique_ptr<Typeface> bold(Typeface::createFromFamilies(
-            makeSingleFamlyVector(kBoldFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+    std::unique_ptr<Typeface> bold(
+            Typeface::createFromFamilies(makeSingleFamlyVector(kBoldFont), RESOLVE_BY_FONT_TABLE,
+                                         RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
     EXPECT_EQ(700, bold->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
     EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java, new Typeface.Builder("Family-Italic.ttf").build();
-    std::unique_ptr<Typeface> italic(Typeface::createFromFamilies(
-            makeSingleFamlyVector(kItalicFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+    std::unique_ptr<Typeface> italic(
+            Typeface::createFromFamilies(makeSingleFamlyVector(kItalicFont), RESOLVE_BY_FONT_TABLE,
+                                         RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
     EXPECT_EQ(400, italic->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java, new Typeface.Builder("Family-BoldItalic.ttf").build();
-    std::unique_ptr<Typeface> boldItalic(
-            Typeface::createFromFamilies(makeSingleFamlyVector(kBoldItalicFont),
-                                         RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+    std::unique_ptr<Typeface> boldItalic(Typeface::createFromFamilies(
+            makeSingleFamlyVector(kBoldItalicFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
+            nullptr /* fallback */));
     EXPECT_EQ(700, boldItalic->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
     EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
@@ -427,8 +431,9 @@
     std::vector<std::shared_ptr<minikin::FontFamily>> families = {
             buildFamily(kRegularFont), buildFamily(kBoldFont), buildFamily(kItalicFont),
             buildFamily(kBoldItalicFont)};
-    std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies(
-            std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+    std::unique_ptr<Typeface> typeface(
+            Typeface::createFromFamilies(std::move(families), RESOLVE_BY_FONT_TABLE,
+                                         RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
     EXPECT_EQ(400, typeface->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->fStyle.slant());
 }
@@ -436,10 +441,24 @@
 TEST(TypefaceTest, createFromFamilies_Family_withoutRegular) {
     std::vector<std::shared_ptr<minikin::FontFamily>> families = {
             buildFamily(kBoldFont), buildFamily(kItalicFont), buildFamily(kBoldItalicFont)};
-    std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies(
-            std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+    std::unique_ptr<Typeface> typeface(
+            Typeface::createFromFamilies(std::move(families), RESOLVE_BY_FONT_TABLE,
+                                         RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
     EXPECT_EQ(700, typeface->fStyle.weight());
     EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->fStyle.slant());
 }
 
+TEST(TypefaceTest, createFromFamilies_Family_withFallback) {
+    std::vector<std::shared_ptr<minikin::FontFamily>> fallbackFamilies = {
+            buildFamily(kBoldFont), buildFamily(kItalicFont), buildFamily(kBoldItalicFont)};
+    std::unique_ptr<Typeface> fallback(
+            Typeface::createFromFamilies(std::move(fallbackFamilies), RESOLVE_BY_FONT_TABLE,
+                                         RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
+    std::unique_ptr<Typeface> regular(
+            Typeface::createFromFamilies(makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE,
+                                         RESOLVE_BY_FONT_TABLE, fallback.get()));
+    EXPECT_EQ(400, regular->fStyle.weight());
+    EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
+}
+
 }  // namespace
diff --git a/media/Android.bp b/media/Android.bp
index d28a21c..ec243bf 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -113,7 +113,7 @@
             min_sdk_version: "29",
             apex_available: [
                 "//apex_available:platform",
-                "com.android.bluetooth",
+                "com.android.btservices",
             ],
         },
     },
diff --git a/media/java/android/media/BluetoothProfileConnectionInfo.java b/media/java/android/media/BluetoothProfileConnectionInfo.java
index f3a65a1..a316c21 100644
--- a/media/java/android/media/BluetoothProfileConnectionInfo.java
+++ b/media/java/android/media/BluetoothProfileConnectionInfo.java
@@ -126,7 +126,6 @@
     }
 
     /**
-     * @hide
      * Factory method for <code>BluetoothProfileConnectionInfo</code> for an LE output device
      * @param suppressNoisyIntent if true the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY}
      *     intent will not be sent.
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 8afc7d9..b6f07f4 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -79,8 +79,9 @@
     final String mPackageName;
 
     private final Context mContext;
-    @GuardedBy("sLock")
-    private Client mClient;
+
+    private final Client mClient;
+
     private final IMediaRouterService mMediaRouterService;
     private final AtomicInteger mScanRequestCount = new AtomicInteger(/* initialValue= */ 0);
     final Handler mHandler;
@@ -120,7 +121,12 @@
                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
         mPackageName = mContext.getPackageName();
         mHandler = new Handler(context.getMainLooper());
-        mHandler.post(this::getOrCreateClient);
+        mClient = new Client();
+        try {
+            mMediaRouterService.registerManager(mClient, mPackageName);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -167,7 +173,7 @@
     public void registerScanRequest() {
         if (mScanRequestCount.getAndIncrement() == 0) {
             try {
-                mMediaRouterService.startScan(getOrCreateClient());
+                mMediaRouterService.startScan(mClient);
             } catch (RemoteException ex) {
                 throw ex.rethrowFromSystemServer();
             }
@@ -194,7 +200,7 @@
                 })
                 == 0) {
             try {
-                mMediaRouterService.stopScan(getOrCreateClient());
+                mMediaRouterService.stopScan(mClient);
             } catch (RemoteException ex) {
                 throw ex.rethrowFromSystemServer();
             }
@@ -358,8 +364,7 @@
     @Nullable
     public RoutingSessionInfo getSystemRoutingSession(@Nullable String packageName) {
         try {
-            return mMediaRouterService.getSystemSessionInfoForPackage(
-                    getOrCreateClient(), packageName);
+            return mMediaRouterService.getSystemSessionInfoForPackage(mClient, packageName);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
@@ -424,7 +429,7 @@
     @NonNull
     public List<RoutingSessionInfo> getRemoteSessions() {
         try {
-            return mMediaRouterService.getRemoteSessions(getOrCreateClient());
+            return mMediaRouterService.getRemoteSessions(mClient);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
@@ -512,8 +517,7 @@
 
         try {
             int requestId = mNextRequestId.getAndIncrement();
-            mMediaRouterService.setRouteVolumeWithManager(
-                    getOrCreateClient(), requestId, route, volume);
+            mMediaRouterService.setRouteVolumeWithManager(mClient, requestId, route, volume);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
@@ -540,7 +544,7 @@
         try {
             int requestId = mNextRequestId.getAndIncrement();
             mMediaRouterService.setSessionVolumeWithManager(
-                    getOrCreateClient(), requestId, sessionInfo.getId(), volume);
+                    mClient, requestId, sessionInfo.getId(), volume);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
@@ -765,7 +769,7 @@
         try {
             int requestId = mNextRequestId.getAndIncrement();
             mMediaRouterService.selectRouteWithManager(
-                    getOrCreateClient(), requestId, sessionInfo.getId(), route);
+                    mClient, requestId, sessionInfo.getId(), route);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
@@ -804,7 +808,7 @@
         try {
             int requestId = mNextRequestId.getAndIncrement();
             mMediaRouterService.deselectRouteWithManager(
-                    getOrCreateClient(), requestId, sessionInfo.getId(), route);
+                    mClient, requestId, sessionInfo.getId(), route);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
@@ -825,8 +829,7 @@
 
         try {
             int requestId = mNextRequestId.getAndIncrement();
-            mMediaRouterService.releaseSessionWithManager(
-                    getOrCreateClient(), requestId, sessionInfo.getId());
+            mMediaRouterService.releaseSessionWithManager(mClient, requestId, sessionInfo.getId());
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
@@ -843,7 +846,7 @@
 
         try {
             mMediaRouterService.transferToRouteWithManager(
-                    getOrCreateClient(), requestId, session.getId(), route);
+                    mClient, requestId, session.getId(), route);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
@@ -860,7 +863,7 @@
 
         try {
             mMediaRouterService.requestCreateSessionWithManager(
-                    getOrCreateClient(), requestId, oldSession, route);
+                    mClient, requestId, oldSession, route);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
@@ -906,22 +909,6 @@
                 sessionInfo.getOwnerPackageName());
     }
 
-    private Client getOrCreateClient() {
-        synchronized (sLock) {
-            if (mClient != null) {
-                return mClient;
-            }
-            Client client = new Client();
-            try {
-                mMediaRouterService.registerManager(client, mPackageName);
-                mClient = client;
-                return client;
-            } catch (RemoteException ex) {
-                throw ex.rethrowFromSystemServer();
-            }
-        }
-    }
-
     /**
      * Interface for receiving events about media routing changes.
      */
diff --git a/media/java/android/media/tv/AdResponse.java b/media/java/android/media/tv/AdResponse.java
index 0c20954..08c66ab 100644
--- a/media/java/android/media/tv/AdResponse.java
+++ b/media/java/android/media/tv/AdResponse.java
@@ -25,7 +25,7 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * An advertisement request which can be sent to TV interactive App service to inform AD status.
+ * An advertisement response which can be sent to TV interactive App service to inform AD status.
  */
 public final class AdResponse implements Parcelable {
     /** @hide */
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index 9f92887..a72f34c 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -217,7 +217,7 @@
             case DO_DISPATCH_SURFACE_CHANGED: {
                 SomeArgs args = (SomeArgs) msg.obj;
                 mSessionImpl.dispatchSurfaceChanged(
-                        (Integer) args.arg1, (Integer) args.arg2, (Integer) args.arg3);
+                        (Integer) args.argi1, (Integer) args.argi2, (Integer) args.argi3);
                 args.recycle();
                 break;
             }
diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp
index 4df745f..30d0c35 100644
--- a/native/android/system_fonts.cpp
+++ b/native/android/system_fonts.cpp
@@ -245,32 +245,23 @@
     std::unique_ptr<ASystemFontIterator> ite(new ASystemFontIterator());
 
     std::unordered_set<AFont, FontHasher> fonts;
-    minikin::SystemFonts::getFontMap(
-            [&fonts](const std::vector<std::shared_ptr<minikin::FontCollection>>& collections) {
-                for (const auto& fc : collections) {
-                    for (uint32_t i = 0; i < fc->getFamilyCount(); ++i) {
-                        const auto& family = fc->getFamilyAt(i);
-                        for (uint32_t j = 0; j < family->getNumFonts(); ++j) {
-                            const minikin::Font* font = family->getFont(j);
-
-                            std::optional<std::string> locale;
-                            uint32_t localeId = font->getLocaleListId();
-                            if (localeId != minikin::kEmptyLocaleListId) {
-                                locale.emplace(minikin::getLocaleString(localeId));
-                            }
-                            std::vector<std::pair<uint32_t, float>> axes;
-                            for (const auto& [tag, value] : font->typeface()->GetAxes()) {
-                                axes.push_back(std::make_pair(tag, value));
-                            }
-
-                            fonts.insert(
-                                    {font->typeface()->GetFontPath(), std::move(locale),
-                                     font->style().weight(),
-                                     font->style().slant() == minikin::FontStyle::Slant::ITALIC,
-                                     static_cast<uint32_t>(font->typeface()->GetFontIndex()),
-                                     axes});
-                        }
+    minikin::SystemFonts::getFontSet(
+            [&fonts](const std::vector<std::shared_ptr<minikin::Font>>& fontSet) {
+                for (const auto& font : fontSet) {
+                    std::optional<std::string> locale;
+                    uint32_t localeId = font->getLocaleListId();
+                    if (localeId != minikin::kEmptyLocaleListId) {
+                        locale.emplace(minikin::getLocaleString(localeId));
                     }
+                    std::vector<std::pair<uint32_t, float>> axes;
+                    for (const auto& [tag, value] : font->typeface()->GetAxes()) {
+                        axes.push_back(std::make_pair(tag, value));
+                    }
+
+                    fonts.insert({font->typeface()->GetFontPath(), std::move(locale),
+                                  font->style().weight(),
+                                  font->style().slant() == minikin::FontStyle::Slant::ITALIC,
+                                  static_cast<uint32_t>(font->typeface()->GetFontIndex()), axes});
                 }
             });
 
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
index 35d1323..2aa26e3 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
@@ -20,6 +20,11 @@
     android:layout_height="wrap_content"
     android:layout_width="match_parent"
     android:background="?android:attr/colorBackground"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingTop="@dimen/settingslib_switchbar_margin"
+    android:paddingBottom="@dimen/settingslib_switchbar_margin"
     android:orientation="vertical">
 
     <LinearLayout
@@ -27,7 +32,6 @@
         android:minHeight="@dimen/settingslib_min_switch_bar_height"
         android:layout_height="wrap_content"
         android:layout_width="match_parent"
-        android:layout_margin="@dimen/settingslib_switchbar_margin"
         android:paddingStart="@dimen/settingslib_switchbar_padding_left"
         android:paddingEnd="@dimen/settingslib_switchbar_padding_right">
 
diff --git a/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml b/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml
index cb757d3..9578fcf 100644
--- a/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml
+++ b/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml
@@ -1,6 +1,15 @@
 <component name="ProjectCodeStyleConfiguration">
   <code_scheme name="Project" version="173">
     <JetCodeStyleSettings>
+      <option name="PACKAGES_TO_USE_STAR_IMPORTS">
+        <value />
+      </option>
+      <option name="PACKAGES_IMPORT_LAYOUT">
+        <value>
+          <package name="" alias="false" withSubpackages="true" />
+          <package name="" alias="true" withSubpackages="true" />
+        </value>
+      </option>
       <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
       <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
     </JetCodeStyleSettings>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index 937e594..36361dd 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -17,7 +17,6 @@
 package com.android.settingslib.spa.gallery.page
 
 import android.os.Bundle
-import androidx.compose.foundation.layout.Column
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.navigation.NavType
@@ -28,12 +27,14 @@
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 
+private const val TITLE = "Sample page with arguments"
 private const val STRING_PARAM_NAME = "stringParam"
 private const val INT_PARAM_NAME = "intParam"
 
 object ArgumentPageProvider : SettingsPageProvider {
-    override val name = Destinations.Argument
+    override val name = "Argument"
 
     override val arguments = listOf(
         navArgument(STRING_PARAM_NAME) { type = NavType.StringType },
@@ -51,17 +52,17 @@
     @Composable
     fun EntryItem(stringParam: String, intParam: Int) {
         Preference(object : PreferenceModel {
-            override val title = "Sample page with arguments"
+            override val title = TITLE
             override val summary =
                 "$STRING_PARAM_NAME=$stringParam, $INT_PARAM_NAME=$intParam".toState()
-            override val onClick = navigator("${Destinations.Argument}/$stringParam/$intParam")
+            override val onClick = navigator("$name/$stringParam/$intParam")
         })
     }
 }
 
 @Composable
 fun ArgumentPage(stringParam: String, intParam: Int) {
-    Column {
+    RegularScaffold(title = TITLE) {
         Preference(object : PreferenceModel {
             override val title = "String param value"
             override val summary = stringParam.toState()
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
index 143c365..82005ec 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
@@ -17,12 +17,8 @@
 package com.android.settingslib.spa.gallery.page
 
 import android.os.Bundle
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.api.SettingsPageProvider
 import com.android.settingslib.spa.framework.compose.navigator
@@ -30,8 +26,11 @@
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import com.android.settingslib.spa.widget.ui.Footer
 
+private const val TITLE = "Sample Footer"
+
 object FooterPageProvider : SettingsPageProvider {
     override val name = "Footer"
 
@@ -43,7 +42,7 @@
     @Composable
     fun EntryItem() {
         Preference(object : PreferenceModel {
-            override val title = "Sample Footer"
+            override val title = TITLE
             override val onClick = navigator(name)
         })
     }
@@ -51,7 +50,7 @@
 
 @Composable
 private fun FooterPage() {
-    Column(Modifier.verticalScroll(rememberScrollState())) {
+    RegularScaffold(title = TITLE) {
         Preference(remember {
             object : PreferenceModel {
                 override val title = "Some Preference"
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt
index ee077f4..6b7de8d 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt
@@ -31,7 +31,7 @@
 import com.android.settingslib.spa.gallery.R
 
 object HomePageProvider : SettingsPageProvider {
-    override val name = Destinations.Home
+    override val name = "Home"
 
     @Composable
     override fun Page(arguments: Bundle?) {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt
index 6465225..cbfc603 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt
@@ -18,14 +18,6 @@
 
 import com.android.settingslib.spa.framework.api.SettingsPageRepository
 
-object Destinations {
-    const val Home = "Home"
-    const val Preference = "Preference"
-    const val SwitchPreference = "SwitchPreference"
-    const val Argument = "Argument"
-    const val Slider = "Slider"
-}
-
 val galleryPageRepository = SettingsPageRepository(
     allPages = listOf(
         HomePageProvider,
@@ -36,5 +28,5 @@
         SettingsPagerPageProvider,
         FooterPageProvider,
     ),
-    startDestination = Destinations.Home,
+    startDestination = HomePageProvider.name,
 )
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt
index 8a29d35..0463e58 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt
@@ -17,9 +17,6 @@
 package com.android.settingslib.spa.gallery.page
 
 import android.os.Bundle
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.DisabledByDefault
 import androidx.compose.material.icons.outlined.TouchApp
@@ -31,7 +28,6 @@
 import androidx.compose.runtime.produceState
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.api.SettingsPageProvider
 import com.android.settingslib.spa.framework.compose.navigator
@@ -39,11 +35,14 @@
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import com.android.settingslib.spa.widget.ui.SettingsIcon
 import kotlinx.coroutines.delay
 
+private const val TITLE = "Sample Preference"
+
 object PreferencePageProvider : SettingsPageProvider {
-    override val name = Destinations.Preference
+    override val name = "Preference"
 
     @Composable
     override fun Page(arguments: Bundle?) {
@@ -53,15 +52,15 @@
     @Composable
     fun EntryItem() {
         Preference(object : PreferenceModel {
-            override val title = "Sample Preference"
-            override val onClick = navigator(Destinations.Preference)
+            override val title = TITLE
+            override val onClick = navigator(name)
         })
     }
 }
 
 @Composable
 private fun PreferencePage() {
-    Column(Modifier.verticalScroll(rememberScrollState())) {
+    RegularScaffold(title = TITLE) {
         Preference(object : PreferenceModel {
             override val title = "Preference"
         })
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
index 5351ea6..df48517 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
@@ -17,11 +17,7 @@
 package com.android.settingslib.spa.gallery.page
 
 import android.os.Bundle
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.api.SettingsPageProvider
 import com.android.settingslib.spa.framework.compose.navigator
@@ -29,7 +25,10 @@
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.scaffold.SettingsPager
-import com.android.settingslib.spa.widget.ui.SettingsTitle
+import com.android.settingslib.spa.widget.scaffold.SettingsScaffold
+import com.android.settingslib.spa.widget.ui.PlaceholderTitle
+
+private const val TITLE = "Sample SettingsPager"
 
 object SettingsPagerPageProvider : SettingsPageProvider {
     override val name = "SettingsPager"
@@ -42,7 +41,7 @@
     @Composable
     fun EntryItem() {
         Preference(object : PreferenceModel {
-            override val title = "Sample SettingsPager"
+            override val title = TITLE
             override val onClick = navigator(name)
         })
     }
@@ -50,9 +49,9 @@
 
 @Composable
 private fun SettingsPagerPage() {
-    SettingsPager(listOf("Personal", "Work")) {
-        Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
-            SettingsTitle(title = "Page $it")
+    SettingsScaffold(title = TITLE) {
+        SettingsPager(listOf("Personal", "Work")) {
+            PlaceholderTitle("Page $it")
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
index 9bcac1b..04046fa 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
@@ -17,9 +17,6 @@
 package com.android.settingslib.spa.gallery.page
 
 import android.os.Bundle
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.AccessAlarm
 import androidx.compose.material.icons.outlined.MusicNote
@@ -29,18 +26,20 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.api.SettingsPageProvider
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import com.android.settingslib.spa.widget.ui.SettingsSlider
 import com.android.settingslib.spa.widget.ui.SettingsSliderModel
 
+private const val TITLE = "Sample Slider"
+
 object SliderPageProvider : SettingsPageProvider {
-    override val name = Destinations.Slider
+    override val name = "Slider"
 
     @Composable
     override fun Page(arguments: Bundle?) {
@@ -50,15 +49,15 @@
     @Composable
     fun EntryItem() {
         Preference(object : PreferenceModel {
-            override val title = "Sample Slider"
-            override val onClick = navigator(Destinations.Slider)
+            override val title = TITLE
+            override val onClick = navigator(name)
         })
     }
 }
 
 @Composable
 private fun SliderPage() {
-    Column(Modifier.verticalScroll(rememberScrollState())) {
+    RegularScaffold(title = TITLE) {
         SettingsSlider(object : SettingsSliderModel {
             override val title = "Slider"
             override val initValue = 40
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt
index b6f7258..e9e5d35 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt
@@ -17,15 +17,11 @@
 package com.android.settingslib.spa.gallery.page
 
 import android.os.Bundle
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.produceState
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.api.SettingsPageProvider
 import com.android.settingslib.spa.framework.compose.navigator
@@ -35,10 +31,13 @@
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.preference.SwitchPreference
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import kotlinx.coroutines.delay
 
+private const val TITLE = "Sample SwitchPreference"
+
 object SwitchPreferencePageProvider : SettingsPageProvider {
-    override val name = Destinations.SwitchPreference
+    override val name = "SwitchPreference"
 
     @Composable
     override fun Page(arguments: Bundle?) {
@@ -48,15 +47,15 @@
     @Composable
     fun EntryItem() {
         Preference(object : PreferenceModel {
-            override val title = "Sample SwitchPreference"
-            override val onClick = navigator(Destinations.SwitchPreference)
+            override val title = TITLE
+            override val onClick = navigator(name)
         })
     }
 }
 
 @Composable
 private fun SwitchPreferencePage() {
-    Column(Modifier.verticalScroll(rememberScrollState())) {
+    RegularScaffold(title = TITLE) {
         SampleSwitchPreference()
         SampleSwitchPreferenceWithSummary()
         SampleSwitchPreferenceWithAsyncSummary()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/LogCompositions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/LogCompositions.kt
new file mode 100644
index 0000000..4eef2a8
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/LogCompositions.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.compose
+
+import android.util.Log
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.remember
+
+const val ENABLE_LOG_COMPOSITIONS = false
+
+data class LogCompositionsRef(var count: Int)
+
+// Note the inline function below which ensures that this function is essentially
+// copied at the call site to ensure that its logging only recompositions from the
+// original call site.
+@Suppress("NOTHING_TO_INLINE")
+@Composable
+inline fun LogCompositions(tag: String, msg: String) {
+    if (ENABLE_LOG_COMPOSITIONS) {
+        val ref = remember { LogCompositionsRef(0) }
+        SideEffect { ref.count++ }
+        Log.d(tag, "Compositions $msg: ${ref.count}")
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
new file mode 100644
index 0000000..bf33857
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.compose
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.calculateEndPadding
+import androidx.compose.foundation.layout.calculateStartPadding
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.drop
+import kotlinx.coroutines.flow.filter
+
+/**
+ * *************************************************************************************************
+ * This file was forked from
+ * https://github.com/google/accompanist/blob/main/pager/src/main/java/com/google/accompanist/pager/Pager.kt
+ * and will be removed once it lands in AndroidX.
+ */
+
+/**
+ * A horizontally scrolling layout that allows users to flip between items to the left and right.
+ *
+ * @sample com.google.accompanist.sample.pager.HorizontalPagerSample
+ *
+ * @param count the number of pages.
+ * @param modifier the modifier to apply to this layout.
+ * @param state the state object to be used to control or observe the pager's state.
+ * @param reverseLayout reverse the direction of scrolling and layout, when `true` items will be
+ * composed from the end to the start and [PagerState.currentPage] == 0 will mean
+ * the first item is located at the end.
+ * @param itemSpacing horizontal spacing to add between items.
+ * @param key the scroll position will be maintained based on the key, which means if you
+ * add/remove items before the current visible item the item with the given key will be kept as the
+ * first visible one.
+ * @param content a block which describes the content. Inside this block you can reference
+ * [PagerScope.currentPage] and other properties in [PagerScope].
+ */
+@Composable
+fun HorizontalPager(
+    count: Int,
+    modifier: Modifier = Modifier,
+    state: PagerState = rememberPagerState(),
+    reverseLayout: Boolean = false,
+    itemSpacing: Dp = 0.dp,
+    contentPadding: PaddingValues = PaddingValues(0.dp),
+    verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
+    key: ((page: Int) -> Any)? = null,
+    content: @Composable PagerScope.(page: Int) -> Unit,
+) {
+    Pager(
+        count = count,
+        state = state,
+        modifier = modifier,
+        isVertical = false,
+        reverseLayout = reverseLayout,
+        itemSpacing = itemSpacing,
+        verticalAlignment = verticalAlignment,
+        key = key,
+        contentPadding = contentPadding,
+        content = content
+    )
+}
+
+/**
+ * A vertically scrolling layout that allows users to flip between items to the top and bottom.
+ *
+ * @sample com.google.accompanist.sample.pager.VerticalPagerSample
+ *
+ * @param count the number of pages.
+ * @param modifier the modifier to apply to this layout.
+ * @param state the state object to be used to control or observe the pager's state.
+ * @param reverseLayout reverse the direction of scrolling and layout, when `true` items will be
+ * composed from the bottom to the top and [PagerState.currentPage] == 0 will mean
+ * the first item is located at the bottom.
+ * @param itemSpacing vertical spacing to add between items.
+ * @param key the scroll position will be maintained based on the key, which means if you
+ * add/remove items before the current visible item the item with the given key will be kept as the
+ * first visible one.
+ * @param content a block which describes the content. Inside this block you can reference
+ * [PagerScope.currentPage] and other properties in [PagerScope].
+ */
+@Composable
+fun VerticalPager(
+    count: Int,
+    modifier: Modifier = Modifier,
+    state: PagerState = rememberPagerState(),
+    reverseLayout: Boolean = false,
+    itemSpacing: Dp = 0.dp,
+    contentPadding: PaddingValues = PaddingValues(0.dp),
+    horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
+    key: ((page: Int) -> Any)? = null,
+    content: @Composable() (PagerScope.(page: Int) -> Unit),
+) {
+    Pager(
+        count = count,
+        state = state,
+        modifier = modifier,
+        isVertical = true,
+        reverseLayout = reverseLayout,
+        itemSpacing = itemSpacing,
+        horizontalAlignment = horizontalAlignment,
+        key = key,
+        contentPadding = contentPadding,
+        content = content
+    )
+}
+
+@Composable
+internal fun Pager(
+    count: Int,
+    modifier: Modifier,
+    state: PagerState,
+    reverseLayout: Boolean,
+    itemSpacing: Dp,
+    isVertical: Boolean,
+    key: ((page: Int) -> Any)?,
+    contentPadding: PaddingValues,
+    verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
+    horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
+    content: @Composable PagerScope.(page: Int) -> Unit,
+) {
+    require(count >= 0) { "pageCount must be >= 0" }
+
+    LaunchedEffect(count) {
+        state.currentPage = minOf(count - 1, state.currentPage).coerceAtLeast(0)
+    }
+
+    // Once a fling (scroll) has finished, notify the state
+    LaunchedEffect(state) {
+        // When a 'scroll' has finished, notify the state
+        snapshotFlow { state.isScrollInProgress }
+            .filter { !it }
+            // initially isScrollInProgress is false as well and we want to start receiving
+            // the events only after the real scroll happens.
+            .drop(1)
+            .collect { state.onScrollFinished() }
+    }
+    LaunchedEffect(state) {
+        snapshotFlow { state.mostVisiblePageLayoutInfo?.index }
+            .distinctUntilChanged()
+            .collect { state.updateCurrentPageBasedOnLazyListState() }
+    }
+    val density = LocalDensity.current
+    val layoutDirection = LocalLayoutDirection.current
+    LaunchedEffect(density, contentPadding, isVertical, layoutDirection, reverseLayout, state) {
+        with(density) {
+            // this should be exposed on LazyListLayoutInfo instead. b/200920410
+            state.afterContentPadding = if (isVertical) {
+                if (!reverseLayout) {
+                    contentPadding.calculateBottomPadding()
+                } else {
+                    contentPadding.calculateTopPadding()
+                }
+            } else {
+                if (!reverseLayout) {
+                    contentPadding.calculateEndPadding(layoutDirection)
+                } else {
+                    contentPadding.calculateStartPadding(layoutDirection)
+                }
+            }.roundToPx()
+        }
+    }
+
+    val pagerScope = remember(state) { PagerScopeImpl(state) }
+
+    // We only consume nested flings in the main-axis, allowing cross-axis flings to propagate
+    // as normal
+    val consumeFlingNestedScrollConnection = remember(isVertical) {
+        ConsumeFlingNestedScrollConnection(
+            consumeHorizontal = !isVertical,
+            consumeVertical = isVertical,
+        )
+    }
+
+    if (isVertical) {
+        LazyColumn(
+            state = state.lazyListState,
+            verticalArrangement = Arrangement.spacedBy(itemSpacing, verticalAlignment),
+            horizontalAlignment = horizontalAlignment,
+            reverseLayout = reverseLayout,
+            contentPadding = contentPadding,
+            modifier = modifier,
+        ) {
+            items(
+                count = count,
+                key = key,
+            ) { page ->
+                Box(
+                    Modifier
+                        // We don't any nested flings to continue in the pager, so we add a
+                        // connection which consumes them.
+                        // See: https://github.com/google/accompanist/issues/347
+                        .nestedScroll(connection = consumeFlingNestedScrollConnection)
+                        // Constraint the content height to be <= than the height of the pager.
+                        .fillParentMaxHeight()
+                        .wrapContentSize()
+                ) {
+                    pagerScope.content(page)
+                }
+            }
+        }
+    } else {
+        LazyRow(
+            state = state.lazyListState,
+            verticalAlignment = verticalAlignment,
+            horizontalArrangement = Arrangement.spacedBy(itemSpacing, horizontalAlignment),
+            reverseLayout = reverseLayout,
+            contentPadding = contentPadding,
+            modifier = modifier,
+        ) {
+            items(
+                count = count,
+                key = key,
+            ) { page ->
+                Box(
+                    Modifier
+                        // We don't any nested flings to continue in the pager, so we add a
+                        // connection which consumes them.
+                        // See: https://github.com/google/accompanist/issues/347
+                        .nestedScroll(connection = consumeFlingNestedScrollConnection)
+                        // Constraint the content width to be <= than the width of the pager.
+                        .fillParentMaxWidth()
+                        .wrapContentSize()
+                ) {
+                    pagerScope.content(page)
+                }
+            }
+        }
+    }
+}
+
+private class ConsumeFlingNestedScrollConnection(
+    private val consumeHorizontal: Boolean,
+    private val consumeVertical: Boolean,
+) : NestedScrollConnection {
+    override fun onPostScroll(
+        consumed: Offset,
+        available: Offset,
+        source: NestedScrollSource
+    ): Offset = when (source) {
+        // We can consume all resting fling scrolls so that they don't propagate up to the
+        // Pager
+        NestedScrollSource.Fling -> available.consume(consumeHorizontal, consumeVertical)
+        else -> Offset.Zero
+    }
+
+    override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+        // We can consume all post fling velocity on the main-axis
+        // so that it doesn't propagate up to the Pager
+        return available.consume(consumeHorizontal, consumeVertical)
+    }
+}
+
+private fun Offset.consume(
+    consumeHorizontal: Boolean,
+    consumeVertical: Boolean,
+): Offset = Offset(
+    x = if (consumeHorizontal) this.x else 0f,
+    y = if (consumeVertical) this.y else 0f,
+)
+
+private fun Velocity.consume(
+    consumeHorizontal: Boolean,
+    consumeVertical: Boolean,
+): Velocity = Velocity(
+    x = if (consumeHorizontal) this.x else 0f,
+    y = if (consumeVertical) this.y else 0f,
+)
+
+/**
+ * Scope for [HorizontalPager] content.
+ */
+@Stable
+interface PagerScope {
+    /**
+     * Returns the current selected page
+     */
+    val currentPage: Int
+
+    /**
+     * The current offset from the start of [currentPage], as a ratio of the page width.
+     */
+    val currentPageOffset: Float
+}
+
+private class PagerScopeImpl(
+    private val state: PagerState,
+) : PagerScope {
+    override val currentPage: Int get() = state.currentPage
+    override val currentPageOffset: Float get() = state.currentPageOffset
+}
+
+/**
+ * Calculate the offset for the given [page] from the current scroll position. This is useful
+ * when using the scroll position to apply effects or animations to items.
+ *
+ * The returned offset can positive or negative, depending on whether which direction the [page] is
+ * compared to the current scroll position.
+ *
+ * @sample com.google.accompanist.sample.pager.HorizontalPagerWithOffsetTransition
+ */
+fun PagerScope.calculateCurrentOffsetForPage(page: Int): Float {
+    return (currentPage - page) + currentPageOffset
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt
new file mode 100644
index 0000000..21ba117
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.compose
+
+import androidx.annotation.FloatRange
+import androidx.annotation.IntRange
+import androidx.compose.foundation.MutatePriority
+import androidx.compose.foundation.gestures.ScrollScope
+import androidx.compose.foundation.gestures.ScrollableState
+import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.foundation.lazy.LazyListItemInfo
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.listSaver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import kotlin.math.abs
+import kotlin.math.absoluteValue
+import kotlin.math.roundToInt
+
+/**
+ * *************************************************************************************************
+ * This file was forked from
+ * https://github.com/google/accompanist/blob/main/pager/src/main/java/com/google/accompanist/pager/PagerState.kt
+ * and will be removed once it lands in AndroidX.
+ */
+
+/**
+ * Creates a [PagerState] that is remembered across compositions.
+ *
+ * Changes to the provided values for [initialPage] will **not** result in the state being
+ * recreated or changed in any way if it has already
+ * been created.
+ *
+ * @param initialPage the initial value for [PagerState.currentPage]
+ */
+@Composable
+fun rememberPagerState(
+    @IntRange(from = 0) initialPage: Int = 0,
+): PagerState = rememberSaveable(saver = PagerState.Saver) {
+    PagerState(
+        currentPage = initialPage,
+    )
+}
+
+/**
+ * A state object that can be hoisted to control and observe scrolling for [HorizontalPager].
+ *
+ * In most cases, this will be created via [rememberPagerState].
+ *
+ * @param currentPage the initial value for [PagerState.currentPage]
+ */
+@Stable
+class PagerState(
+    @IntRange(from = 0) currentPage: Int = 0,
+) : ScrollableState {
+    // Should this be public?
+    internal val lazyListState = LazyListState(firstVisibleItemIndex = currentPage)
+
+    private var _currentPage by mutableStateOf(currentPage)
+
+    // finds the page which has larger visible area within the viewport not including paddings
+    internal val mostVisiblePageLayoutInfo: LazyListItemInfo?
+        get() {
+            val layoutInfo = lazyListState.layoutInfo
+            return layoutInfo.visibleItemsInfo.maxByOrNull {
+                val start = maxOf(it.offset, 0)
+                val end = minOf(
+                    it.offset + it.size, layoutInfo.viewportEndOffset - afterContentPadding)
+                end - start
+            }
+        }
+
+    internal var afterContentPadding = 0
+
+    private val currentPageLayoutInfo: LazyListItemInfo?
+        get() = lazyListState.layoutInfo.visibleItemsInfo.lastOrNull {
+            it.index == currentPage
+        }
+
+    /**
+     * [InteractionSource] that will be used to dispatch drag events when this
+     * list is being dragged. If you want to know whether the fling (or animated scroll) is in
+     * progress, use [isScrollInProgress].
+     */
+    val interactionSource: InteractionSource
+        get() = lazyListState.interactionSource
+
+    /**
+     * The number of pages to display.
+     */
+    @get:IntRange(from = 0)
+    val pageCount: Int by derivedStateOf {
+        lazyListState.layoutInfo.totalItemsCount
+    }
+
+    /**
+     * The index of the currently selected page. This may not be the page which is
+     * currently displayed on screen.
+     *
+     * To update the scroll position, use [scrollToPage] or [animateScrollToPage].
+     */
+    @get:IntRange(from = 0)
+    var currentPage: Int
+        get() = _currentPage
+        internal set(value) {
+            if (value != _currentPage) {
+                _currentPage = value
+            }
+        }
+
+    /**
+     * The current offset from the start of [currentPage], as a ratio of the page width.
+     *
+     * To update the scroll position, use [scrollToPage] or [animateScrollToPage].
+     */
+    val currentPageOffset: Float by derivedStateOf {
+        currentPageLayoutInfo?.let {
+            // We coerce since itemSpacing can make the offset > 1f.
+            // We don't want to count spacing in the offset so cap it to 1f
+            (-it.offset / it.size.toFloat()).coerceIn(-1f, 1f)
+        } ?: 0f
+    }
+
+    /**
+     * The target page for any on-going animations.
+     */
+    private var animationTargetPage: Int? by mutableStateOf(null)
+
+    /**
+     * Animate (smooth scroll) to the given page to the middle of the viewport.
+     *
+     * Cancels the currently running scroll, if any, and suspends until the cancellation is
+     * complete.
+     *
+     * @param page the page to animate to. Must be >= 0.
+     * @param pageOffset the percentage of the page size to offset, from the start of [page].
+     * Must be in the range -1f..1f.
+     */
+    suspend fun animateScrollToPage(
+        @IntRange(from = 0) page: Int,
+        @FloatRange(from = -1.0, to = 1.0) pageOffset: Float = 0f,
+    ) {
+        requireCurrentPage(page, "page")
+        requireCurrentPageOffset(pageOffset, "pageOffset")
+        try {
+            animationTargetPage = page
+
+            // pre-jump to nearby item for long jumps as an optimization
+            // the same trick is done in ViewPager2
+            val oldPage = lazyListState.firstVisibleItemIndex
+            if (abs(page - oldPage) > 3) {
+                lazyListState.scrollToItem(if (page > oldPage) page - 3 else page + 3)
+            }
+
+            if (pageOffset.absoluteValue <= 0.005f) {
+                // If the offset is (close to) zero, just call animateScrollToItem and we're done
+                lazyListState.animateScrollToItem(index = page)
+            } else {
+                // Else we need to figure out what the offset is in pixels...
+                lazyListState.scroll { } // this will await for the first layout.
+                val layoutInfo = lazyListState.layoutInfo
+                var target = layoutInfo.visibleItemsInfo
+                    .firstOrNull { it.index == page }
+
+                if (target != null) {
+                    // If we have access to the target page layout, we can calculate the pixel
+                    // offset from the size
+                    lazyListState.animateScrollToItem(
+                        index = page,
+                        scrollOffset = (target.size * pageOffset).roundToInt()
+                    )
+                } else if (layoutInfo.visibleItemsInfo.isNotEmpty()) {
+                    // If we don't, we use the current page size as a guide
+                    val currentSize = layoutInfo.visibleItemsInfo.first().size
+                    lazyListState.animateScrollToItem(
+                        index = page,
+                        scrollOffset = (currentSize * pageOffset).roundToInt()
+                    )
+
+                    // The target should be visible now
+                    target = lazyListState.layoutInfo.visibleItemsInfo.firstOrNull {
+                        it.index == page
+                    }
+
+                    if (target != null && target.size != currentSize) {
+                        // If the size we used for calculating the offset differs from the actual
+                        // target page size, we need to scroll again. This doesn't look great,
+                        // but there's not much else we can do.
+                        lazyListState.animateScrollToItem(
+                            index = page,
+                            scrollOffset = (target.size * pageOffset).roundToInt()
+                        )
+                    }
+                }
+            }
+        } finally {
+            // We need to manually call this, as the `animateScrollToItem` call above will happen
+            // in 1 frame, which is usually too fast for the LaunchedEffect in Pager to detect
+            // the change. This is especially true when running unit tests.
+            onScrollFinished()
+        }
+    }
+
+    /**
+     * Instantly brings the item at [page] to the middle of the viewport.
+     *
+     * Cancels the currently running scroll, if any, and suspends until the cancellation is
+     * complete.
+     *
+     * @param page the page to snap to. Must be >= 0.
+     * @param pageOffset the percentage of the page size to offset, from the start of [page].
+     * Must be in the range -1f..1f.
+     */
+    suspend fun scrollToPage(
+        @IntRange(from = 0) page: Int,
+        @FloatRange(from = -1.0, to = 1.0) pageOffset: Float = 0f,
+    ) {
+        requireCurrentPage(page, "page")
+        requireCurrentPageOffset(pageOffset, "pageOffset")
+        try {
+            animationTargetPage = page
+
+            // First scroll to the given page. It will now be laid out at offset 0
+            lazyListState.scrollToItem(index = page)
+            updateCurrentPageBasedOnLazyListState()
+
+            // If we have a start spacing, we need to offset (scroll) by that too
+            if (pageOffset.absoluteValue > 0.0001f) {
+                currentPageLayoutInfo?.let {
+                    scroll {
+                        scrollBy(it.size * pageOffset)
+                    }
+                }
+            }
+        } finally {
+            // We need to manually call this, as the `scroll` call above will happen in 1 frame,
+            // which is usually too fast for the LaunchedEffect in Pager to detect the change.
+            // This is especially true when running unit tests.
+            onScrollFinished()
+        }
+    }
+
+    internal fun updateCurrentPageBasedOnLazyListState() {
+        // Then update the current page to our layout page
+        mostVisiblePageLayoutInfo?.let {
+            currentPage = it.index
+        }
+    }
+
+    internal fun onScrollFinished() {
+        // Clear the animation target page
+        animationTargetPage = null
+    }
+
+    override suspend fun scroll(
+        scrollPriority: MutatePriority,
+        block: suspend ScrollScope.() -> Unit
+    ) = lazyListState.scroll(scrollPriority, block)
+
+    override fun dispatchRawDelta(delta: Float): Float {
+        return lazyListState.dispatchRawDelta(delta)
+    }
+
+    override val isScrollInProgress: Boolean
+        get() = lazyListState.isScrollInProgress
+
+    override fun toString(): String = "PagerState(" +
+        "pageCount=$pageCount, " +
+        "currentPage=$currentPage, " +
+        "currentPageOffset=$currentPageOffset" +
+        ")"
+
+    private fun requireCurrentPage(value: Int, name: String) {
+        require(value >= 0) { "$name[$value] must be >= 0" }
+    }
+
+    private fun requireCurrentPageOffset(value: Float, name: String) {
+        require(value in -1f..1f) { "$name must be >= 0 and <= 1" }
+    }
+
+    companion object {
+        /**
+         * The default [Saver] implementation for [PagerState].
+         */
+        val Saver: Saver<PagerState, *> = listSaver(
+            save = {
+                listOf<Any>(
+                    it.currentPage,
+                )
+            },
+            restore = {
+                PagerState(
+                    currentPage = it[0] as Int,
+                )
+            }
+        )
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index e1ca69b..9654368 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -31,4 +31,11 @@
         end = itemPaddingEnd,
         bottom = itemPaddingVertical,
     )
+    val itemPaddingAround = 8.dp
+
+    /** The size when app icon is displayed in list. */
+    val appIconItemSize = 32.dp
+
+    /** The size when app icon is displayed in App info page. */
+    val appIconInfoSize = 48.dp
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Collections.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Collections.kt
new file mode 100644
index 0000000..ba25336
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Collections.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.coroutineScope
+
+suspend inline fun <R, T> Iterable<T>.asyncMap(crossinline transform: (T) -> R): List<R> =
+    coroutineScope {
+        map { item ->
+            async { transform(item) }
+        }.awaitAll()
+    }
+
+suspend inline fun <T> Iterable<T>.asyncFilter(crossinline predicate: (T) -> Boolean): List<T> =
+    asyncMap { item -> item to predicate(item) }
+        .filter { it.second }
+        .map { it.first }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
new file mode 100644
index 0000000..999d8d7
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.State
+import androidx.compose.runtime.snapshotFlow
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+
+inline fun <T, R> Flow<List<T>>.asyncMapItem(crossinline transform: (T) -> R): Flow<List<R>> =
+    map { list -> list.asyncMap(transform) }
+
+@OptIn(ExperimentalCoroutinesApi::class)
+inline fun <T, R> Flow<T>.mapState(crossinline block: (T) -> State<R>): Flow<R> =
+    flatMapLatest { snapshotFlow { block(it).value } }
+
+fun <T1, T2> Flow<T1>.waitFirst(flow: Flow<T2>): Flow<T1> =
+    combine(flow.distinctUntilChangedBy {}) { value, _ -> value }
+
+class StateFlowBridge<T> {
+    private val stateFlow = MutableStateFlow<T?>(null)
+    val flow = stateFlow.filterNotNull()
+
+    fun setIfAbsent(value: T) {
+        if (stateFlow.value == null) {
+            stateFlow.value = value
+        }
+    }
+
+    @Composable
+    fun Sync(state: State<T>) {
+        LaunchedEffect(state.value) {
+            stateFlow.value = state.value
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
new file mode 100644
index 0000000..c960254
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.scaffold
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.ArrowBack
+import androidx.compose.material.icons.outlined.MoreVert
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.android.settingslib.spa.framework.compose.LocalNavController
+
+@Composable
+internal fun NavigateUp() {
+    val navController = LocalNavController.current
+    val contentDescription = stringResource(
+        id = androidx.appcompat.R.string.abc_action_bar_up_description,
+    )
+    BackAction(contentDescription) {
+        navController.navigateUp()
+    }
+}
+
+@Composable
+private fun BackAction(contentDescription: String, onClick: () -> Unit) {
+    IconButton(onClick) {
+        Icon(
+            imageVector = Icons.Outlined.ArrowBack,
+            contentDescription = contentDescription,
+        )
+    }
+}
+
+@Composable
+fun MoreOptionsAction(onClick: () -> Unit) {
+    IconButton(onClick) {
+        Icon(
+            imageVector = Icons.Outlined.MoreVert,
+            contentDescription = stringResource(
+                id = androidx.appcompat.R.string.abc_action_menu_overflow_description,
+            )
+        )
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt
new file mode 100644
index 0000000..9a17b2a
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+/**
+ * A [Scaffold] which content is scrollable and wrapped in a [Column].
+ *
+ * For example, this is for the pages with some preferences and is scrollable when the items out of
+ * the screen.
+ */
+@Composable
+fun RegularScaffold(
+    title: String,
+    actions: @Composable RowScope.() -> Unit = {},
+    content: @Composable () -> Unit,
+) {
+    SettingsScaffold(title, actions) { paddingValues ->
+        Column(Modifier.verticalScroll(rememberScrollState())) {
+            Spacer(Modifier.padding(paddingValues))
+            content()
+        }
+    }
+}
+
+@Preview
+@Composable
+private fun RegularScaffoldPreview() {
+    SettingsTheme {
+        RegularScaffold(title = "Display") {}
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
index 1ec2390..e0e9b95 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
@@ -20,13 +20,14 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.TabRow
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import com.android.settingslib.spa.framework.compose.HorizontalPager
+import com.android.settingslib.spa.framework.compose.rememberPagerState
 import com.android.settingslib.spa.framework.theme.SettingsDimension
+import kotlin.math.absoluteValue
+import kotlinx.coroutines.launch
 
 @Composable
 fun SettingsPager(titles: List<String>, content: @Composable (page: Int) -> Unit) {
@@ -37,10 +38,11 @@
     }
 
     Column {
-        var currentPage by rememberSaveable { mutableStateOf(0) }
+        val coroutineScope = rememberCoroutineScope()
+        val pagerState = rememberPagerState()
 
         TabRow(
-            selectedTabIndex = currentPage,
+            selectedTabIndex = pagerState.currentPage,
             modifier = Modifier.padding(horizontal = SettingsDimension.itemPaddingEnd),
             containerColor = Color.Transparent,
             indicator = {},
@@ -49,12 +51,19 @@
             titles.forEachIndexed { page, title ->
                 SettingsTab(
                     title = title,
-                    selected = currentPage == page,
-                    onClick = { currentPage = page },
+                    selected = pagerState.currentPage == page,
+                    currentPageOffset = pagerState.currentPageOffset.absoluteValue,
+                    onClick = {
+                        coroutineScope.launch {
+                            pagerState.animateScrollToPage(page)
+                        }
+                    },
                 )
             }
         }
 
-        content(currentPage)
+        HorizontalPager(count = titles.size, state = pagerState) { page ->
+            content(page)
+        }
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
new file mode 100644
index 0000000..ee453f2
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SmallTopAppBar
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+/**
+ * A [Scaffold] which content is can be full screen when needed.
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SettingsScaffold(
+    title: String,
+    actions: @Composable RowScope.() -> Unit = {},
+    content: @Composable (PaddingValues) -> Unit,
+) {
+    Scaffold(
+        topBar = {
+            SmallTopAppBar(
+                title = {
+                    Text(
+                        text = title,
+                        modifier = Modifier.padding(SettingsDimension.itemPaddingAround),
+                    )
+                },
+                navigationIcon = { NavigateUp() },
+                actions = actions,
+                colors = settingsTopAppBarColors(),
+            )
+        },
+        content = content,
+    )
+}
+
+@Composable
+internal fun settingsTopAppBarColors() = TopAppBarDefaults.largeTopAppBarColors(
+    containerColor = SettingsTheme.colorScheme.surfaceHeader,
+    scrolledContainerColor = SettingsTheme.colorScheme.surfaceHeader,
+)
+
+@Preview
+@Composable
+private fun SettingsScaffoldPreview() {
+    SettingsTheme {
+        SettingsScaffold(title = "Display") {}
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt
index 16d8dbc..30a4349 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt
@@ -26,6 +26,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.lerp
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.theme.SettingsShape
@@ -35,8 +36,12 @@
 internal fun SettingsTab(
     title: String,
     selected: Boolean,
+    currentPageOffset: Float,
     onClick: () -> Unit,
 ) {
+    // Shows a color transition during pager scroll.
+    // 0f -> Selected, 1f -> Not selected
+    val colorFraction = if (selected) (currentPageOffset * 2).coerceAtMost(1f) else 1f
     Tab(
         selected = selected,
         onClick = onClick,
@@ -44,29 +49,33 @@
             .height(48.dp)
             .padding(horizontal = 4.dp, vertical = 6.dp)
             .clip(SettingsShape.CornerMedium)
-            .background(color = when {
-                selected -> SettingsTheme.colorScheme.primaryContainer
-                else -> SettingsTheme.colorScheme.surface
-            }),
+            .background(
+                color = lerp(
+                    start = SettingsTheme.colorScheme.primaryContainer,
+                    stop = SettingsTheme.colorScheme.surface,
+                    fraction = colorFraction,
+                ),
+            ),
     ) {
         Text(
             text = title,
             style = MaterialTheme.typography.labelLarge,
-            color = when {
-                selected -> SettingsTheme.colorScheme.onPrimaryContainer
-                else -> SettingsTheme.colorScheme.secondaryText
-            },
+            color = lerp(
+                start = SettingsTheme.colorScheme.onPrimaryContainer,
+                stop = SettingsTheme.colorScheme.secondaryText,
+                fraction = colorFraction,
+            ),
         )
     }
 }
 
 @Preview
 @Composable
-private fun SettingsTabPreview() {
+fun SettingsTabPreview() {
     SettingsTheme {
         Column {
-            SettingsTab(title = "Personal", selected = true) {}
-            SettingsTab(title = "Work", selected = false) {}
+            SettingsTab(title = "Personal", selected = true, currentPageOffset = 0f) {}
+            SettingsTab(title = "Work", selected = false, currentPageOffset = 0f) {}
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
index a414c89..59b413c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
@@ -16,10 +16,14 @@
 
 package com.android.settingslib.spa.widget.ui
 
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
 
 @Composable
 fun SettingsTitle(title: State<String>) {
@@ -50,3 +54,17 @@
         )
     }
 }
+
+@Composable
+fun PlaceholderTitle(title: String) {
+    Box(
+        modifier = Modifier.fillMaxSize(),
+        contentAlignment = Alignment.Center,
+    ) {
+        Text(
+            text = title,
+            color = MaterialTheme.colorScheme.onSurface,
+            style = MaterialTheme.typography.titleLarge,
+        )
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt
index f608e10..0c84eac 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
 import androidx.compose.ui.test.assertIsNotSelected
 import androidx.compose.ui.test.assertIsSelected
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -43,7 +44,7 @@
         composeTestRule.onNodeWithText("Personal").assertIsSelected()
         composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
         composeTestRule.onNodeWithText("Work").assertIsNotSelected()
-        composeTestRule.onNodeWithText("Page 1").assertDoesNotExist()
+        composeTestRule.onNodeWithText("Page 1").assertIsNotDisplayed()
     }
 
     @Test
@@ -55,7 +56,7 @@
         composeTestRule.onNodeWithText("Work").performClick()
 
         composeTestRule.onNodeWithText("Personal").assertIsNotSelected()
-        composeTestRule.onNodeWithText("Page 0").assertDoesNotExist()
+        composeTestRule.onNodeWithText("Page 0").assertIsNotDisplayed()
         composeTestRule.onNodeWithText("Work").assertIsSelected()
         composeTestRule.onNodeWithText("Page 1").assertIsDisplayed()
     }
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
index ecbb219..a6469b5 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -28,5 +28,8 @@
         "SettingsLib",
         "androidx.compose.runtime_runtime",
     ],
-    kotlincflags: ["-Xjvm-default=all"],
+    kotlincflags: [
+        "-Xjvm-default=all",
+        "-Xopt-in=kotlin.RequiresOptIn",
+    ],
 }
diff --git a/packages/SettingsLib/SpaPrivileged/res/values/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values/strings.xml
new file mode 100644
index 0000000..b2302a5
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/res/values/strings.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<resources>
+    <!-- [CHAR LIMIT=25] Text shown when there are no applications to display. -->
+    <string name="no_applications">No apps.</string>
+    <!-- [CHAR LIMIT=NONE] Menu for manage apps to control whether system processes are shown -->
+    <string name="menu_show_system">Show system</string>
+    <!-- [CHAR LIMIT=NONE] Menu for manage apps to control whether system processes are hidden -->
+    <string name="menu_hide_system">Hide system</string>
+    <!-- Preference summary text for an app when it is allowed for a permission. [CHAR LIMIT=45] -->
+    <string name="app_permission_summary_allowed">Allowed</string>
+    <!-- Preference summary text for an app when it is disallowed for a permission. [CHAR LIMIT=45] -->
+    <string name="app_permission_summary_not_allowed">Not allowed</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
new file mode 100644
index 0000000..00eb60b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
@@ -0,0 +1,29 @@
+package com.android.settingslib.spaprivileged.model.app
+
+import android.content.pm.ApplicationInfo
+import android.icu.text.CollationKey
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import kotlinx.coroutines.flow.Flow
+
+data class AppEntry<T : AppRecord>(
+    val record: T,
+    val label: String,
+    val labelCollationKey: CollationKey,
+)
+
+interface AppListModel<T : AppRecord> {
+    fun getSpinnerOptions(): List<String> = emptyList()
+    fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>): Flow<List<T>>
+    fun filter(userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<T>>): Flow<List<T>>
+
+    suspend fun onFirstLoaded(recordList: List<T>) {}
+    fun getComparator(option: Int): Comparator<AppEntry<T>> = compareBy(
+        { it.labelCollationKey },
+        { it.record.app.packageName },
+        { it.record.app.uid },
+    )
+
+    @Composable
+    fun getSummary(option: Int, record: T): State<String>?
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
new file mode 100644
index 0000000..9265158
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.app
+
+import android.app.Application
+import android.content.pm.ApplicationInfo
+import android.content.pm.UserInfo
+import android.icu.text.Collator
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import com.android.settingslib.spa.framework.util.StateFlowBridge
+import com.android.settingslib.spa.framework.util.asyncMapItem
+import com.android.settingslib.spa.framework.util.waitFirst
+import java.util.concurrent.ConcurrentHashMap
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.plus
+
+internal data class AppListData<T : AppRecord>(
+    val appEntries: List<AppEntry<T>>,
+    val option: Int,
+) {
+    fun filter(predicate: (AppEntry<T>) -> Boolean) =
+        AppListData(appEntries.filter(predicate), option)
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+internal class AppListViewModel<T : AppRecord>(
+    application: Application,
+) : AndroidViewModel(application) {
+    val userInfo = StateFlowBridge<UserInfo>()
+    val listModel = StateFlowBridge<AppListModel<T>>()
+    val showSystem = StateFlowBridge<Boolean>()
+    val option = StateFlowBridge<Int>()
+    val searchQuery = StateFlowBridge<String>()
+
+    private val appsRepository = AppsRepository(application)
+    private val appRepository = AppRepositoryImpl(application)
+    private val collator = Collator.getInstance().freeze()
+    private val labelMap = ConcurrentHashMap<String, String>()
+    private val scope = viewModelScope + Dispatchers.Default
+
+    private val userIdFlow = userInfo.flow.map { it.id }
+
+    private val recordListFlow = listModel.flow
+        .flatMapLatest { it.transform(userIdFlow, appsRepository.loadApps(userInfo.flow)) }
+        .shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
+
+    private val systemFilteredFlow = appsRepository.showSystemPredicate(userIdFlow, showSystem.flow)
+        .combine(recordListFlow) { showAppPredicate, recordList ->
+            recordList.filter { showAppPredicate(it.app) }
+        }
+
+    val appListDataFlow = option.flow.flatMapLatest(::filterAndSort)
+        .combine(searchQuery.flow) { appListData, searchQuery ->
+            appListData.filter {
+                it.label.contains(other = searchQuery, ignoreCase = true)
+            }
+        }
+        .shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
+
+    init {
+        scheduleOnFirstLoaded()
+    }
+
+    private fun filterAndSort(option: Int) = listModel.flow.flatMapLatest { listModel ->
+        listModel.filter(userIdFlow, option, systemFilteredFlow)
+            .asyncMapItem { record ->
+                val label = getLabel(record.app)
+                AppEntry(
+                    record = record,
+                    label = label,
+                    labelCollationKey = collator.getCollationKey(label),
+                )
+            }
+            .map { appEntries ->
+                AppListData(
+                    appEntries = appEntries.sortedWith(listModel.getComparator(option)),
+                    option = option,
+                )
+            }
+    }
+
+    private fun scheduleOnFirstLoaded() {
+        recordListFlow
+            .waitFirst(appListDataFlow)
+            .combine(listModel.flow) { recordList, listModel ->
+                listModel.maybePreFetchLabels(recordList)
+                listModel.onFirstLoaded(recordList)
+            }
+            .launchIn(scope)
+    }
+
+    private fun AppListModel<T>.maybePreFetchLabels(recordList: List<T>) {
+        if (getSpinnerOptions().isNotEmpty()) {
+            for (record in recordList) {
+                getLabel(record.app)
+            }
+        }
+    }
+
+    private fun getLabel(app: ApplicationInfo) = labelMap.computeIfAbsent(app.packageName) {
+        appRepository.loadLabel(app)
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
similarity index 96%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppOpsController.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
index 3808f64..c2d85a5 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppOpsController.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spaprivileged.framework.app
+package com.android.settingslib.spaprivileged.model.app
 
 import android.app.AppOpsManager
 import android.app.AppOpsManager.MODE_ALLOWED
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRecord.kt
similarity index 92%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRecord.kt
index 8dde897..2978688 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRecord.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spaprivileged.framework.app
+package com.android.settingslib.spaprivileged.model.app
 
 import android.content.pm.ApplicationInfo
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
similarity index 86%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRepository.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
index a6378ef..34f12af 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spaprivileged.framework.app
+package com.android.settingslib.spaprivileged.model.app
 
 import android.content.Context
 import android.content.pm.ApplicationInfo
@@ -31,6 +31,8 @@
 fun rememberAppRepository(): AppRepository = rememberContext(::AppRepositoryImpl)
 
 interface AppRepository {
+    fun loadLabel(app: ApplicationInfo): String
+
     @Composable
     fun produceLabel(app: ApplicationInfo): State<String>
 
@@ -38,9 +40,11 @@
     fun produceIcon(app: ApplicationInfo): State<Drawable?>
 }
 
-private class AppRepositoryImpl(private val context: Context) : AppRepository {
+internal class AppRepositoryImpl(private val context: Context) : AppRepository {
     private val packageManager = context.packageManager
 
+    override fun loadLabel(app: ApplicationInfo): String = app.loadLabel(packageManager).toString()
+
     @Composable
     override fun produceLabel(app: ApplicationInfo) = produceState(initialValue = "", app) {
         withContext(Dispatchers.Default) {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/Apps.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
similarity index 92%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/Apps.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
index f675545..6e1afd9 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/Apps.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spaprivileged.framework.app
+package com.android.settingslib.spaprivileged.model.app
 
 import android.content.pm.ApplicationInfo
 import android.os.UserHandle
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppsRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppsRepository.kt
new file mode 100644
index 0000000..6a64620
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppsRepository.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.app
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.content.pm.UserInfo
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+class AppsRepository(context: Context) {
+    private val packageManager = context.packageManager
+
+    fun loadApps(userInfoFlow: Flow<UserInfo>): Flow<List<ApplicationInfo>> = userInfoFlow
+        .map { loadApps(it) }
+        .flowOn(Dispatchers.Default)
+
+    private suspend fun loadApps(userInfo: UserInfo): List<ApplicationInfo> {
+        return coroutineScope {
+            val hiddenSystemModulesDeferred = async {
+                packageManager.getInstalledModules(0)
+                    .filter { it.isHidden }
+                    .map { it.packageName }
+                    .toSet()
+            }
+            val flags = PackageManager.ApplicationInfoFlags.of(
+                ((if (userInfo.isAdmin) PackageManager.MATCH_ANY_USER else 0) or
+                    PackageManager.MATCH_DISABLED_COMPONENTS or
+                    PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
+            )
+            val installedApplicationsAsUser =
+                packageManager.getInstalledApplicationsAsUser(flags, userInfo.id)
+
+            val hiddenSystemModules = hiddenSystemModulesDeferred.await()
+            installedApplicationsAsUser.filter { app ->
+                app.isInAppList(hiddenSystemModules)
+            }
+        }
+    }
+
+    fun showSystemPredicate(
+        userIdFlow: Flow<Int>,
+        showSystemFlow: Flow<Boolean>,
+    ): Flow<(app: ApplicationInfo) -> Boolean> =
+        userIdFlow.combine(showSystemFlow) { userId, showSystem ->
+            showSystemPredicate(userId, showSystem)
+        }
+
+    private suspend fun showSystemPredicate(
+        userId: Int,
+        showSystem: Boolean,
+    ): (app: ApplicationInfo) -> Boolean {
+        if (showSystem) return { true }
+        val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId)
+        return { app ->
+            app.isUpdatedSystemApp || !app.isSystemApp || app.packageName in homeOrLauncherPackages
+        }
+    }
+
+    private suspend fun loadHomeOrLauncherPackages(userId: Int): Set<String> {
+        val launchIntent = Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER)
+        // If we do not specify MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE, system will
+        // derive and update the flags according to the user's lock state. When the user is locked,
+        // components with ComponentInfo#directBootAware == false will be filtered. We should
+        // explicitly include both direct boot aware and unaware component here.
+        val flags = PackageManager.ResolveInfoFlags.of(
+            (PackageManager.MATCH_DISABLED_COMPONENTS or
+                PackageManager.MATCH_DIRECT_BOOT_AWARE or
+                PackageManager.MATCH_DIRECT_BOOT_UNAWARE).toLong()
+        )
+        return coroutineScope {
+            val launcherActivities = async {
+                packageManager.queryIntentActivitiesAsUser(launchIntent, flags, userId)
+            }
+            val homeActivities = ArrayList<ResolveInfo>()
+            packageManager.getHomeActivities(homeActivities)
+            (launcherActivities.await() + homeActivities)
+                .map { it.activityInfo.packageName }
+                .toSet()
+        }
+    }
+
+    companion object {
+        private fun ApplicationInfo.isInAppList(hiddenSystemModules: Set<String>) =
+            when {
+                packageName in hiddenSystemModules -> false
+                enabled -> true
+                enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER -> true
+                else -> false
+            }
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
similarity index 94%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
index 66b05da..0cc497a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spaprivileged.framework.app
+package com.android.settingslib.spaprivileged.model.app
 
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageInfo
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/enterprise/EnterpriseRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
similarity index 95%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/enterprise/EnterpriseRepository.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
index ae0cb77..fab3ae8 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/enterprise/EnterpriseRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spaprivileged.framework.enterprise
+package com.android.settingslib.spaprivileged.model.enterprise
 
 import android.app.admin.DevicePolicyManager
 import android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index 5ae514c..99deb70 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -29,37 +29,49 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.compose.rememberDrawablePainter
+import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.widget.ui.SettingsBody
 import com.android.settingslib.spa.widget.ui.SettingsTitle
-import com.android.settingslib.spaprivileged.framework.app.PackageManagers
-import com.android.settingslib.spaprivileged.framework.app.rememberAppRepository
+import com.android.settingslib.spaprivileged.model.app.PackageManagers
+import com.android.settingslib.spaprivileged.model.app.rememberAppRepository
 
 @Composable
 fun AppInfo(packageName: String, userId: Int) {
     Column(
         modifier = Modifier
             .fillMaxWidth()
-            .padding(16.dp),
-        horizontalAlignment = Alignment.CenterHorizontally) {
+            .padding(
+                horizontal = SettingsDimension.itemPaddingStart,
+                vertical = SettingsDimension.itemPaddingVertical,
+            ),
+        horizontalAlignment = Alignment.CenterHorizontally,
+    ) {
         val packageInfo = remember { PackageManagers.getPackageInfoAsUser(packageName, userId) }
-        Box(modifier = Modifier.padding(8.dp)) {
-            AppIcon(app = packageInfo.applicationInfo, size = 48)
+        Box(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) {
+            AppIcon(app = packageInfo.applicationInfo, size = SettingsDimension.appIconInfoSize)
         }
         AppLabel(packageInfo.applicationInfo)
-        Spacer(modifier = Modifier.height(4.dp))
-        SettingsBody(packageInfo.versionName)
+        AppVersion(packageInfo.versionName)
     }
 }
 
 @Composable
-fun AppIcon(app: ApplicationInfo, size: Int) {
+private fun AppVersion(versionName: String?) {
+    if (versionName == null) return
+    Spacer(modifier = Modifier.height(4.dp))
+    SettingsBody(versionName)
+}
+
+@Composable
+fun AppIcon(app: ApplicationInfo, size: Dp) {
     val appRepository = rememberAppRepository()
     Image(
         painter = rememberDrawablePainter(appRepository.produceIcon(app).value),
         contentDescription = null,
-        modifier = Modifier.size(size.dp)
+        modifier = Modifier.size(size)
     )
 }
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
index 06d7547..9b45318 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
@@ -16,15 +16,8 @@
 
 package com.android.settingslib.spaprivileged.template.app
 
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import com.android.settingslib.spa.widget.ui.Footer
 
 @Composable
@@ -35,15 +28,7 @@
     footerText: String,
     content: @Composable () -> Unit,
 ) {
-    // TODO: Replace with SettingsScaffold
-    Column(Modifier.verticalScroll(rememberScrollState())) {
-        Text(
-            text = title,
-            modifier = Modifier.padding(SettingsDimension.itemPadding),
-            color = MaterialTheme.colorScheme.onSurface,
-            style = MaterialTheme.typography.headlineMedium,
-        )
-
+    RegularScaffold(title = title) {
         AppInfo(packageName, userId)
 
         content()
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
new file mode 100644
index 0000000..315dc5d
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.content.pm.UserInfo
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.android.settingslib.spa.framework.compose.LogCompositions
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.ui.PlaceholderTitle
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.app.AppListData
+import com.android.settingslib.spaprivileged.model.app.AppListModel
+import com.android.settingslib.spaprivileged.model.app.AppListViewModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import kotlinx.coroutines.Dispatchers
+
+private const val TAG = "AppList"
+
+@Composable
+internal fun <T : AppRecord> AppList(
+    userInfo: UserInfo,
+    listModel: AppListModel<T>,
+    showSystem: State<Boolean>,
+    option: State<Int>,
+    searchQuery: State<String>,
+    appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
+) {
+    LogCompositions(TAG, userInfo.id.toString())
+    val appListData = loadAppEntries(userInfo, listModel, showSystem, option, searchQuery)
+    AppListWidget(appListData, listModel, appItem)
+}
+
+@Composable
+private fun <T : AppRecord> AppListWidget(
+    appListData: State<AppListData<T>?>,
+    listModel: AppListModel<T>,
+    appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
+) {
+    appListData.value?.let { (list, option) ->
+        if (list.isEmpty()) {
+            PlaceholderTitle(stringResource(R.string.no_applications))
+            return
+        }
+        LazyColumn(
+            modifier = Modifier.fillMaxSize(),
+            state = rememberLazyListState(),
+            contentPadding = PaddingValues(bottom = SettingsDimension.itemPaddingVertical),
+        ) {
+            items(count = list.size, key = { option to list[it].record.app.packageName }) {
+                val appEntry = list[it]
+                val summary = listModel.getSummary(option, appEntry.record) ?: "".toState()
+                val itemModel = remember(appEntry) {
+                    AppListItemModel(appEntry.record, appEntry.label, summary)
+                }
+                appItem(itemModel)
+            }
+        }
+    }
+}
+
+@Composable
+private fun <T : AppRecord> loadAppEntries(
+    userInfo: UserInfo,
+    listModel: AppListModel<T>,
+    showSystem: State<Boolean>,
+    option: State<Int>,
+    searchQuery: State<String>,
+): State<AppListData<T>?> {
+    val viewModel: AppListViewModel<T> = viewModel(key = userInfo.id.toString())
+    viewModel.userInfo.setIfAbsent(userInfo)
+    viewModel.listModel.setIfAbsent(listModel)
+    viewModel.showSystem.Sync(showSystem)
+    viewModel.option.Sync(option)
+    viewModel.searchQuery.Sync(searchQuery)
+
+    return viewModel.appListDataFlow.collectAsState(null, Dispatchers.Default)
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
new file mode 100644
index 0000000..ac3f8ff
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+
+class AppListItemModel<T : AppRecord>(
+    val record: T,
+    val label: String,
+    val summary: State<String>,
+)
+
+@Composable
+fun <T : AppRecord> AppListItem(
+    itemModel: AppListItemModel<T>,
+    onClick: () -> Unit,
+) {
+    Preference(remember {
+        object : PreferenceModel {
+            override val title = itemModel.label
+            override val summary = itemModel.summary
+            override val icon = @Composable {
+                AppIcon(app = itemModel.record.app, size = SettingsDimension.appIconItemSize)
+            }
+            override val onClick = onClick
+        }
+    })
+}
+
+@Preview
+@Composable
+private fun AppListItemPreview() {
+    SettingsTheme {
+        val record = object : AppRecord {
+            override val app = LocalContext.current.applicationInfo
+        }
+        val itemModel = AppListItemModel<AppRecord>(record, "Chrome", "Allowed".toState())
+        AppListItem(itemModel) {}
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
new file mode 100644
index 0000000..dc30e79
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.widget.scaffold.MoreOptionsAction
+import com.android.settingslib.spa.widget.scaffold.SettingsScaffold
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.app.AppListModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spaprivileged.template.common.WorkProfilePager
+
+@Composable
+fun <T : AppRecord> AppListPage(
+    title: String,
+    listModel: AppListModel<T>,
+    appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
+) {
+    val showSystem = rememberSaveable { mutableStateOf(false) }
+    // TODO: Use SearchScaffold here.
+    SettingsScaffold(
+        title = title,
+        actions = {
+            ShowSystemAction(showSystem.value) { showSystem.value = it }
+        },
+    ) { paddingValues ->
+        Spacer(Modifier.padding(paddingValues))
+        WorkProfilePager { userInfo ->
+            // TODO: Add a Spinner here.
+            AppList(
+                userInfo = userInfo,
+                listModel = listModel,
+                showSystem = showSystem,
+                option = stateOf(0),
+                searchQuery = stateOf(""),
+                appItem = appItem,
+            )
+        }
+    }
+}
+
+@Composable
+private fun ShowSystemAction(showSystem: Boolean, setShowSystem: (showSystem: Boolean) -> Unit) {
+    var expanded by remember { mutableStateOf(false) }
+    MoreOptionsAction { expanded = true }
+    DropdownMenu(
+        expanded = expanded,
+        onDismissRequest = { expanded = false },
+    ) {
+        val menuText = if (showSystem) R.string.menu_hide_system else R.string.menu_show_system
+        DropdownMenuItem(
+            text = { Text(stringResource(menuText)) },
+            onClick = {
+                expanded = false
+                setShowSystem(!showSystem)
+            },
+        )
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 57e9e9a..298ecd5 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -17,6 +17,7 @@
 package com.android.settingslib.spaprivileged.template.app
 
 import android.content.Context
+import android.content.pm.ApplicationInfo
 import android.os.Bundle
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
@@ -28,21 +29,24 @@
 import androidx.navigation.NavType
 import androidx.navigation.navArgument
 import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.rememberContext
 import com.android.settingslib.spa.widget.preference.SwitchPreference
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-import com.android.settingslib.spaprivileged.framework.app.AppRecord
-import com.android.settingslib.spaprivileged.framework.app.PackageManagers
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spaprivileged.model.app.PackageManagers
+import com.android.settingslib.spaprivileged.model.app.toRoute
 import kotlinx.coroutines.Dispatchers
 
+private const val NAME = "TogglePermissionAppInfoPage"
 private const val PERMISSION = "permission"
 private const val PACKAGE_NAME = "packageName"
 private const val USER_ID = "userId"
 
-open class TogglePermissionAppInfoPageProvider(
+internal class TogglePermissionAppInfoPageProvider(
     private val factory: TogglePermissionAppListModelFactory,
 ) : SettingsPageProvider {
-    override val name = "TogglePermissionAppInfoPage"
+    override val name = NAME
 
     override val arguments = listOf(
         navArgument(PERMISSION) { type = NavType.StringType },
@@ -60,8 +64,11 @@
         TogglePermissionAppInfoPage(listModel, packageName, userId)
     }
 
-    fun getRoute(permissionType: String, packageName: String, userId: Int): String =
-        "$name/$permissionType/$packageName/$userId"
+    companion object {
+        @Composable
+        internal fun navigator(permissionType: String, app: ApplicationInfo) =
+            navigator(route = "$NAME/$permissionType/${app.toRoute()}")
+    }
 }
 
 @Composable
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt
index 88ad9da..70ff9a4 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt
@@ -20,14 +20,25 @@
 import android.content.pm.ApplicationInfo
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
-import com.android.settingslib.spaprivileged.framework.app.AppRecord
+import androidx.compose.ui.res.stringResource
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.rememberContext
+import com.android.settingslib.spa.framework.util.asyncMapItem
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import kotlinx.coroutines.flow.Flow
 
 interface TogglePermissionAppListModel<T : AppRecord> {
     val pageTitleResId: Int
     val switchTitleResId: Int
     val footerResId: Int
 
+    fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>): Flow<List<T>> =
+        appListFlow.asyncMapItem(::transformItem)
+
     fun transformItem(app: ApplicationInfo): T
+    fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<T>>): Flow<List<T>>
 
     @Composable
     fun isAllowed(record: T): State<Boolean?>
@@ -41,4 +52,24 @@
         permission: String,
         context: Context,
     ): TogglePermissionAppListModel<out AppRecord>
+
+    fun createPageProviders(): List<SettingsPageProvider> = listOf(
+        TogglePermissionAppListPageProvider(this),
+        TogglePermissionAppInfoPageProvider(this),
+    )
+
+    @Composable
+    fun EntryItem(permissionType: String) {
+        val listModel = rememberModel(permissionType)
+        Preference(
+            object : PreferenceModel {
+                override val title = stringResource(listModel.pageTitleResId)
+                override val onClick = TogglePermissionAppListPageProvider.navigator(permissionType)
+            }
+        )
+    }
 }
+
+@Composable
+internal fun TogglePermissionAppListModelFactory.rememberModel(permission: String) =
+    rememberContext { context -> createModel(permission, context) }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
new file mode 100644
index 0000000..107bf94
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.app.AppListModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import kotlinx.coroutines.flow.Flow
+
+private const val NAME = "TogglePermissionAppList"
+private const val PERMISSION = "permission"
+
+internal class TogglePermissionAppListPageProvider(
+    private val factory: TogglePermissionAppListModelFactory,
+) : SettingsPageProvider {
+    override val name = NAME
+
+    override val arguments = listOf(
+        navArgument(PERMISSION) { type = NavType.StringType },
+    )
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        checkNotNull(arguments)
+        val permissionType = checkNotNull(arguments.getString(PERMISSION))
+        TogglePermissionAppList(permissionType)
+    }
+
+    @Composable
+    private fun TogglePermissionAppList(permissionType: String) {
+        val listModel = factory.rememberModel(permissionType)
+        val context = LocalContext.current
+        val internalListModel = remember {
+            TogglePermissionInternalAppListModel(context, listModel)
+        }
+        AppListPage(
+            title = stringResource(listModel.pageTitleResId),
+            listModel = internalListModel,
+        ) { itemModel ->
+            AppListItem(
+                itemModel = itemModel,
+                onClick = TogglePermissionAppInfoPageProvider.navigator(
+                    permissionType = permissionType,
+                    app = itemModel.record.app,
+                ),
+            )
+        }
+    }
+
+    companion object {
+        @Composable
+        internal fun navigator(permissionType: String) = navigator(route = "$NAME/$permissionType")
+    }
+}
+
+private class TogglePermissionInternalAppListModel<T : AppRecord>(
+    private val context: Context,
+    private val listModel: TogglePermissionAppListModel<T>,
+) : AppListModel<T> {
+    override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+        listModel.transform(userIdFlow, appListFlow)
+
+    override fun filter(userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<T>>) =
+        listModel.filter(userIdFlow, recordListFlow)
+
+    @Composable
+    override fun getSummary(option: Int, record: T): State<String> {
+        val allowed = listModel.isAllowed(record)
+        return remember {
+            derivedStateOf {
+                when (allowed.value) {
+                    true -> context.getString(R.string.app_permission_summary_allowed)
+                    false -> context.getString(R.string.app_permission_summary_not_allowed)
+                    else -> ""
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/WorkProfilePager.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/WorkProfilePager.kt
similarity index 90%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/WorkProfilePager.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/WorkProfilePager.kt
index 09864a1..aa5ccf1 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/WorkProfilePager.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/WorkProfilePager.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spaprivileged.template.scaffold
+package com.android.settingslib.spaprivileged.template.common
 
 import android.content.pm.UserInfo
 import android.os.UserHandle
@@ -23,7 +23,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
 import com.android.settingslib.spa.widget.scaffold.SettingsPager
-import com.android.settingslib.spaprivileged.framework.enterprise.EnterpriseRepository
+import com.android.settingslib.spaprivileged.model.enterprise.EnterpriseRepository
 
 @Composable
 fun WorkProfilePager(content: @Composable (userInfo: UserInfo) -> Unit) {
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index aab0d3a..06d7bb4 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1601,21 +1601,6 @@
     <!-- Content description of the no calling for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_no_calling">No calling.</string>
 
-    <!-- Screensaver overlay which displays the time. [CHAR LIMIT=20] -->
-    <string name="dream_complication_title_time">Time</string>
-    <!-- Screensaver overlay which displays the date. [CHAR LIMIT=20] -->
-    <string name="dream_complication_title_date">Date</string>
-    <!-- Screensaver overlay which displays the weather. [CHAR LIMIT=20] -->
-    <string name="dream_complication_title_weather">Weather</string>
-    <!-- Screensaver overlay which displays air quality. [CHAR LIMIT=20] -->
-    <string name="dream_complication_title_aqi">Air Quality</string>
-    <!-- Screensaver overlay which displays cast info. [CHAR LIMIT=20] -->
-    <string name="dream_complication_title_cast_info">Cast Info</string>
-    <!-- Screensaver overlay which displays home controls. [CHAR LIMIT=20] -->
-    <string name="dream_complication_title_home_controls">Home Controls</string>
-    <!-- Screensaver overlay which displays smartspace. [CHAR LIMIT=20] -->
-    <string name="dream_complication_title_smartspace">Smartspace</string>
-
 
     <!-- Title for a screen allowing the user to choose a profile picture. [CHAR LIMIT=NONE] -->
     <string name="avatar_picker_title">Choose a profile picture</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index b9c4030..a822e18 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -600,6 +600,9 @@
      * Returns the WifiInfo for the underlying WiFi network of the VCN network, returns null if the
      * input NetworkCapabilities is not for a VCN network with underlying WiFi network.
      *
+     * TODO(b/238425913): Move this method to be inside systemui not settingslib once we've migrated
+     *   off of {@link WifiStatusTracker} and {@link NetworkControllerImpl}.
+     *
      * @param networkCapabilities NetworkCapabilities of the network.
      */
     @Nullable
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index 2258617..1606540 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -17,7 +17,6 @@
 package com.android.settingslib.dream;
 
 import android.annotation.IntDef;
-import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -32,17 +31,14 @@
 import android.provider.Settings;
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
-import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.settingslib.R;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Comparator;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -64,18 +60,21 @@
         public String toString() {
             StringBuilder sb = new StringBuilder(DreamInfo.class.getSimpleName());
             sb.append('[').append(caption);
-            if (isActive)
+            if (isActive) {
                 sb.append(",active");
+            }
             sb.append(',').append(componentName);
-            if (settingsComponentName != null)
+            if (settingsComponentName != null) {
                 sb.append("settings=").append(settingsComponentName);
+            }
             return sb.append(']').toString();
         }
     }
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({WHILE_CHARGING, WHILE_DOCKED, EITHER, NEVER})
-    public @interface WhenToDream {}
+    public @interface WhenToDream {
+    }
 
     public static final int WHILE_CHARGING = 0;
     public static final int WHILE_DOCKED = 1;
@@ -96,7 +95,8 @@
             COMPLICATION_TYPE_SMARTSPACE
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface ComplicationType {}
+    public @interface ComplicationType {
+    }
 
     public static final int COMPLICATION_TYPE_TIME = 1;
     public static final int COMPLICATION_TYPE_DATE = 2;
@@ -114,8 +114,6 @@
     private final boolean mDreamsActivatedOnDockByDefault;
     private final Set<ComponentName> mDisabledDreams;
     private final Set<Integer> mSupportedComplications;
-    private final Set<Integer> mDefaultEnabledComplications;
-
     private static DreamBackend sInstance;
 
     public static DreamBackend getInstance(Context context) {
@@ -147,13 +145,6 @@
                         com.android.internal.R.array.config_supportedDreamComplications))
                 .boxed()
                 .collect(Collectors.toSet());
-
-        mDefaultEnabledComplications = Arrays.stream(resources.getIntArray(
-                        com.android.internal.R.array.config_dreamComplicationsEnabledByDefault))
-                .boxed()
-                // A complication can only be enabled by default if it is also supported.
-                .filter(mSupportedComplications::contains)
-                .collect(Collectors.toSet());
     }
 
     public List<DreamInfo> getDreamInfos() {
@@ -251,11 +242,12 @@
         return null;
     }
 
-    public @WhenToDream int getWhenToDreamSetting() {
+    @WhenToDream
+    public int getWhenToDreamSetting() {
         return isActivatedOnDock() && isActivatedOnSleep() ? EITHER
                 : isActivatedOnDock() ? WHILE_DOCKED
-                : isActivatedOnSleep() ? WHILE_CHARGING
-                : NEVER;
+                        : isActivatedOnSleep() ? WHILE_CHARGING
+                                : NEVER;
     }
 
     public void setWhenToDream(@WhenToDream int whenToDream) {
@@ -283,22 +275,24 @@
         }
     }
 
-    /** Returns whether a particular complication is enabled */
-    public boolean isComplicationEnabled(@ComplicationType int complication) {
-        return getEnabledComplications().contains(complication);
-    }
-
     /** Gets all complications which have been enabled by the user. */
     public Set<Integer> getEnabledComplications() {
-        final String enabledComplications = Settings.Secure.getString(
+        return getComplicationsEnabled() ? mSupportedComplications : Collections.emptySet();
+    }
+
+    /** Sets complication enabled state. */
+    public void setComplicationsEnabled(boolean enabled) {
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, enabled ? 1 : 0);
+    }
+
+    /**
+     * Gets whether complications are enabled on this device
+     */
+    public boolean getComplicationsEnabled() {
+        return Settings.Secure.getInt(
                 mContext.getContentResolver(),
-                Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS);
-
-        if (enabledComplications == null) {
-            return mDefaultEnabledComplications;
-        }
-
-        return parseFromString(enabledComplications);
+                Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, 1) == 1;
     }
 
     /** Gets all dream complications which are supported on this device. **/
@@ -306,77 +300,6 @@
         return mSupportedComplications;
     }
 
-    /**
-     * Enables or disables a particular dream complication.
-     *
-     * @param complicationType The dream complication to be enabled/disabled.
-     * @param value            If true, the complication is enabled. Otherwise it is disabled.
-     */
-    public void setComplicationEnabled(@ComplicationType int complicationType, boolean value) {
-        if (!mSupportedComplications.contains(complicationType)) return;
-
-        Set<Integer> enabledComplications = getEnabledComplications();
-        if (value) {
-            enabledComplications.add(complicationType);
-        } else {
-            enabledComplications.remove(complicationType);
-        }
-
-        Settings.Secure.putString(mContext.getContentResolver(),
-                Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS,
-                convertToString(enabledComplications));
-    }
-
-    /**
-     * Gets the title of a particular complication type to be displayed to the user. If there
-     * is no title, null is returned.
-     */
-    @Nullable
-    public CharSequence getComplicationTitle(@ComplicationType int complicationType) {
-        int res = 0;
-        switch (complicationType) {
-            case COMPLICATION_TYPE_TIME:
-                res = R.string.dream_complication_title_time;
-                break;
-            case COMPLICATION_TYPE_DATE:
-                res = R.string.dream_complication_title_date;
-                break;
-            case COMPLICATION_TYPE_WEATHER:
-                res = R.string.dream_complication_title_weather;
-                break;
-            case COMPLICATION_TYPE_AIR_QUALITY:
-                res = R.string.dream_complication_title_aqi;
-                break;
-            case COMPLICATION_TYPE_CAST_INFO:
-                res = R.string.dream_complication_title_cast_info;
-                break;
-            case COMPLICATION_TYPE_HOME_CONTROLS:
-                res = R.string.dream_complication_title_home_controls;
-                break;
-            case COMPLICATION_TYPE_SMARTSPACE:
-                res = R.string.dream_complication_title_smartspace;
-                break;
-            default:
-                return null;
-        }
-        return mContext.getString(res);
-    }
-
-    private static String convertToString(Set<Integer> set) {
-        return set.stream()
-                .map(String::valueOf)
-                .collect(Collectors.joining(","));
-    }
-
-    private static Set<Integer> parseFromString(String string) {
-        if (TextUtils.isEmpty(string)) {
-            return new HashSet<>();
-        }
-        return Arrays.stream(string.split(","))
-                .map(Integer::parseInt)
-                .collect(Collectors.toSet());
-    }
-
     public boolean isEnabled() {
         return getBoolean(Settings.Secure.SCREENSAVER_ENABLED, mDreamsEnabledByDefault);
     }
@@ -416,10 +339,11 @@
 
     public void setActiveDream(ComponentName dream) {
         logd("setActiveDream(%s)", dream);
-        if (mDreamManager == null)
+        if (mDreamManager == null) {
             return;
+        }
         try {
-            ComponentName[] dreams = { dream };
+            ComponentName[] dreams = {dream};
             mDreamManager.setDreamComponents(dream == null ? null : dreams);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to set active dream to " + dream, e);
@@ -427,8 +351,9 @@
     }
 
     public ComponentName getActiveDream() {
-        if (mDreamManager == null)
+        if (mDreamManager == null) {
             return null;
+        }
         try {
             ComponentName[] dreams = mDreamManager.getDreamComponents();
             return dreams != null && dreams.length > 0 ? dreams[0] : null;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/OWNERS b/packages/SettingsLib/src/com/android/settingslib/media/OWNERS
new file mode 100644
index 0000000..d40f322
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/OWNERS
@@ -0,0 +1,2 @@
+# Default reviewers for this and subdirectories.
+shaoweishen@google.com
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
index 86f7850..52b9227 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
 
@@ -34,29 +35,35 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.shadows.ShadowSettings;
 
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowSettings.ShadowSecure.class})
 public final class DreamBackendTest {
     private static final int[] SUPPORTED_DREAM_COMPLICATIONS = {1, 2, 3};
-    private static final int[] DEFAULT_DREAM_COMPLICATIONS = {1, 3, 4};
+    private static final List<Integer> SUPPORTED_DREAM_COMPLICATIONS_LIST = Arrays.stream(
+            SUPPORTED_DREAM_COMPLICATIONS).boxed().collect(
+            Collectors.toList());
 
     @Mock
     private Context mContext;
+    @Mock
+    private ContentResolver mMockResolver;
     private DreamBackend mBackend;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         when(mContext.getApplicationContext()).thenReturn(mContext);
+        when(mContext.getContentResolver()).thenReturn(mMockResolver);
 
         final Resources res = mock(Resources.class);
         when(mContext.getResources()).thenReturn(res);
         when(res.getIntArray(
                 com.android.internal.R.array.config_supportedDreamComplications)).thenReturn(
                 SUPPORTED_DREAM_COMPLICATIONS);
-        when(res.getIntArray(
-                com.android.internal.R.array.config_dreamComplicationsEnabledByDefault)).thenReturn(
-                DEFAULT_DREAM_COMPLICATIONS);
         when(res.getStringArray(
                 com.android.internal.R.array.config_disabledDreamComponents)).thenReturn(
                 new String[]{});
@@ -69,31 +76,25 @@
     }
 
     @Test
-    public void testSupportedComplications() {
-        assertThat(mBackend.getSupportedComplications()).containsExactly(1, 2, 3);
+    public void testComplicationsEnabledByDefault() {
+        assertThat(mBackend.getComplicationsEnabled()).isTrue();
+        assertThat(mBackend.getEnabledComplications()).containsExactlyElementsIn(
+                SUPPORTED_DREAM_COMPLICATIONS_LIST);
     }
 
     @Test
-    public void testGetEnabledDreamComplications_default() {
-        assertThat(mBackend.getEnabledComplications()).containsExactly(1, 3);
+    public void testEnableComplicationExplicitly() {
+        mBackend.setComplicationsEnabled(true);
+        assertThat(mBackend.getEnabledComplications()).containsExactlyElementsIn(
+                SUPPORTED_DREAM_COMPLICATIONS_LIST);
+        assertThat(mBackend.getComplicationsEnabled()).isTrue();
     }
 
     @Test
-    public void testEnableComplication() {
-        mBackend.setComplicationEnabled(/* complicationType= */ 2, true);
-        assertThat(mBackend.getEnabledComplications()).containsExactly(1, 2, 3);
-    }
-
-    @Test
-    public void testEnableComplication_notSupported() {
-        mBackend.setComplicationEnabled(/* complicationType= */ 5, true);
-        assertThat(mBackend.getEnabledComplications()).containsExactly(1, 3);
-    }
-
-    @Test
-    public void testDisableComplication() {
-        mBackend.setComplicationEnabled(/* complicationType= */ 1, false);
-        assertThat(mBackend.getEnabledComplications()).containsExactly(3);
+    public void testDisableComplications() {
+        mBackend.setComplicationsEnabled(false);
+        assertThat(mBackend.getEnabledComplications()).isEmpty();
+        assertThat(mBackend.getComplicationsEnabled()).isFalse();
     }
 }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java
index d86bd01..24037ca 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.widget;
 
+import static android.graphics.text.LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
@@ -97,4 +99,14 @@
 
         assertThat(mBar.getVisibility()).isEqualTo(View.GONE);
     }
+
+    @Test
+    public void setTitle_shouldSetCorrectLineBreakStyle() {
+        final String title = "title";
+
+        mBar.setTitle(title);
+        final TextView textView = ((TextView) mBar.findViewById(R.id.switch_text));
+
+        assertThat(textView.getLineBreakWordStyle()).isEqualTo(LINE_BREAK_WORD_STYLE_PHRASE);
+    }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index fd7554f..528af2e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -376,9 +376,11 @@
             Setting newSetting = new Setting(name, oldSetting.getValue(), null,
                     oldSetting.getPackageName(), oldSetting.getTag(), false,
                     oldSetting.getId());
-            mSettings.put(name, newSetting);
-            updateMemoryUsagePerPackageLocked(newSetting.getPackageName(), oldValue,
+            int newSize = getNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), oldValue,
                     newSetting.getValue(), oldDefaultValue, newSetting.getDefaultValue());
+            checkNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize);
+            mSettings.put(name, newSetting);
+            updateMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize);
             scheduleWriteIfNeededLocked();
         }
     }
@@ -410,6 +412,12 @@
         Setting oldState = mSettings.get(name);
         String oldValue = (oldState != null) ? oldState.value : null;
         String oldDefaultValue = (oldState != null) ? oldState.defaultValue : null;
+        String newDefaultValue = makeDefault ? value : oldDefaultValue;
+
+        int newSize = getNewMemoryUsagePerPackageLocked(packageName, oldValue, value,
+                oldDefaultValue, newDefaultValue);
+        checkNewMemoryUsagePerPackageLocked(packageName, newSize);
+
         Setting newState;
 
         if (oldState != null) {
@@ -430,8 +438,7 @@
 
         addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, newState);
 
-        updateMemoryUsagePerPackageLocked(packageName, oldValue, value,
-                oldDefaultValue, newState.getDefaultValue());
+        updateMemoryUsagePerPackageLocked(packageName, newSize);
 
         scheduleWriteIfNeededLocked();
 
@@ -552,13 +559,14 @@
         }
 
         Setting oldState = mSettings.remove(name);
+        int newSize = getNewMemoryUsagePerPackageLocked(oldState.packageName, oldState.value,
+                null, oldState.defaultValue, null);
 
         FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, name, /* value= */ "",
                 /* newValue= */ "", oldState.value, /* tag */ "", false, getUserIdFromKey(mKey),
                 FrameworkStatsLog.SETTING_CHANGED__REASON__DELETED);
 
-        updateMemoryUsagePerPackageLocked(oldState.packageName, oldState.value,
-                null, oldState.defaultValue, null);
+        updateMemoryUsagePerPackageLocked(oldState.packageName, newSize);
 
         addHistoricalOperationLocked(HISTORICAL_OPERATION_DELETE, oldState);
 
@@ -579,16 +587,18 @@
         Setting oldSetting = new Setting(setting);
         String oldValue = setting.getValue();
         String oldDefaultValue = setting.getDefaultValue();
+        String newValue = oldDefaultValue;
+        String newDefaultValue = oldDefaultValue;
+
+        int newSize = getNewMemoryUsagePerPackageLocked(setting.packageName, oldValue,
+                newValue, oldDefaultValue, newDefaultValue);
+        checkNewMemoryUsagePerPackageLocked(setting.packageName, newSize);
 
         if (!setting.reset()) {
             return false;
         }
 
-        String newValue = setting.getValue();
-        String newDefaultValue = setting.getDefaultValue();
-
-        updateMemoryUsagePerPackageLocked(setting.packageName, oldValue,
-                newValue, oldDefaultValue, newDefaultValue);
+        updateMemoryUsagePerPackageLocked(setting.packageName, newSize);
 
         addHistoricalOperationLocked(HISTORICAL_OPERATION_RESET, oldSetting);
 
@@ -696,38 +706,49 @@
     }
 
     @GuardedBy("mLock")
-    private void updateMemoryUsagePerPackageLocked(String packageName, String oldValue,
+    private boolean isExemptFromMemoryUsageCap(String packageName) {
+        return mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED
+                || SYSTEM_PACKAGE_NAME.equals(packageName);
+    }
+
+    @GuardedBy("mLock")
+    private void checkNewMemoryUsagePerPackageLocked(String packageName, int newSize)
+            throws IllegalStateException {
+        if (isExemptFromMemoryUsageCap(packageName)) {
+            return;
+        }
+        if (newSize > mMaxBytesPerAppPackage) {
+            throw new IllegalStateException("You are adding too many system settings. "
+                    + "You should stop using system settings for app specific data"
+                    + " package: " + packageName);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private int getNewMemoryUsagePerPackageLocked(String packageName, String oldValue,
             String newValue, String oldDefaultValue, String newDefaultValue) {
-        if (mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED) {
-            return;
+        if (isExemptFromMemoryUsageCap(packageName)) {
+            return 0;
         }
-
-        if (SYSTEM_PACKAGE_NAME.equals(packageName)) {
-            return;
-        }
-
+        final Integer currentSize = mPackageToMemoryUsage.get(packageName);
         final int oldValueSize = (oldValue != null) ? oldValue.length() : 0;
         final int newValueSize = (newValue != null) ? newValue.length() : 0;
         final int oldDefaultValueSize = (oldDefaultValue != null) ? oldDefaultValue.length() : 0;
         final int newDefaultValueSize = (newDefaultValue != null) ? newDefaultValue.length() : 0;
         final int deltaSize = newValueSize + newDefaultValueSize
                 - oldValueSize - oldDefaultValueSize;
+        return Math.max((currentSize != null) ? currentSize + deltaSize : deltaSize, 0);
+    }
 
-        Integer currentSize = mPackageToMemoryUsage.get(packageName);
-        final int newSize = Math.max((currentSize != null)
-                ? currentSize + deltaSize : deltaSize, 0);
-
-        if (newSize > mMaxBytesPerAppPackage) {
-            throw new IllegalStateException("You are adding too many system settings. "
-                    + "You should stop using system settings for app specific data"
-                    + " package: " + packageName);
+    @GuardedBy("mLock")
+    private void updateMemoryUsagePerPackageLocked(String packageName, int newSize) {
+        if (isExemptFromMemoryUsageCap(packageName)) {
+            return;
         }
-
         if (DEBUG) {
             Slog.i(LOG_TAG, "Settings for package: " + packageName
                     + " size: " + newSize + " bytes.");
         }
-
         mPackageToMemoryUsage.put(packageName, newSize);
     }
 
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 69eb713..a637efa 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -20,6 +20,8 @@
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
 
+import com.google.common.base.Strings;
+
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
@@ -276,4 +278,39 @@
         settingsState.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING);
         return settingsState;
     }
+
+    public void testInsertSetting_memoryUsage() {
+        SettingsState settingsState = getSettingStateObject();
+        // No exception should be thrown when there is no cap
+        settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+                null, false, "p1");
+        settingsState.deleteSettingLocked(SETTING_NAME);
+
+        settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
+                SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
+        // System package doesn't have memory usage limit
+        settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+                null, false, SYSTEM_PACKAGE);
+        settingsState.deleteSettingLocked(SETTING_NAME);
+
+        // Should not throw if usage is under the cap
+        settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 19999),
+                null, false, "p1");
+        settingsState.deleteSettingLocked(SETTING_NAME);
+        try {
+            settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+                    null, false, "p1");
+            fail("Should throw because it exceeded per package memory usage");
+        } catch (IllegalStateException ex) {
+            assertTrue(ex.getMessage().contains("p1"));
+        }
+        try {
+            settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+                    null, false, "p1");
+            fail("Should throw because it exceeded per package memory usage");
+        } catch (IllegalStateException ex) {
+            assertTrue(ex.getMessage().contains("p1"));
+        }
+        assertTrue(settingsState.getSettingLocked(SETTING_NAME).isNull());
+    }
 }
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6edf13a..78dea89 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -85,6 +85,7 @@
     <uses-permission android:name="android.permission.CONTROL_VPN" />
     <uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/>
     <uses-permission android:name="android.permission.READ_WIFI_CREDENTIAL"/>
+    <uses-permission android:name="android.permission.NETWORK_STACK"/>
     <!-- Physical hardware -->
     <uses-permission android:name="android.permission.MANAGE_USB" />
     <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
@@ -394,6 +395,11 @@
                   android:label="@string/screenshot_scroll_label"
                   android:finishOnTaskLaunch="true" />
 
+        <service android:name=".screenshot.ScreenshotProxyService"
+                 android:permission="com.android.systemui.permission.SELF"
+                 android:exported="false" />
+
+
         <service android:name=".screenrecord.RecordingService" />
 
         <receiver android:name=".SysuiRestartReceiver"
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 61fac29..4a6164c 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -14,11 +14,10 @@
 brycelee@google.com
 ccassidy@google.com
 chrisgollner@google.com
-cinek@google.com
-cwren@google.com
 dupin@google.com
 ethibodeau@google.com
 evanlaird@google.com
+florenceyang@google.com
 gwasserman@google.com
 hwwang@google.com
 hyunyoungs@google.com
@@ -27,36 +26,36 @@
 jbolinger@google.com
 jdemeulenaere@google.com
 jeffdq@google.com
+jernej@google.com
+jglazier@google.com
 jjaggi@google.com
 jonmiranda@google.com
 joshtrask@google.com
 juliacr@google.com
 juliatuttle@google.com
 justinkoh@google.com
-kchyn@google.com
 kozynski@google.com
 kprevas@google.com
 lynhan@google.com
 madym@google.com
 mankoff@google.com
-mett@google.com
 mkephart@google.com
 mpietal@google.com
 mrcasey@google.com
 mrenouf@google.com
-nesciosquid@google.com
 nickchameyev@google.com
 nicomazz@google.com
 ogunwale@google.com
 peanutbutter@google.com
+peskal@google.com
 pinyaoting@google.com
 pixel@google.com
+pomini@google.com
 rahulbanerjee@google.com
 roosa@google.com
 santie@google.com
 shanh@google.com
 snoeberger@google.com
-sreyasr@google.com
 steell@google.com
 sfufa@google.com
 stwu@google.com
@@ -70,15 +69,10 @@
 victortulias@google.com
 winsonc@google.com
 wleshner@google.com
-yurilin@google.com
 xuqiu@google.com
+yuandizhou@google.com
+yurilin@google.com
 zakcohen@google.com
-jernej@google.com
-jglazier@google.com
-peskal@google.com
-
-#Android Auto
-hseog@google.com
 
 #Android TV
 rgl@google.com
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 1008481..1ac1e3a 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -17,6 +17,7 @@
 import android.graphics.drawable.Drawable
 import android.view.View
 import com.android.systemui.plugins.annotations.ProvidesInterface
+import com.android.systemui.shared.regionsampling.RegionDarkness
 import java.io.PrintWriter
 import java.util.Locale
 import java.util.TimeZone
@@ -116,12 +117,3 @@
     val clockId: ClockId,
     val name: String
 )
-
-/**
- * Enum for whether clock region is dark or light.
- */
-enum class RegionDarkness(val isDark: Boolean) {
-    DEFAULT(false),
-    DARK(true),
-    LIGHT(false)
-}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/shared/regionsampling/RegionDarkness.kt b/packages/SystemUI/plugin/src/com/android/systemui/shared/regionsampling/RegionDarkness.kt
new file mode 100644
index 0000000..344fdb8
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/shared/regionsampling/RegionDarkness.kt
@@ -0,0 +1,10 @@
+package com.android.systemui.shared.regionsampling
+
+/**
+ * Enum for whether clock region is dark or light.
+ */
+enum class RegionDarkness(val isDark: Boolean) {
+    DEFAULT(false),
+    DARK(true),
+    LIGHT(false)
+}
diff --git a/packages/SystemUI/res-keyguard/values-land/dimens.xml b/packages/SystemUI/res-keyguard/values-land/dimens.xml
index 4e92884f..a4e7a5f 100644
--- a/packages/SystemUI/res-keyguard/values-land/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-land/dimens.xml
@@ -22,7 +22,6 @@
     <dimen name="keyguard_eca_top_margin">0dp</dimen>
     <dimen name="keyguard_eca_bottom_margin">2dp</dimen>
     <dimen name="keyguard_password_height">26dp</dimen>
-    <dimen name="num_pad_entry_row_margin_bottom">0dp</dimen>
 
     <!-- The size of PIN text in the PIN unlock method. -->
     <integer name="scaled_password_text_size">26</integer>
diff --git a/packages/SystemUI/res-keyguard/values-sw360dp-land/dimens.xml b/packages/SystemUI/res-keyguard/values-sw360dp-land/dimens.xml
index f465be4..0421135 100644
--- a/packages/SystemUI/res-keyguard/values-sw360dp-land/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-sw360dp-land/dimens.xml
@@ -22,7 +22,6 @@
     <dimen name="keyguard_eca_top_margin">4dp</dimen>
     <dimen name="keyguard_eca_bottom_margin">4dp</dimen>
     <dimen name="keyguard_password_height">50dp</dimen>
-    <dimen name="num_pad_entry_row_margin_bottom">4dp</dimen>
 
     <!-- The size of PIN text in the PIN unlock method. -->
     <integer name="scaled_password_text_size">40</integer>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index acf3e4d..32871f0 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -86,7 +86,7 @@
 
     <!-- Spacing around each button used for PIN view -->
     <dimen name="num_pad_key_width">72dp</dimen>
-    <dimen name="num_pad_entry_row_margin_bottom">16dp</dimen>
+    <dimen name="num_pad_entry_row_margin_bottom">12dp</dimen>
     <dimen name="num_pad_row_margin_bottom">6dp</dimen>
     <dimen name="num_pad_key_margin_end">12dp</dimen>
 
diff --git a/packages/SystemUI/res/layout/new_status_bar_wifi_group.xml b/packages/SystemUI/res/layout/new_status_bar_wifi_group.xml
new file mode 100644
index 0000000..753ba2f
--- /dev/null
+++ b/packages/SystemUI/res/layout/new_status_bar_wifi_group.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2022, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/wifi_combo"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:gravity="center_vertical" >
+
+    <include layout="@layout/status_bar_wifi_group_inner" />
+
+</com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView>
diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group.xml b/packages/SystemUI/res/layout/status_bar_wifi_group.xml
index 35cce25..6cb6993b 100644
--- a/packages/SystemUI/res/layout/status_bar_wifi_group.xml
+++ b/packages/SystemUI/res/layout/status_bar_wifi_group.xml
@@ -18,70 +18,11 @@
 -->
 <com.android.systemui.statusbar.StatusBarWifiView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/wifi_combo"
     android:layout_width="wrap_content"
     android:layout_height="match_parent"
     android:gravity="center_vertical" >
 
-    <com.android.keyguard.AlphaOptimizedLinearLayout
-        android:id="@+id/wifi_group"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:gravity="center_vertical"
-        android:layout_marginStart="2.5dp"
-    >
-        <FrameLayout
-                android:id="@+id/inout_container"
-                android:layout_height="17dp"
-                android:layout_width="wrap_content"
-                android:gravity="center_vertical" >
-            <ImageView
-                android:id="@+id/wifi_in"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:src="@drawable/ic_activity_down"
-                android:visibility="gone"
-                android:paddingEnd="2dp"
-            />
-            <ImageView
-                android:id="@+id/wifi_out"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:src="@drawable/ic_activity_up"
-                android:paddingEnd="2dp"
-                android:visibility="gone"
-            />
-        </FrameLayout>
-        <FrameLayout
-            android:id="@+id/wifi_combo"
-            android:layout_height="wrap_content"
-            android:layout_width="wrap_content"
-            android:gravity="center_vertical" >
-            <com.android.systemui.statusbar.AlphaOptimizedImageView
-                android:id="@+id/wifi_signal"
-                android:layout_height="@dimen/status_bar_wifi_signal_size"
-                android:layout_width="@dimen/status_bar_wifi_signal_size" />
-        </FrameLayout>
+    <include layout="@layout/status_bar_wifi_group_inner" />
 
-        <View
-            android:id="@+id/wifi_signal_spacer"
-            android:layout_width="@dimen/status_bar_wifi_signal_spacer_width"
-            android:layout_height="4dp"
-            android:visibility="gone" />
-
-        <!-- Looks like CarStatusBar uses this... -->
-        <ViewStub
-            android:id="@+id/connected_device_signals_stub"
-            android:layout="@layout/connected_device_signal"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
-
-        <View
-            android:id="@+id/wifi_airplane_spacer"
-            android:layout_width="@dimen/status_bar_airplane_spacer_width"
-            android:layout_height="4dp"
-            android:visibility="gone"
-        />
-    </com.android.keyguard.AlphaOptimizedLinearLayout>
-</com.android.systemui.statusbar.StatusBarWifiView>
+</com.android.systemui.statusbar.StatusBarWifiView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
new file mode 100644
index 0000000..0ea0653
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2022, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <com.android.keyguard.AlphaOptimizedLinearLayout
+        android:id="@+id/wifi_group"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="center_vertical"
+        android:layout_marginStart="2.5dp"
+    >
+        <FrameLayout
+                android:id="@+id/inout_container"
+                android:layout_height="17dp"
+                android:layout_width="wrap_content"
+                android:gravity="center_vertical" >
+            <ImageView
+                android:id="@+id/wifi_in"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:src="@drawable/ic_activity_down"
+                android:visibility="gone"
+                android:paddingEnd="2dp"
+            />
+            <ImageView
+                android:id="@+id/wifi_out"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:src="@drawable/ic_activity_up"
+                android:paddingEnd="2dp"
+                android:visibility="gone"
+            />
+        </FrameLayout>
+        <FrameLayout
+            android:id="@+id/wifi_combo"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:gravity="center_vertical" >
+            <com.android.systemui.statusbar.AlphaOptimizedImageView
+                android:id="@+id/wifi_signal"
+                android:layout_height="@dimen/status_bar_wifi_signal_size"
+                android:layout_width="@dimen/status_bar_wifi_signal_size" />
+        </FrameLayout>
+
+        <View
+            android:id="@+id/wifi_signal_spacer"
+            android:layout_width="@dimen/status_bar_wifi_signal_spacer_width"
+            android:layout_height="4dp"
+            android:visibility="gone" />
+
+        <!-- Looks like CarStatusBar uses this... -->
+        <ViewStub
+            android:id="@+id/connected_device_signals_stub"
+            android:layout="@layout/connected_device_signal"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <View
+            android:id="@+id/wifi_airplane_spacer"
+            android:layout_width="@dimen/status_bar_airplane_spacer_width"
+            android:layout_height="4dp"
+            android:visibility="gone"
+        />
+    </com.android.keyguard.AlphaOptimizedLinearLayout>
+</merge>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7c1fdd5..ae30089 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1179,7 +1179,6 @@
     <item name="shutdown_scrim_behind_alpha" format="float" type="dimen">0.95</item>
 
     <!-- Output switcher panel related dimensions -->
-    <dimen name="media_output_dialog_list_margin">12dp</dimen>
     <dimen name="media_output_dialog_list_max_height">355dp</dimen>
     <dimen name="media_output_dialog_header_album_icon_size">72dp</dimen>
     <dimen name="media_output_dialog_header_back_icon_size">32dp</dimen>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 8f1959e..a21a78b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -50,7 +50,12 @@
     defStyleRes: Int = 0
 ) : TextView(context, attrs, defStyleAttr, defStyleRes) {
 
-    private var lastMeasureCall: CharSequence = ""
+    private var lastMeasureCall: CharSequence? = null
+    private var lastDraw: CharSequence? = null
+    private var lastTextUpdate: CharSequence? = null
+    private var lastOnTextChanged: CharSequence? = null
+    private var lastInvalidate: CharSequence? = null
+    private var lastTimeZoneChange: CharSequence? = null
 
     private val time = Calendar.getInstance()
 
@@ -142,7 +147,6 @@
         // relayout if the text didn't actually change.
         if (!TextUtils.equals(text, formattedText)) {
             text = formattedText
-
             // Because the TextLayout may mutate under the hood as a result of the new text, we
             // notify the TextAnimator that it may have changed and request a measure/layout. A
             // crash will occur on the next invocation of setTextStyle if the layout is mutated
@@ -151,18 +155,19 @@
                 textAnimator?.updateLayout(layout)
             }
             requestLayout()
+            lastTextUpdate = getTimestamp()
         }
     }
 
     fun onTimeZoneChanged(timeZone: TimeZone?) {
         time.timeZone = timeZone
         refreshFormat()
+        lastTimeZoneChange = "${getTimestamp()} timeZone=${time.timeZone}"
     }
 
     @SuppressLint("DrawAllocation")
     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec)
-        lastMeasureCall = DateFormat.format(descFormat, System.currentTimeMillis())
         val animator = textAnimator
         if (animator == null) {
             textAnimator = TextAnimator(layout) { invalidate() }
@@ -171,13 +176,34 @@
         } else {
             animator.updateLayout(layout)
         }
+        lastMeasureCall = getTimestamp()
     }
 
     override fun onDraw(canvas: Canvas) {
+        lastDraw = getTimestamp()
         // intentionally doesn't call super.onDraw here or else the text will be rendered twice
         textAnimator?.draw(canvas)
     }
 
+    override fun invalidate() {
+        super.invalidate()
+        lastInvalidate = getTimestamp()
+    }
+
+    private fun getTimestamp(): CharSequence {
+        return "${DateFormat.format("HH:mm:ss", System.currentTimeMillis())} text=$text"
+    }
+
+    override fun onTextChanged(
+            text: CharSequence,
+            start: Int,
+            lengthBefore: Int,
+            lengthAfter: Int
+    ) {
+        super.onTextChanged(text, start, lengthBefore, lengthAfter)
+        lastOnTextChanged = "${getTimestamp()}"
+    }
+
     fun setLineSpacingScale(scale: Float) {
         lineSpacingScale = scale
         setLineSpacing(0f, lineSpacingScale)
@@ -370,7 +396,12 @@
         pw.println("    measuredWidth=$measuredWidth")
         pw.println("    measuredHeight=$measuredHeight")
         pw.println("    singleLineInternal=$isSingleLineInternal")
+        pw.println("    lastTextUpdate=$lastTextUpdate")
+        pw.println("    lastOnTextChanged=$lastOnTextChanged")
+        pw.println("    lastInvalidate=$lastInvalidate")
         pw.println("    lastMeasureCall=$lastMeasureCall")
+        pw.println("    lastDraw=$lastDraw")
+        pw.println("    lastTimeZoneChange=$lastTimeZoneChange")
         pw.println("    currText=$text")
         pw.println("    currTimeContextDesc=$contentDescription")
         pw.println("    time=$time")
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 3d72f15..6c49186 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -26,8 +26,8 @@
 import com.android.systemui.plugins.ClockId
 import com.android.systemui.plugins.ClockMetadata
 import com.android.systemui.plugins.ClockProvider
-import com.android.systemui.plugins.RegionDarkness
 import com.android.systemui.shared.R
+import com.android.systemui.shared.regionsampling.RegionDarkness
 import java.io.PrintWriter
 import java.util.Locale
 import java.util.TimeZone
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
index deabc27..dd2e55d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
@@ -18,7 +18,6 @@
 import android.graphics.Rect
 import android.view.View
 import androidx.annotation.VisibleForTesting
-import com.android.systemui.plugins.RegionDarkness
 import com.android.systemui.shared.navigationbar.RegionSamplingHelper
 import com.android.systemui.shared.navigationbar.RegionSamplingHelper.SamplingCallback
 import java.io.PrintWriter
@@ -36,6 +35,7 @@
 ) {
     private var isDark = RegionDarkness.DEFAULT
     private var samplingBounds = Rect()
+    private val tmpScreenLocation = IntArray(2)
     @VisibleForTesting var regionSampler: RegionSamplingHelper? = null
 
     /**
@@ -100,10 +100,21 @@
                             isDark = convertToClockDarkness(isRegionDark)
                             updateFun.updateColors()
                         }
-
+                        /**
+                        * The method getLocationOnScreen is used to obtain the view coordinates
+                        * relative to its left and top edges on the device screen.
+                        * Directly accessing the X and Y coordinates of the view returns the
+                        * location relative to its parent view instead.
+                        */
                         override fun getSampledRegion(sampledView: View): Rect {
-                            samplingBounds = Rect(sampledView.left, sampledView.top,
-                                    sampledView.right, sampledView.bottom)
+                            val screenLocation = tmpScreenLocation
+                            sampledView.getLocationOnScreen(screenLocation)
+                            val left = screenLocation[0]
+                            val top = screenLocation[1]
+                            samplingBounds.left = left
+                            samplingBounds.top = top
+                            samplingBounds.right = left + sampledView.width
+                            samplingBounds.bottom = top + sampledView.height
                             return samplingBounds
                         }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index b29dc83..22bffda 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -49,6 +49,7 @@
 import android.view.animation.Interpolator;
 import android.view.animation.LinearInterpolator;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
@@ -99,6 +100,7 @@
     private @WindowInsetsController.Behavior
     int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT;
     private int mNavBarMode;
+    private boolean mTaskBarVisible = false;
     private boolean mSkipOverrideUserLockPrefsOnce;
     private final int mLightIconColor;
     private final int mDarkIconColor;
@@ -422,6 +424,7 @@
     }
 
     public void onTaskbarStateChange(boolean visible, boolean stashed) {
+        mTaskBarVisible = visible;
         if (getRotationButton() == null) {
             return;
         }
@@ -438,9 +441,12 @@
      * Return true when either the task bar is visible or it's in visual immersive mode.
      */
     @SuppressLint("InlinedApi")
-    private boolean canShowRotationButton() {
-        return mIsNavigationBarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT
-                || isGesturalMode(mNavBarMode);
+    @VisibleForTesting
+    boolean canShowRotationButton() {
+        return mIsNavigationBarShowing
+            || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT
+            || isGesturalMode(mNavBarMode)
+            || mTaskBarVisible;
     }
 
     @DrawableRes
@@ -624,4 +630,3 @@
         }
     }
 }
-
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 2f9bc1c..d752852 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -54,6 +54,7 @@
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.hardware.SensorPrivacyManager;
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricSourceType;
@@ -2018,12 +2019,13 @@
         // in case authenticators aren't registered yet at this point:
         mAuthController.addCallback(new AuthController.Callback() {
             @Override
-            public void onAllAuthenticatorsRegistered() {
+            public void onAllAuthenticatorsRegistered(
+                    @BiometricAuthenticator.Modality int modality) {
                 mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE));
             }
 
             @Override
-            public void onEnrollmentsChanged() {
+            public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
                 mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE));
             }
         });
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 06e1828..d6974df 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
 
 import static com.android.keyguard.LockIconView.ICON_FINGERPRINT;
@@ -29,6 +30,7 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.AnimatedStateListDrawable;
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricSourceType;
 import android.os.Process;
 import android.os.VibrationAttributes;
@@ -701,13 +703,17 @@
 
     private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
         @Override
-        public void onAllAuthenticatorsRegistered() {
-            updateUdfpsConfig();
+        public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) {
+            if (modality == TYPE_FINGERPRINT) {
+                updateUdfpsConfig();
+            }
         }
 
         @Override
-        public void onEnrollmentsChanged() {
-            updateUdfpsConfig();
+        public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
+            if (modality == TYPE_FINGERPRINT) {
+                updateUdfpsConfig();
+            }
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java b/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java
index 43b3929..df65bcf 100644
--- a/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ActivityIntentHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui;
 
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -34,12 +35,12 @@
 @SysUISingleton
 public class ActivityIntentHelper {
 
-    private final Context mContext;
+    private final PackageManager mPm;
 
     @Inject
     public ActivityIntentHelper(Context context) {
         // TODO: inject a package manager, not a context.
-        mContext = context;
+        mPm = context.getPackageManager();
     }
 
     /**
@@ -57,6 +58,15 @@
     }
 
     /**
+     * @see #wouldLaunchResolverActivity(Intent, int)
+     */
+    public boolean wouldPendingLaunchResolverActivity(PendingIntent intent, int currentUserId) {
+        ActivityInfo targetActivityInfo = getPendingTargetActivityInfo(intent, currentUserId,
+                false /* onlyDirectBootAware */);
+        return targetActivityInfo == null;
+    }
+
+    /**
      * Returns info about the target Activity of a given intent, or null if the intent does not
      * resolve to a specific component meeting the requirements.
      *
@@ -68,19 +78,45 @@
      */
     public ActivityInfo getTargetActivityInfo(Intent intent, int currentUserId,
             boolean onlyDirectBootAware) {
-        PackageManager packageManager = mContext.getPackageManager();
-        int flags = PackageManager.MATCH_DEFAULT_ONLY;
+        int flags = PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA;
         if (!onlyDirectBootAware) {
             flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
                     | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
         }
-        final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
+        final List<ResolveInfo> appList = mPm.queryIntentActivitiesAsUser(
                 intent, flags, currentUserId);
         if (appList.size() == 0) {
             return null;
         }
-        ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
-                flags | PackageManager.GET_META_DATA, currentUserId);
+        if (appList.size() == 1) {
+            return appList.get(0).activityInfo;
+        }
+        ResolveInfo resolved = mPm.resolveActivityAsUser(intent, flags, currentUserId);
+        if (resolved == null || wouldLaunchResolverActivity(resolved, appList)) {
+            return null;
+        } else {
+            return resolved.activityInfo;
+        }
+    }
+
+    /**
+     * @see #getTargetActivityInfo(Intent, int, boolean)
+     */
+    public ActivityInfo getPendingTargetActivityInfo(PendingIntent intent, int currentUserId,
+            boolean onlyDirectBootAware) {
+        int flags = PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA;
+        if (!onlyDirectBootAware) {
+            flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
+                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+        }
+        final List<ResolveInfo> appList = intent.queryIntentComponents(flags);
+        if (appList.size() == 0) {
+            return null;
+        }
+        if (appList.size() == 1) {
+            return appList.get(0).activityInfo;
+        }
+        ResolveInfo resolved = mPm.resolveActivityAsUser(intent.getIntent(), flags, currentUserId);
         if (resolved == null || wouldLaunchResolverActivity(resolved, appList)) {
             return null;
         } else {
@@ -104,6 +140,17 @@
     }
 
     /**
+     * @see #wouldShowOverLockscreen(Intent, int)
+     */
+    public boolean wouldPendingShowOverLockscreen(PendingIntent intent, int currentUserId) {
+        ActivityInfo targetActivityInfo = getPendingTargetActivityInfo(intent,
+                currentUserId, false /* onlyDirectBootAware */);
+        return targetActivityInfo != null
+                && (targetActivityInfo.flags & (ActivityInfo.FLAG_SHOW_WHEN_LOCKED
+                | ActivityInfo.FLAG_SHOW_FOR_ALL_USERS)) > 0;
+    }
+
+    /**
      * Determines if sending the given intent would result in starting an Intent resolver activity,
      * instead of resolving to a specific component.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/GuestSessionNotification.java b/packages/SystemUI/src/com/android/systemui/GuestSessionNotification.java
index b0eaab9..fa9a83e 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestSessionNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestSessionNotification.java
@@ -25,7 +25,6 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.util.FeatureFlagUtils;
 
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.systemui.util.NotificationChannels;
@@ -59,10 +58,8 @@
     }
 
     void createPersistentNotification(UserInfo userInfo, boolean isGuestFirstLogin) {
-        if (!FeatureFlagUtils.isEnabled(mContext,
-                FeatureFlagUtils.SETTINGS_GUEST_MODE_UX_CHANGES)
-                || !userInfo.isGuest()) {
-            // we create a persistent notification only if enabled and only for guests
+        if (!userInfo.isGuest()) {
+            // we create a persistent notification only for guests
             return;
         }
         String contentText;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 47ff59c..282f251 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -46,6 +46,7 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
@@ -156,25 +157,6 @@
         }
     };
 
-    private final IFingerprintAuthenticatorsRegisteredCallback
-            mFingerprintAuthenticatorsRegisteredCallback =
-            new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
-                @Override
-                public void onAllAuthenticatorsRegistered(
-                        List<FingerprintSensorPropertiesInternal> sensors) {
-                    mHandler.post(() -> handleAllFingerprintAuthenticatorsRegistered(sensors));
-                }
-            };
-
-    private final BiometricStateListener mBiometricStateListener =
-            new BiometricStateListener() {
-                @Override
-                public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
-                    mHandler.post(
-                            () -> handleEnrollmentsChanged(userId, sensorId, hasEnrollments));
-                }
-            };
-
     @VisibleForTesting
     final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
@@ -249,8 +231,8 @@
             List<FingerprintSensorPropertiesInternal> sensors) {
         mExecution.assertIsMainThread();
         if (DEBUG) {
-            Log.d(TAG, "handleAllAuthenticatorsRegistered | sensors: " + Arrays.toString(
-                    sensors.toArray()));
+            Log.d(TAG, "handleAllFingerprintAuthenticatorsRegistered | sensors: "
+                    + Arrays.toString(sensors.toArray()));
         }
         mAllFingerprintAuthenticatorsRegistered = true;
         mFpProps = sensors;
@@ -292,15 +274,42 @@
             mSidefpsController = mSidefpsControllerFactory.get();
         }
 
-        mFingerprintManager.registerBiometricStateListener(mBiometricStateListener);
+        mFingerprintManager.registerBiometricStateListener(new BiometricStateListener() {
+            @Override
+            public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+                mHandler.post(() -> handleEnrollmentsChanged(
+                        TYPE_FINGERPRINT, userId, sensorId, hasEnrollments));
+            }
+        });
         updateFingerprintLocation();
 
         for (Callback cb : mCallbacks) {
-            cb.onAllAuthenticatorsRegistered();
+            cb.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT);
         }
     }
 
-    private void handleEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+    private void handleAllFaceAuthenticatorsRegistered(List<FaceSensorPropertiesInternal> sensors) {
+        mExecution.assertIsMainThread();
+        if (DEBUG) {
+            Log.d(TAG, "handleAllFaceAuthenticatorsRegistered | sensors: " + Arrays.toString(
+                    sensors.toArray()));
+        }
+
+        mFaceManager.registerBiometricStateListener(new BiometricStateListener() {
+            @Override
+            public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+                mHandler.post(() -> handleEnrollmentsChanged(
+                        TYPE_FACE, userId, sensorId, hasEnrollments));
+            }
+        });
+
+        for (Callback cb : mCallbacks) {
+            cb.onAllAuthenticatorsRegistered(TYPE_FACE);
+        }
+    }
+
+    private void handleEnrollmentsChanged(@Modality int modality, int userId, int sensorId,
+            boolean hasEnrollments) {
         mExecution.assertIsMainThread();
         Log.d(TAG, "handleEnrollmentsChanged, userId: " + userId + ", sensorId: " + sensorId
                 + ", hasEnrollments: " + hasEnrollments);
@@ -314,7 +323,7 @@
             }
         }
         for (Callback cb : mCallbacks) {
-            cb.onEnrollmentsChanged();
+            cb.onEnrollmentsChanged(modality);
         }
     }
 
@@ -700,7 +709,26 @@
 
         if (mFingerprintManager != null) {
             mFingerprintManager.addAuthenticatorsRegisteredCallback(
-                    mFingerprintAuthenticatorsRegisteredCallback);
+                    new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+                        @Override
+                        public void onAllAuthenticatorsRegistered(
+                                List<FingerprintSensorPropertiesInternal> sensors) {
+                            mHandler.post(() ->
+                                    handleAllFingerprintAuthenticatorsRegistered(sensors));
+                        }
+                    });
+        }
+        if (mFaceManager != null) {
+            mFaceManager.addAuthenticatorsRegisteredCallback(
+                    new IFaceAuthenticatorsRegisteredCallback.Stub() {
+                        @Override
+                        public void onAllAuthenticatorsRegistered(
+                                List<FaceSensorPropertiesInternal> sensors) {
+                            mHandler.post(() ->
+                                    handleAllFaceAuthenticatorsRegistered(sensors));
+                        }
+                    }
+            );
         }
 
         mStableDisplaySize = mDisplayManager.getStableDisplaySize();
@@ -1116,13 +1144,13 @@
          * Called when authenticators are registered. If authenticators are already
          * registered before this call, this callback will never be triggered.
          */
-        default void onAllAuthenticatorsRegistered() {}
+        default void onAllAuthenticatorsRegistered(@Modality int modality) {}
 
         /**
-         * Called when UDFPS enrollments have changed. This is called after boot and on changes to
+         * Called when enrollments have changed. This is called after boot and on changes to
          * enrollment.
          */
-        default void onEnrollmentsChanged() {}
+        default void onEnrollmentsChanged(@Modality int modality) {}
 
         /**
          * Called when the biometric prompt starts showing.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 38fab8f..fd3f600 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -308,7 +308,7 @@
 
     private val authControllerCallback =
         object : AuthController.Callback {
-            override fun onAllAuthenticatorsRegistered() {
+            override fun onAllAuthenticatorsRegistered(modality: Int) {
                 updateUdfpsDependentParams()
                 updateSensorLocation()
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index 49e378e..d96476f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -99,12 +99,11 @@
         mProgressColor = context.getColor(R.color.udfps_enroll_progress);
         final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
         mIsAccessibilityEnabled = am.isTouchExplorationEnabled();
+        mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
         if (!mIsAccessibilityEnabled) {
             mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
-            mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
         } else {
             mHelpColor = context.getColor(R.color.udfps_enroll_progress_help_with_talkback);
-            mOnFirstBucketFailedColor = mHelpColor;
         }
         mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
         mCheckmarkDrawable.mutate();
@@ -167,7 +166,8 @@
     }
 
     private void updateProgress(int remainingSteps, int totalSteps, boolean showingHelp) {
-        if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps) {
+        if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps
+                && mShowingHelp == showingHelp) {
             return;
         }
 
@@ -197,6 +197,7 @@
             }
         }
 
+        mShowingHelp = showingHelp;
         mRemainingSteps = remainingSteps;
         mTotalSteps = totalSteps;
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 5dff4a5..4157728 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -26,6 +26,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.keyguard.KeyguardViewController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -61,6 +62,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryControllerImpl;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -191,7 +193,9 @@
             GroupMembershipManager groupManager,
             VisualStabilityProvider visualStabilityProvider,
             ConfigurationController configurationController,
-            @Main Handler handler) {
+            @Main Handler handler,
+            AccessibilityManagerWrapper accessibilityManagerWrapper,
+            UiEventLogger uiEventLogger) {
         return new HeadsUpManagerPhone(
                 context,
                 headsUpManagerLogger,
@@ -200,7 +204,9 @@
                 groupManager,
                 visualStabilityProvider,
                 configurationController,
-                handler
+                handler,
+                accessibilityManagerWrapper,
+                uiEventLogger
         );
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index f32ea35..e549a96 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -18,6 +18,7 @@
 
 import android.app.INotificationManager;
 import android.content.Context;
+import android.service.dreams.IDreamManager;
 
 import androidx.annotation.Nullable;
 
@@ -213,6 +214,7 @@
             ShadeController shadeController,
             @Nullable IStatusBarService statusBarService,
             INotificationManager notificationManager,
+            IDreamManager dreamManager,
             NotificationVisibilityProvider visibilityProvider,
             NotificationInterruptStateProvider interruptionStateProvider,
             ZenModeController zenModeController,
@@ -230,6 +232,7 @@
                 shadeController,
                 statusBarService,
                 notificationManager,
+                dreamManager,
                 visibilityProvider,
                 interruptionStateProvider,
                 zenModeController,
@@ -238,7 +241,6 @@
                 notifCollection,
                 notifPipeline,
                 sysUiState,
-                dumpManager,
                 sysuiMainExecutor));
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index a9e310d..7da2cf1 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -16,12 +16,15 @@
 
 package com.android.systemui.doze;
 
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
 import static com.android.systemui.doze.DozeMachine.State.DOZE;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
 import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
 
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.os.Handler;
 import android.util.Log;
 import android.view.Display;
@@ -232,13 +235,17 @@
 
     private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
         @Override
-        public void onAllAuthenticatorsRegistered() {
-            updateUdfpsController();
+        public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) {
+            if (modality == TYPE_FINGERPRINT) {
+                updateUdfpsController();
+            }
         }
 
         @Override
-        public void onEnrollmentsChanged() {
-            updateUdfpsController();
+        public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
+            if (modality == TYPE_FINGERPRINT) {
+                updateUdfpsController();
+            }
         }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index da6c163..997a6e5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.doze;
 
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
 import static com.android.systemui.doze.DozeLog.REASON_SENSOR_QUICK_PICKUP;
 import static com.android.systemui.doze.DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS;
 import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_DISPLAY;
@@ -29,6 +31,7 @@
 import android.hardware.SensorManager;
 import android.hardware.TriggerEvent;
 import android.hardware.TriggerEventListener;
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.net.Uri;
 import android.os.Handler;
@@ -835,13 +838,17 @@
 
     private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
         @Override
-        public void onAllAuthenticatorsRegistered() {
-            updateUdfpsEnrolled();
+        public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) {
+            if (modality == TYPE_FINGERPRINT) {
+                updateUdfpsEnrolled();
+            }
         }
 
         @Override
-        public void onEnrollmentsChanged() {
-            updateUdfpsEnrolled();
+        public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
+            if (modality == TYPE_FINGERPRINT) {
+                updateUdfpsEnrolled();
+            }
         }
 
         private void updateUdfpsEnrolled() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
index 7e4a108..823255c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -113,7 +113,7 @@
     }
 
     void setExtraStatusBarItemViews(List<View> views) {
-        mSystemStatusViewGroup.removeAllViews();
+        removeAllStatusBarItemViews();
         views.forEach(view -> mSystemStatusViewGroup.addView(view));
     }
 
@@ -121,4 +121,8 @@
         final View statusIcon = findViewById(resId);
         return Objects.requireNonNull(statusIcon);
     }
+
+    void removeAllStatusBarItemViews() {
+        mSystemStatusViewGroup.removeAllViews();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index 65cfae1..6f50550 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -192,6 +192,7 @@
         mDreamOverlayNotificationCountProvider.ifPresent(
                 provider -> provider.removeCallback(mNotificationCountCallback));
         mStatusBarItemsProvider.removeCallback(mStatusBarItemsProviderCallback);
+        mView.removeAllStatusBarItemViews();
         mTouchInsetSession.clear();
 
         mIsAttached = false;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
index 83249aa..bbcab60 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
@@ -69,7 +69,7 @@
         };
 
         mSecureSettings.registerContentObserverForUser(
-                Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS,
+                Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED,
                 settingsObserver,
                 UserHandle.myUserId());
         settingsObserver.onChange(false);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 4b409f5..29d6765 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -154,7 +154,11 @@
     public static final ReleasedFlag STATUS_BAR_LETTERBOX_APPEARANCE =
             new ReleasedFlag(603, false);
 
-    public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE = new UnreleasedFlag(604, true);
+    public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_BACKEND =
+            new UnreleasedFlag(604, false);
+
+    public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_FRONTEND =
+            new UnreleasedFlag(605, false);
 
     /***************************************/
     // 700 - dialer/calls
@@ -237,6 +241,7 @@
     // 1300 - screenshots
 
     public static final UnreleasedFlag SCREENSHOT_REQUEST_PROCESSOR = new UnreleasedFlag(1300);
+    public static final UnreleasedFlag SCREENSHOT_WORK_PROFILE_POLICY = new UnreleasedFlag(1301);
 
     // 1400 - columbus, b/242800729
     public static final UnreleasedFlag QUICK_TAP_IN_PCC = new UnreleasedFlag(1400);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index ca65d12..da5819a 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -90,6 +90,8 @@
 import android.widget.LinearLayout;
 import android.widget.ListPopupWindow;
 import android.widget.TextView;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
 
 import androidx.annotation.NonNull;
 import androidx.lifecycle.Lifecycle;
@@ -155,6 +157,8 @@
     public static final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
     public static final String SYSTEM_DIALOG_REASON_DREAM = "dream";
 
+    private static final boolean DEBUG = false;
+
     private static final String TAG = "GlobalActionsDialogLite";
 
     private static final String INTERACTION_JANK_TAG = "global_actions";
@@ -2177,6 +2181,11 @@
 
         protected ViewGroup mContainer;
 
+        private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
+            logOnBackInvocation();
+            dismiss();
+        };
+
         @VisibleForTesting
         protected GestureDetector.SimpleOnGestureListener mGestureListener =
                 new GestureDetector.SimpleOnGestureListener() {
@@ -2221,6 +2230,16 @@
                     }
                 };
 
+
+        // this exists so that we can point it to a mock during Unit Testing
+        private OnBackInvokedDispatcher mOverriddenBackDispatcher;
+
+        // the following method exists so that a Unit Test can supply a `OnBackInvokedDispatcher`
+        @VisibleForTesting
+        void setBackDispatcherOverride(OnBackInvokedDispatcher mockDispatcher) {
+            mOverriddenBackDispatcher = mockDispatcher;
+        }
+
         ActionsDialogLite(Context context, int themeRes, MyAdapter adapter,
                 MyOverflowAdapter overflowAdapter,
                 SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
@@ -2254,6 +2273,22 @@
             super.onCreate(savedInstanceState);
             initializeLayout();
             mWindowDimAmount = getWindow().getAttributes().dimAmount;
+            getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+                    OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback);
+            if (DEBUG) Log.d(TAG, "OnBackInvokedCallback handler registered");
+        }
+
+        @VisibleForTesting
+        @Override
+        public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
+            if (mOverriddenBackDispatcher != null) return mOverriddenBackDispatcher;
+            else return super.getOnBackInvokedDispatcher();
+        }
+
+        @Override
+        public void onDetachedFromWindow() {
+            getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
+            if (DEBUG) Log.d(TAG, "OnBackInvokedCallback handler unregistered");
         }
 
         @Override
@@ -2453,7 +2488,12 @@
         @Override
         public void onBackPressed() {
             super.onBackPressed();
+            logOnBackInvocation();
+        }
+
+        private void logOnBackInvocation() {
             mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_BACK);
+            if (DEBUG) Log.d(TAG, "onBack invoked");
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 3eb3c80..6dfbd42 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -323,6 +323,8 @@
         if (sEnableRemoteKeyguardOccludeAnimation) {
             Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_(UN)OCCLUDE");
             // Register for occluding
+            final RemoteTransition occludeTransition = new RemoteTransition(
+                    mOccludeAnimation, getIApplicationThread());
             TransitionFilter f = new TransitionFilter();
             f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED;
             f.mRequirements = new TransitionFilter.Requirement[]{
@@ -337,10 +339,11 @@
             f.mRequirements[1].mMustBeIndependent = false;
             f.mRequirements[1].mFlags = FLAG_OCCLUDES_KEYGUARD;
             f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
-            mShellTransitions.registerRemote(f,
-                    new RemoteTransition(mOccludeAnimation, getIApplicationThread()));
+            mShellTransitions.registerRemote(f, occludeTransition);
 
             // Now register for un-occlude.
+            final RemoteTransition unoccludeTransition = new RemoteTransition(
+                    mUnoccludeAnimation, getIApplicationThread());
             f = new TransitionFilter();
             f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED;
             f.mRequirements = new TransitionFilter.Requirement[]{
@@ -358,8 +361,23 @@
             f.mRequirements[0].mMustBeIndependent = false;
             f.mRequirements[0].mFlags = FLAG_OCCLUDES_KEYGUARD;
             f.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
-            mShellTransitions.registerRemote(f,
-                    new RemoteTransition(mUnoccludeAnimation, getIApplicationThread()));
+            mShellTransitions.registerRemote(f, unoccludeTransition);
+
+            // Register for specific transition type.
+            // Above filter cannot fulfill all conditions.
+            // E.g. close top activity while screen off but next activity is occluded, this should
+            // an occluded transition, but since the activity is invisible, the condition would
+            // match unoccluded transition.
+            // But on the contrary, if we add above condition in occluded transition, then when user
+            // trying to dismiss occluded activity when unlock keyguard, the condition would match
+            // occluded transition.
+            f = new TransitionFilter();
+            f.mTypeSet = new int[]{TRANSIT_KEYGUARD_OCCLUDE};
+            mShellTransitions.registerRemote(f, occludeTransition);
+
+            f = new TransitionFilter();
+            f.mTypeSet = new int[]{TRANSIT_KEYGUARD_UNOCCLUDE};
+            mShellTransitions.registerRemote(f, unoccludeTransition);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index b4f40e2..4a7346e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2377,10 +2377,10 @@
     private void handleHide() {
         Trace.beginSection("KeyguardViewMediator#handleHide");
 
-        // It's possible that the device was unlocked in a dream state. It's time to wake up.
-        if (mAodShowing || mDreamOverlayShowing) {
-            PowerManager pm = mContext.getSystemService(PowerManager.class);
-            pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+        // It's possible that the device was unlocked (via BOUNCER) while dozing. It's time to
+        // wake up.
+        if (mAodShowing) {
+            mPM.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
                     "com.android.systemui:BOUNCER_DOZING");
         }
 
@@ -2409,6 +2409,13 @@
                             null /* nonApps */, null /* finishedCallback */);
                 });
             }
+
+            // It's possible that the device was unlocked (via BOUNCER or Fingerprint) while
+            // dreaming. It's time to wake up.
+            if (mDreamOverlayShowing) {
+                mPM.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+                        "com.android.systemui:UNLOCK_DREAMING");
+            }
         }
         Trace.endSection();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 19c6249..c4e3d4e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -251,9 +251,21 @@
             Utils.getColorAttr(view.context, com.android.internal.R.attr.colorSurface)
 
         view.contentDescription = view.context.getString(viewModel.contentDescriptionResourceId)
-        view.setOnClickListener {
+        view.isClickable = viewModel.isClickable
+        if (viewModel.isClickable) {
+            view.setOnClickListener(OnClickListener(viewModel, falsingManager))
+        } else {
+            view.setOnClickListener(null)
+        }
+    }
+
+    private class OnClickListener(
+        private val viewModel: KeyguardQuickAffordanceViewModel,
+        private val falsingManager: FalsingManager,
+    ) : View.OnClickListener {
+        override fun onClick(view: View) {
             if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-                return@setOnClickListener
+                return
             }
 
             if (viewModel.configKey != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index 01d5e5c..e3ebac6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.doze.util.BurnInHelperWrapper
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -37,6 +38,23 @@
     private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
     private val burnInHelperWrapper: BurnInHelperWrapper,
 ) {
+    /**
+     * Whether quick affordances are "opaque enough" to be considered visible to and interactive by
+     * the user. If they are not interactive, user input should not be allowed on them.
+     *
+     * Note that there is a margin of error, where we allow very, very slightly transparent views to
+     * be considered "fully opaque" for the purpose of being interactive. This is to accommodate the
+     * error margin of floating point arithmetic.
+     *
+     * A view that is visible but with an alpha of less than our threshold either means it's not
+     * fully done fading in or is fading/faded out. Either way, it should not be
+     * interactive/clickable unless "fully opaque" to avoid issues like in b/241830987.
+     */
+    private val areQuickAffordancesFullyOpaque: Flow<Boolean> =
+        bottomAreaInteractor.alpha
+            .map { alpha -> alpha >= AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD }
+            .distinctUntilChanged()
+
     /** An observable for the view-model of the "start button" quick affordance. */
     val startButton: Flow<KeyguardQuickAffordanceViewModel> =
         button(KeyguardQuickAffordancePosition.BOTTOM_START)
@@ -77,14 +95,19 @@
         return combine(
                 quickAffordanceInteractor.quickAffordance(position),
                 bottomAreaInteractor.animateDozingTransitions.distinctUntilChanged(),
-            ) { model, animateReveal ->
-                model.toViewModel(animateReveal)
+                areQuickAffordancesFullyOpaque,
+            ) { model, animateReveal, isFullyOpaque ->
+                model.toViewModel(
+                    animateReveal = animateReveal,
+                    isClickable = isFullyOpaque,
+                )
             }
             .distinctUntilChanged()
     }
 
     private fun KeyguardQuickAffordanceModel.toViewModel(
         animateReveal: Boolean,
+        isClickable: Boolean,
     ): KeyguardQuickAffordanceViewModel {
         return when (this) {
             is KeyguardQuickAffordanceModel.Visible ->
@@ -100,8 +123,20 @@
                             animationController = parameters.animationController,
                         )
                     },
+                    isClickable = isClickable,
                 )
             is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
         }
     }
+
+    companion object {
+        // We select a value that's less than 1.0 because we want floating point math precision to
+        // not be a factor in determining whether the affordance UI is fully opaque. The number we
+        // choose needs to be close enough 1.0 such that the user can't easily tell the difference
+        // between the UI with an alpha at the threshold and when the alpha is 1.0. At the same
+        // time, we don't want the number to be too close to 1.0 such that there is a chance that we
+        // never treat the affordance UI as "fully opaque" as that would risk making it forever not
+        // clickable.
+        @VisibleForTesting const val AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD = 0.95f
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
index 985ab62..b1de27d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -31,6 +31,7 @@
     val icon: ContainedDrawable = ContainedDrawable.WithResource(0),
     @StringRes val contentDescriptionResourceId: Int = 0,
     val onClicked: (OnClickedParameters) -> Unit = {},
+    val isClickable: Boolean = false,
 ) {
     data class OnClickedParameters(
         val configKey: KClass<out KeyguardQuickAffordanceConfig>,
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 88a1b17..7b497ad 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.media
 
 import android.app.Notification
+import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
 import android.app.PendingIntent
 import android.app.smartspace.SmartspaceConfig
 import android.app.smartspace.SmartspaceManager
@@ -27,6 +28,7 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.graphics.Bitmap
 import android.graphics.ImageDecoder
@@ -57,8 +59,8 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
-import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
 import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
+import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
 import com.android.systemui.statusbar.notification.row.HybridGroupManager
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.Assert
@@ -633,9 +635,14 @@
         }
         val mediaController = mediaControllerFactory.create(token)
         val metadata = mediaController.metadata
+        val notif: Notification = sbn.notification
+
+        val appInfo = notif.extras.getParcelable(
+            Notification.EXTRA_BUILDER_APPLICATION_INFO,
+            ApplicationInfo::class.java
+        ) ?: getAppInfoFromPackage(sbn.packageName)
 
         // Album art
-        val notif: Notification = sbn.notification
         var artworkBitmap = metadata?.let { loadBitmapFromUri(it) }
         if (artworkBitmap == null) {
             artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ART)
@@ -650,8 +657,7 @@
         }
 
         // App name
-        val builder = Notification.Builder.recoverBuilder(context, notif)
-        val app = builder.loadHeaderAppName()
+        val appName = getAppName(sbn, appInfo)
 
         // App Icon
         val smallIcon = sbn.notification.smallIcon
@@ -712,12 +718,7 @@
 
         val currentEntry = mediaEntries.get(key)
         val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
-        val appUid = try {
-            context.packageManager.getApplicationInfo(sbn.packageName, 0)?.uid!!
-        } catch (e: PackageManager.NameNotFoundException) {
-            Log.w(TAG, "Could not get app UID for ${sbn.packageName}", e)
-            Process.INVALID_UID
-        }
+        val appUid = appInfo?.uid ?: Process.INVALID_UID
 
         if (logEvent) {
             logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation)
@@ -730,7 +731,7 @@
             val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
             val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
             val active = mediaEntries[key]?.active ?: true
-            onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, app,
+            onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, appName,
                     smallIcon, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed,
                     semanticActions, sbn.packageName, token, notif.contentIntent, device,
                     active, resumeAction = resumeAction, playbackLocation = playbackLocation,
@@ -740,6 +741,28 @@
         }
     }
 
+    private fun getAppInfoFromPackage(packageName: String): ApplicationInfo? {
+        try {
+            return context.packageManager.getApplicationInfo(packageName, 0)
+        } catch (e: PackageManager.NameNotFoundException) {
+            Log.w(TAG, "Could not get app info for $packageName", e)
+        }
+        return null
+    }
+
+    private fun getAppName(sbn: StatusBarNotification, appInfo: ApplicationInfo?): String {
+        val name = sbn.notification.extras.getString(EXTRA_SUBSTITUTE_APP_NAME)
+        if (name != null) {
+            return name
+        }
+
+        return if (appInfo != null) {
+            context.packageManager.getApplicationLabel(appInfo).toString()
+        } else {
+            sbn.packageName
+        }
+    }
+
     /**
      * Generate action buttons based on notification actions
      */
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index e360d10..ee59561 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.media.dialog;
 
+import android.annotation.DrawableRes;
 import android.content.res.ColorStateList;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
@@ -42,9 +43,6 @@
     private static final String TAG = "MediaOutputAdapter";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    private ViewGroup mConnectedItem;
-    private boolean mIncludeDynamicGroup;
-
     public MediaOutputAdapter(MediaOutputController controller) {
         super(controller);
         setHasStableIds(true);
@@ -102,141 +100,90 @@
         void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) {
             super.onBind(device, topMargin, bottomMargin, position);
             boolean isMutingExpectedDeviceExist = mController.hasMutingExpectedDevice();
-            final boolean currentlyConnected = !mIncludeDynamicGroup
-                    && isCurrentlyConnected(device);
+            final boolean currentlyConnected = isCurrentlyConnected(device);
             boolean isCurrentSeekbarInvisible = mSeekBar.getVisibility() == View.GONE;
-            if (currentlyConnected) {
-                mConnectedItem = mContainerLayout;
-            }
-            mCheckBox.setVisibility(View.GONE);
-            mStatusIcon.setVisibility(View.GONE);
-            mEndTouchArea.setVisibility(View.GONE);
-            mEndTouchArea.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
-            mContainerLayout.setOnClickListener(null);
-            mContainerLayout.setContentDescription(null);
-            mTitleText.setTextColor(mController.getColorItemContent());
-            mSubTitleText.setTextColor(mController.getColorItemContent());
-            mTwoLineTitleText.setTextColor(mController.getColorItemContent());
-            mSeekBar.getProgressDrawable().setColorFilter(
-                    new PorterDuffColorFilter(mController.getColorSeekbarProgress(),
-                            PorterDuff.Mode.SRC_IN));
             if (mCurrentActivePosition == position) {
                 mCurrentActivePosition = -1;
             }
 
-            if (mController.isTransferring()) {
+            if (mController.isAnyDeviceTransferring()) {
                 if (device.getState() == MediaDeviceState.STATE_CONNECTING
                         && !mController.hasAdjustVolumeUserRestriction()) {
                     setUpDeviceIcon(device);
-                    mProgressBar.getIndeterminateDrawable().setColorFilter(
-                            new PorterDuffColorFilter(
-                                    mController.getColorItemContent(),
-                                    PorterDuff.Mode.SRC_IN));
-                    setSingleLineLayout(getItemTitle(device), true /* bFocused */,
-                            false /* showSeekBar*/,
-                            true /* showProgressBar */, false /* showStatus */);
+                    updateProgressBarColor();
+                    setSingleLineLayout(getItemTitle(device), false /* showSeekBar*/,
+                            true /* showProgressBar */, false /* showCheckBox */,
+                            false /* showEndTouchArea */);
                 } else {
                     setUpDeviceIcon(device);
-                    setSingleLineLayout(getItemTitle(device), false /* bFocused */);
+                    setSingleLineLayout(getItemTitle(device));
                 }
             } else {
                 // Set different layout for each device
                 if (device.isMutingExpectedDevice()
                         && !mController.isCurrentConnectedDeviceRemote()) {
-                    mTitleIcon.setImageDrawable(
-                            mContext.getDrawable(R.drawable.media_output_icon_volume));
-                    mTitleIcon.setColorFilter(mController.getColorItemContent());
-                    mTitleText.setTextColor(mController.getColorItemContent());
-                    setSingleLineLayout(getItemTitle(device), true /* bFocused */,
-                            false /* showSeekBar */,
-                            false /* showProgressBar */, false /* showStatus */);
+                    updateTitleIcon(R.drawable.media_output_icon_volume,
+                            mController.getColorItemContent());
                     initMutingExpectedDevice();
                     mCurrentActivePosition = position;
-                    mContainerLayout.setOnClickListener(v -> onItemClick(v, device));
+                    updateContainerClickListener(v -> onItemClick(v, device));
+                    setSingleLineLayout(getItemTitle(device));
                 } else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
                     setUpDeviceIcon(device);
-                    mStatusIcon.setImageDrawable(
-                            mContext.getDrawable(R.drawable.media_output_status_failed));
-                    mStatusIcon.setColorFilter(mController.getColorItemContent());
-                    setTwoLineLayout(device, false /* bFocused */,
-                            false /* showSeekBar */, false /* showProgressBar */,
-                            true /* showSubtitle */, true /* showStatus */);
+                    updateConnectionFailedStatusIcon();
                     mSubTitleText.setText(R.string.media_output_dialog_connect_failed);
-                    mContainerLayout.setOnClickListener(v -> onItemClick(v, device));
+                    updateContainerClickListener(v -> onItemClick(v, device));
+                    setTwoLineLayout(device, false /* bFocused */, false /* showSeekBar */,
+                            false /* showProgressBar */, true /* showSubtitle */,
+                            true /* showStatus */);
                 } else if (device.getState() == MediaDeviceState.STATE_GROUPING) {
                     setUpDeviceIcon(device);
-                    mProgressBar.getIndeterminateDrawable().setColorFilter(
-                            new PorterDuffColorFilter(
-                                    mController.getColorItemContent(),
-                                    PorterDuff.Mode.SRC_IN));
-                    setSingleLineLayout(getItemTitle(device), true /* bFocused */,
-                            false /* showSeekBar*/,
-                            true /* showProgressBar */, false /* showStatus */);
+                    updateProgressBarColor();
+                    setSingleLineLayout(getItemTitle(device), false /* showSeekBar*/,
+                            true /* showProgressBar */, false /* showCheckBox */,
+                            false /* showEndTouchArea */);
                 } else if (mController.getSelectedMediaDevice().size() > 1
                         && isDeviceIncluded(mController.getSelectedMediaDevice(), device)) {
                     boolean isDeviceDeselectable = isDeviceIncluded(
                             mController.getDeselectableMediaDevice(), device);
-                    mTitleText.setTextColor(mController.getColorItemContent());
-                    mTitleIcon.setImageDrawable(
-                            mContext.getDrawable(R.drawable.media_output_icon_volume));
-                    mTitleIcon.setColorFilter(mController.getColorItemContent());
-                    setSingleLineLayout(getItemTitle(device), true /* bFocused */,
-                            true /* showSeekBar */,
-                            false /* showProgressBar */, false /* showStatus */);
+                    updateTitleIcon(R.drawable.media_output_icon_volume,
+                            mController.getColorItemContent());
+                    updateGroupableCheckBox(true, isDeviceDeselectable, device);
+                    updateEndClickArea(device, isDeviceDeselectable);
                     setUpContentDescriptionForView(mContainerLayout, false, device);
-                    mCheckBox.setOnCheckedChangeListener(null);
-                    mCheckBox.setVisibility(View.VISIBLE);
-                    mCheckBox.setChecked(true);
-                    mCheckBox.setOnCheckedChangeListener(isDeviceDeselectable
-                            ? (buttonView, isChecked) -> onGroupActionTriggered(false, device)
-                            : null);
-                    mCheckBox.setEnabled(isDeviceDeselectable);
-                    setCheckBoxColor(mCheckBox, mController.getColorItemContent());
+                    setSingleLineLayout(getItemTitle(device), true /* showSeekBar */,
+                            false /* showProgressBar */, true /* showCheckBox */,
+                            true /* showEndTouchArea */);
                     initSeekbar(device, isCurrentSeekbarInvisible);
-                    mEndTouchArea.setVisibility(View.VISIBLE);
-                    mEndTouchArea.setOnClickListener(null);
-                    mEndTouchArea.setOnClickListener(
-                            isDeviceDeselectable ? (v) -> mCheckBox.performClick() : null);
-                    mEndTouchArea.setImportantForAccessibility(
-                            View.IMPORTANT_FOR_ACCESSIBILITY_YES);
-                    setUpContentDescriptionForView(mEndTouchArea, true, device);
                 } else if (!mController.hasAdjustVolumeUserRestriction()
                         && currentlyConnected) {
                     if (isMutingExpectedDeviceExist
                             && !mController.isCurrentConnectedDeviceRemote()) {
                         // mark as disconnected and set special click listener
                         setUpDeviceIcon(device);
-                        setSingleLineLayout(getItemTitle(device), false /* bFocused */);
-                        mContainerLayout.setOnClickListener(v -> cancelMuteAwaitConnection());
+                        updateContainerClickListener(v -> cancelMuteAwaitConnection());
+                        setSingleLineLayout(getItemTitle(device));
                     } else {
-                        mTitleIcon.setImageDrawable(
-                                mContext.getDrawable(R.drawable.media_output_icon_volume));
-                        mTitleIcon.setColorFilter(mController.getColorItemContent());
-                        mTitleText.setTextColor(mController.getColorItemContent());
-                        setSingleLineLayout(getItemTitle(device), true /* bFocused */,
-                                true /* showSeekBar */,
-                                false /* showProgressBar */, false /* showStatus */);
-                        initSeekbar(device, isCurrentSeekbarInvisible);
+                        updateTitleIcon(R.drawable.media_output_icon_volume,
+                                mController.getColorItemContent());
                         setUpContentDescriptionForView(mContainerLayout, false, device);
                         mCurrentActivePosition = position;
+                        setSingleLineLayout(getItemTitle(device), true /* showSeekBar */,
+                                false /* showProgressBar */, false /* showCheckBox */,
+                                false /* showEndTouchArea */);
+                        initSeekbar(device, isCurrentSeekbarInvisible);
                     }
                 } else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
                     setUpDeviceIcon(device);
-                    mCheckBox.setOnCheckedChangeListener(null);
-                    mCheckBox.setVisibility(View.VISIBLE);
-                    mCheckBox.setChecked(false);
-                    mCheckBox.setOnCheckedChangeListener(
-                            (buttonView, isChecked) -> onGroupActionTriggered(true, device));
-                    mEndTouchArea.setVisibility(View.VISIBLE);
-                    mContainerLayout.setOnClickListener(v -> onGroupActionTriggered(true, device));
-                    setCheckBoxColor(mCheckBox, mController.getColorItemContent());
-                    setSingleLineLayout(getItemTitle(device), false /* bFocused */,
-                            false /* showSeekBar */,
-                            false /* showProgressBar */, false /* showStatus */);
+                    updateGroupableCheckBox(false, true, device);
+                    updateContainerClickListener(v -> onGroupActionTriggered(true, device));
+                    setSingleLineLayout(getItemTitle(device), false /* showSeekBar */,
+                            false /* showProgressBar */, true /* showCheckBox */,
+                            true /* showEndTouchArea */);
                 } else {
                     setUpDeviceIcon(device);
-                    setSingleLineLayout(getItemTitle(device), false /* bFocused */);
-                    mContainerLayout.setOnClickListener(v -> onItemClick(v, device));
+                    setSingleLineLayout(getItemTitle(device));
+                    updateContainerClickListener(v -> onItemClick(v, device));
                 }
             }
         }
@@ -248,15 +195,56 @@
                     ColorStateList(states, colors));
         }
 
+        private void updateConnectionFailedStatusIcon() {
+            mStatusIcon.setImageDrawable(
+                    mContext.getDrawable(R.drawable.media_output_status_failed));
+            mStatusIcon.setColorFilter(mController.getColorItemContent());
+        }
+
+        private void updateProgressBarColor() {
+            mProgressBar.getIndeterminateDrawable().setColorFilter(
+                    new PorterDuffColorFilter(
+                            mController.getColorItemContent(),
+                            PorterDuff.Mode.SRC_IN));
+        }
+
+        public void updateEndClickArea(MediaDevice device, boolean isDeviceDeselectable) {
+            mEndTouchArea.setOnClickListener(null);
+            mEndTouchArea.setOnClickListener(
+                    isDeviceDeselectable ? (v) -> mCheckBox.performClick() : null);
+            mEndTouchArea.setImportantForAccessibility(
+                    View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+            setUpContentDescriptionForView(mEndTouchArea, true, device);
+        }
+
+        private void updateGroupableCheckBox(boolean isSelected, boolean isGroupable,
+                MediaDevice device) {
+            mCheckBox.setOnCheckedChangeListener(null);
+            mCheckBox.setChecked(isSelected);
+            mCheckBox.setOnCheckedChangeListener(
+                    isGroupable ? (buttonView, isChecked) -> onGroupActionTriggered(!isSelected,
+                            device) : null);
+            mCheckBox.setEnabled(isGroupable);
+            setCheckBoxColor(mCheckBox, mController.getColorItemContent());
+        }
+
+        private void updateTitleIcon(@DrawableRes int id, int color) {
+            mTitleIcon.setImageDrawable(mContext.getDrawable(id));
+            mTitleIcon.setColorFilter(color);
+        }
+
+        private void updateContainerClickListener(View.OnClickListener listener) {
+            mContainerLayout.setOnClickListener(listener);
+        }
+
         @Override
         void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
             if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) {
                 mTitleText.setTextColor(mController.getColorItemContent());
                 mCheckBox.setVisibility(View.GONE);
-                setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new),
-                        false /* bFocused */);
-                final Drawable d = mContext.getDrawable(R.drawable.ic_add);
-                mTitleIcon.setImageDrawable(d);
+                setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new));
+                final Drawable addDrawable = mContext.getDrawable(R.drawable.ic_add);
+                mTitleIcon.setImageDrawable(addDrawable);
                 mTitleIcon.setColorFilter(mController.getColorItemContent());
                 mContainerLayout.setOnClickListener(mController::launchBluetoothPairing);
             }
@@ -273,7 +261,7 @@
         }
 
         private void onItemClick(View view, MediaDevice device) {
-            if (mController.isTransferring()) {
+            if (mController.isAnyDeviceTransferring()) {
                 return;
             }
             if (isCurrentlyConnected(device)) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 43b0287..3f7b226 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -63,8 +63,6 @@
 
     protected final MediaOutputController mController;
 
-    private int mMargin;
-
     Context mContext;
     View mHolderView;
     boolean mIsDragging;
@@ -82,8 +80,6 @@
     public MediaDeviceBaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
             int viewType) {
         mContext = viewGroup.getContext();
-        mMargin = mContext.getResources().getDimensionPixelSize(
-                R.dimen.media_output_dialog_list_margin);
         mHolderView = LayoutInflater.from(mContext).inflate(R.layout.media_output_list_item,
                 viewGroup, false);
 
@@ -168,16 +164,28 @@
 
         void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) {
             mDeviceId = device.getId();
+            mCheckBox.setVisibility(View.GONE);
+            mStatusIcon.setVisibility(View.GONE);
+            mEndTouchArea.setVisibility(View.GONE);
+            mEndTouchArea.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+            mContainerLayout.setOnClickListener(null);
+            mContainerLayout.setContentDescription(null);
+            mTitleText.setTextColor(mController.getColorItemContent());
+            mSubTitleText.setTextColor(mController.getColorItemContent());
+            mTwoLineTitleText.setTextColor(mController.getColorItemContent());
+            mSeekBar.getProgressDrawable().setColorFilter(
+                    new PorterDuffColorFilter(mController.getColorSeekbarProgress(),
+                            PorterDuff.Mode.SRC_IN));
         }
 
         abstract void onBind(int customizedItem, boolean topMargin, boolean bottomMargin);
 
-        void setSingleLineLayout(CharSequence title, boolean bFocused) {
-            setSingleLineLayout(title, bFocused, false, false, false);
+        void setSingleLineLayout(CharSequence title) {
+            setSingleLineLayout(title, false, false, false, false);
         }
 
-        void setSingleLineLayout(CharSequence title, boolean bFocused, boolean showSeekBar,
-                boolean showProgressBar, boolean showStatus) {
+        void setSingleLineLayout(CharSequence title, boolean showSeekBar,
+                boolean showProgressBar, boolean showCheckBox, boolean showEndTouchArea) {
             mTwoLineLayout.setVisibility(View.GONE);
             boolean isActive = showSeekBar || showProgressBar;
             if (!mCornerAnimator.isRunning()) {
@@ -188,10 +196,6 @@
                                 .mutate() : mContext.getDrawable(
                                         R.drawable.media_output_item_background)
                                 .mutate();
-                backgroundDrawable.setColorFilter(new PorterDuffColorFilter(
-                        isActive ? mController.getColorConnectedItemBackground()
-                                : mController.getColorItemBackground(),
-                        PorterDuff.Mode.SRC_IN));
                 mItemLayout.setBackground(backgroundDrawable);
                 if (showSeekBar) {
                     final ClipDrawable clipDrawable =
@@ -201,27 +205,21 @@
                             (GradientDrawable) clipDrawable.getDrawable();
                     progressDrawable.setCornerRadius(mController.getActiveRadius());
                 }
-            } else {
-                mItemLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
-                        isActive ? mController.getColorConnectedItemBackground()
-                                : mController.getColorItemBackground(),
-                        PorterDuff.Mode.SRC_IN));
             }
+            mItemLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
+                    isActive ? mController.getColorConnectedItemBackground()
+                            : mController.getColorItemBackground(),
+                    PorterDuff.Mode.SRC_IN));
             mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
             mSeekBar.setAlpha(1);
             mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE);
             if (!showSeekBar) {
                 mSeekBar.resetVolume();
             }
-            mStatusIcon.setVisibility(showStatus ? View.VISIBLE : View.GONE);
             mTitleText.setText(title);
             mTitleText.setVisibility(View.VISIBLE);
-        }
-
-        void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar,
-                boolean showProgressBar, boolean showSubtitle) {
-            setTwoLineLayout(device, null, bFocused, showSeekBar, showProgressBar, showSubtitle,
-                    false);
+            mCheckBox.setVisibility(showCheckBox ? View.VISIBLE : View.GONE);
+            mEndTouchArea.setVisibility(showEndTouchArea ? View.VISIBLE : View.GONE);
         }
 
         void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar,
@@ -230,12 +228,6 @@
                     showStatus);
         }
 
-        void setTwoLineLayout(CharSequence title, boolean bFocused, boolean showSeekBar,
-                boolean showProgressBar, boolean showSubtitle) {
-            setTwoLineLayout(null, title, bFocused, showSeekBar, showProgressBar, showSubtitle,
-                    false);
-        }
-
         private void setTwoLineLayout(MediaDevice device, CharSequence title, boolean bFocused,
                 boolean showSeekBar, boolean showProgressBar, boolean showSubtitle,
                 boolean showStatus) {
@@ -254,20 +246,11 @@
             mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
             mSubTitleText.setVisibility(showSubtitle ? View.VISIBLE : View.GONE);
             mTwoLineTitleText.setTranslationY(0);
-            if (device == null) {
-                mTwoLineTitleText.setText(title);
-            } else {
-                mTwoLineTitleText.setText(getItemTitle(device));
-            }
-
-            if (bFocused) {
-                mTwoLineTitleText.setTypeface(Typeface.create(mContext.getString(
-                                com.android.internal.R.string.config_headlineFontFamilyMedium),
-                        Typeface.NORMAL));
-            } else {
-                mTwoLineTitleText.setTypeface(Typeface.create(mContext.getString(
-                        com.android.internal.R.string.config_headlineFontFamily), Typeface.NORMAL));
-            }
+            mTwoLineTitleText.setText(device == null ? title : getItemTitle(device));
+            mTwoLineTitleText.setTypeface(Typeface.create(mContext.getString(
+                            bFocused ? com.android.internal.R.string.config_headlineFontFamilyMedium
+                                    : com.android.internal.R.string.config_headlineFontFamily),
+                    Typeface.NORMAL));
         }
 
         void initSeekbar(MediaDevice device, boolean isCurrentSeekbarInvisible) {
@@ -327,35 +310,6 @@
             mItemLayout.setBackground(backgroundDrawable);
         }
 
-        void initSessionSeekbar() {
-            disableSeekBar();
-            mSeekBar.setMax(mController.getSessionVolumeMax());
-            mSeekBar.setMin(0);
-            final int currentVolume = mController.getSessionVolume();
-            if (mSeekBar.getProgress() != currentVolume) {
-                mSeekBar.setProgress(currentVolume, true);
-            }
-            mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
-                @Override
-                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-                    if (!fromUser) {
-                        return;
-                    }
-                    mController.adjustSessionVolume(progress);
-                }
-
-                @Override
-                public void onStartTrackingTouch(SeekBar seekBar) {
-                    mIsDragging = true;
-                }
-
-                @Override
-                public void onStopTrackingTouch(SeekBar seekBar) {
-                    mIsDragging = false;
-                }
-            });
-        }
-
         private void animateCornerAndVolume(int fromProgress, int toProgress) {
             final GradientDrawable layoutBackgroundDrawable =
                     (GradientDrawable) mItemLayout.getBackground();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index f7d80e0..96817c9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -411,7 +411,7 @@
                 device.getId());
         boolean isSelectedDeviceInGroup = getSelectedMediaDevice().size() > 1
                 && getSelectedMediaDevice().contains(device);
-        return (!hasAdjustVolumeUserRestriction() && isConnected && !isTransferring())
+        return (!hasAdjustVolumeUserRestriction() && isConnected && !isAnyDeviceTransferring())
                 || isSelectedDeviceInGroup;
     }
 
@@ -708,7 +708,7 @@
                 UserHandle.of(UserHandle.myUserId()));
     }
 
-    boolean isTransferring() {
+    boolean isAnyDeviceTransferring() {
         synchronized (mMediaDevicesLock) {
             for (MediaDevice device : mMediaDevices) {
                 if (device.getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index 2278938..3a0ac1b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -226,7 +226,7 @@
 
         appIconView.contentDescription = appNameOverride ?: iconInfo.iconName
         appIconView.setImageDrawable(appIconDrawableOverride ?: iconInfo.icon)
-        return appIconView.contentDescription.toString()
+        return appIconView.contentDescription
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 196ea22..00a22f2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -141,12 +141,13 @@
 
     override fun updateChipView(newChipInfo: ChipReceiverInfo, currentChipView: ViewGroup) {
         super.updateChipView(newChipInfo, currentChipView)
-        setIcon(
+        val iconName = setIcon(
                 currentChipView,
                 newChipInfo.routeInfo.clientPackageName,
                 newChipInfo.appIconDrawableOverride,
                 newChipInfo.appNameOverride
         )
+        currentChipView.contentDescription = iconName
     }
 
     override fun animateChipIn(chipView: ViewGroup) {
@@ -159,6 +160,8 @@
                 .alpha(1f)
                 .setDuration(5.frames)
                 .start()
+        // Using withEndAction{} doesn't apply a11y focus when screen is unlocked.
+        appIconView.postOnAnimation { chipView.requestAccessibilityFocus() }
         startRipple(chipView.requireViewById(R.id.ripple))
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 6ac3ead..7c4c64c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -814,8 +814,10 @@
         }
         mLogGesture = false;
         String logPackageName = "";
+        Map<String, Integer> vocab = mVocab;
         // Due to privacy, only top 100 most used apps by all users can be logged.
-        if (mUseMLModel && mVocab.containsKey(mPackageName) && mVocab.get(mPackageName) < 100) {
+        if (mUseMLModel && vocab != null && vocab.containsKey(mPackageName)
+                && vocab.get(mPackageName) < 100) {
             logPackageName = mPackageName;
         }
         SysUiStatsLog.write(SysUiStatsLog.BACK_GESTURE_REPORTED_REPORTED, backType,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index eeb1010..2a6cf66 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -80,7 +80,8 @@
             FeatureFlags featureFlags,
             VariableDateViewController.Factory variableDateViewControllerFactory,
             BatteryMeterViewController batteryMeterViewController,
-            StatusBarContentInsetsProvider statusBarContentInsetsProvider) {
+            StatusBarContentInsetsProvider statusBarContentInsetsProvider,
+            StatusBarIconController.TintedIconManager.Factory tintedIconManagerFactory) {
         super(view);
         mPrivacyIconsController = headerPrivacyIconsController;
         mStatusBarIconController = statusBarIconController;
@@ -103,7 +104,7 @@
                 mView.requireViewById(R.id.date_clock)
         );
 
-        mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer, featureFlags);
+        mIconManager = tintedIconManagerFactory.create(mIconContainer);
         mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
         mColorExtractor = colorExtractor;
         mOnColorsChangedListener = (extractor, which) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 948fb14..6038006 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -129,6 +129,15 @@
         })
     }
 
+    fun logInternetTileUpdate(lastType: Int, callback: String) {
+        log(VERBOSE, {
+            int1 = lastType
+            str1 = callback
+        }, {
+            "mLastTileState=$int1, Callback=$str1."
+        })
+    }
+
     fun logTileUpdated(tileSpec: String, state: QSTile.State) {
         log(VERBOSE, {
             str1 = tileSpec
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 170fecf..ae46477 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -369,11 +369,10 @@
             mWifiInfo.mNoDefaultNetwork = noDefaultNetwork;
             mWifiInfo.mNoValidatedNetwork = noValidatedNetwork;
             mWifiInfo.mNoNetworksAvailable = noNetworksAvailable;
-            if (mLastTileState == LAST_STATE_WIFI) {
-                refreshState(mWifiInfo);
-            } else {
-                refreshState(mCellularInfo);
+            if (!noDefaultNetwork) {
+                return;
             }
+            refreshState(mWifiInfo);
         }
 
         @Override
@@ -388,6 +387,7 @@
 
     @Override
     protected void handleUpdateState(SignalState state, Object arg) {
+        mQSLogger.logInternetTileUpdate(mLastTileState, arg == null ? "null" : arg.toString());
         if (arg instanceof CellularCallbackInfo) {
             mLastTileState = LAST_STATE_CELLULAR;
             handleUpdateCellularState(state, arg);
@@ -605,4 +605,9 @@
         pw.print("    "); pw.println("mLastTileState=" + mLastTileState);
         pw.print("    "); pw.println("mSignalCallback=" + mSignalCallback.toString());
     }
+
+    // For testing usage only.
+    protected int getLastTileState() {
+        return mLastTileState;
+    }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
similarity index 60%
copy from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
copy to packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
index 8dde897..f7c4dad 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
+/**
+ * Copyright (c) 2009, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spaprivileged.framework.app
+package com.android.systemui.screenshot;
 
-import android.content.pm.ApplicationInfo
+/** Interface implemented by ScreenshotProxyService */
+interface IScreenshotProxy {
 
-interface AppRecord {
-    val app: ApplicationInfo
-}
+    /** Is the notification shade currently exanded? */
+    boolean isNotificationShadeExpanded();
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt
index 39f35a5..7779760 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt
@@ -22,5 +22,5 @@
 
     fun captureDisplay(displayId: Int, crop: Rect? = null): Bitmap?
 
-    fun captureTask(taskId: Int): Bitmap?
+    suspend fun captureTask(taskId: Int): Bitmap?
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt
index 258c436..246265b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt
@@ -27,13 +27,19 @@
 import android.view.SurfaceControl.DisplayCaptureArgs
 import android.view.SurfaceControl.ScreenshotHardwareBuffer
 import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
 
 private const val TAG = "ImageCaptureImpl"
 
+@SysUISingleton
 open class ImageCaptureImpl @Inject constructor(
     private val displayManager: DisplayManager,
-    private val atmService: IActivityTaskManager
+    private val atmService: IActivityTaskManager,
+    @Background private val bgContext: CoroutineDispatcher
 ) : ImageCapture {
 
     override fun captureDisplay(displayId: Int, crop: Rect?): Bitmap? {
@@ -46,8 +52,8 @@
         return buffer?.asBitmap()
     }
 
-    override fun captureTask(taskId: Int): Bitmap? {
-        val snapshot = atmService.takeTaskSnapshot(taskId)
+    override suspend fun captureTask(taskId: Int): Bitmap? {
+        val snapshot = withContext(bgContext) { atmService.takeTaskSnapshot(taskId) } ?: return null
         return Bitmap.wrapHardwareBuffer(snapshot.hardwareBuffer, snapshot.colorSpace)
     }
 
@@ -67,12 +73,17 @@
     }
 
     @VisibleForTesting
-    open fun captureDisplay(displayToken: IBinder, width: Int, height: Int, crop: Rect): ScreenshotHardwareBuffer? {
-        val captureArgs = DisplayCaptureArgs.Builder(displayToken)
-            .setSize(width, height)
-            .setSourceCrop(crop)
-            .build()
+    open fun captureDisplay(
+        displayToken: IBinder,
+        width: Int,
+        height: Int,
+        crop: Rect
+    ): ScreenshotHardwareBuffer? {
+        val captureArgs =
+            DisplayCaptureArgs.Builder(displayToken)
+                .setSize(width, height)
+                .setSourceCrop(crop)
+                .build()
         return SurfaceControl.captureDisplay(captureArgs)
     }
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index 4397d3d..a918e5d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -16,51 +16,84 @@
 
 package com.android.systemui.screenshot
 
-import android.net.Uri
-import android.util.Log
-import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+import android.graphics.Insets
 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
 import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
 import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
 import java.util.function.Consumer
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 
 /**
  * Processes a screenshot request sent from {@link ScreenshotHelper}.
  */
 @SysUISingleton
-internal class RequestProcessor @Inject constructor(
-    private val controller: ScreenshotController,
+class RequestProcessor @Inject constructor(
+    private val capture: ImageCapture,
+    private val policy: ScreenshotPolicy,
+    private val flags: FeatureFlags,
+    /** For the Java Async version, to invoke the callback. */
+    @Application private val mainScope: CoroutineScope
 ) {
-    fun processRequest(
-        request: ScreenshotRequest,
-        onSavedListener: Consumer<Uri>,
-        callback: RequestCallback
-    ) {
+    /**
+     * Inspects the incoming request, returning a potentially modified request depending on policy.
+     *
+     * @param request the request to process
+     */
+    suspend fun process(request: ScreenshotRequest): ScreenshotRequest {
+        var result = request
 
-        if (request.type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
-            val image = HardwareBitmapBundler.bundleToHardwareBitmap(request.bitmapBundle)
+        // Apply work profile screenshots policy:
+        //
+        // If the focused app belongs to a work profile, transforms a full screen
+        // (or partial) screenshot request to a task snapshot (provided image) screenshot.
 
-            controller.handleImageAsScreenshot(
-                image, request.boundsInScreen, request.insets,
-                request.taskId, request.userId, request.topComponent, onSavedListener, callback
-            )
-            return
+        // Whenever displayContentInfo is fetched, the topComponent is also populated
+        // regardless of the managed profile status.
+
+        if (request.type != TAKE_SCREENSHOT_PROVIDED_IMAGE &&
+            flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+        ) {
+
+            val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
+
+            result = if (policy.isManagedProfile(info.userId)) {
+                val image = capture.captureTask(info.taskId)
+                    ?: error("Task snapshot returned a null Bitmap!")
+
+                // Provide the task snapshot as the screenshot
+                ScreenshotRequest(
+                    TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source,
+                    HardwareBitmapBundler.hardwareBitmapToBundle(image),
+                    info.bounds, Insets.NONE, info.taskId, info.userId, info.component
+                )
+            } else {
+                // Create a new request of the same type which includes the top component
+                ScreenshotRequest(request.source, request.type, info.component)
+            }
         }
 
-        when (request.type) {
-            TAKE_SCREENSHOT_FULLSCREEN ->
-                controller.takeScreenshotFullscreen(null, onSavedListener, callback)
-            TAKE_SCREENSHOT_SELECTED_REGION ->
-                controller.takeScreenshotPartial(null, onSavedListener, callback)
-            else -> Log.w(TAG, "Invalid screenshot option: ${request.type}")
-        }
+        return result
     }
 
-    companion object {
-        const val TAG: String = "RequestProcessor"
+    /**
+     * Note: This is for compatibility with existing Java. Prefer the suspending function when
+     * calling from a Coroutine context.
+     *
+     * @param request the request to process
+     * @param callback the callback to provide the processed request, invoked from the main thread
+     */
+    fun processAsync(request: ScreenshotRequest, callback: Consumer<ScreenshotRequest>) {
+        mainScope.launch {
+            val result = process(request)
+            callback.accept(result)
+        }
     }
 }
+
+private const val TAG = "RequestProcessor"
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt
new file mode 100644
index 0000000..3580010
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.annotation.UserIdInt
+import android.content.ComponentName
+import android.graphics.Rect
+import android.view.Display
+
+/**
+ * Provides policy decision-making information to screenshot request handling.
+ */
+interface ScreenshotPolicy {
+
+    /** @return true if the user is a managed profile (a.k.a. work profile) */
+    suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean
+
+    /**
+     * Requests information about the owner of display content which occupies a majority of the
+     * screenshot and/or has most recently been interacted with at the time the screenshot was
+     * requested.
+     *
+     * @param displayId the id of the display to inspect
+     * @return content info for the primary content on the display
+     */
+    suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo
+
+    data class DisplayContentInfo(
+        val component: ComponentName,
+        val bounds: Rect,
+        @UserIdInt val userId: Int,
+        val taskId: Int,
+    )
+
+    fun getDefaultDisplayId(): Int = Display.DEFAULT_DISPLAY
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
new file mode 100644
index 0000000..ba809f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.annotation.UserIdInt
+import android.app.ActivityTaskManager
+import android.app.ActivityTaskManager.RootTaskInfo
+import android.app.IActivityTaskManager
+import android.app.WindowConfiguration
+import android.app.WindowConfiguration.activityTypeToString
+import android.app.WindowConfiguration.windowingModeToString
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Rect
+import android.os.Process
+import android.os.RemoteException
+import android.os.UserManager
+import android.util.Log
+import android.view.Display.DEFAULT_DISPLAY
+import com.android.internal.infra.ServiceConnector
+import com.android.systemui.SystemUIService
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
+import java.util.Arrays
+import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+internal class ScreenshotPolicyImpl @Inject constructor(
+    context: Context,
+    private val userMgr: UserManager,
+    private val atmService: IActivityTaskManager,
+    @Background val bgDispatcher: CoroutineDispatcher,
+) : ScreenshotPolicy {
+
+    private val systemUiContent =
+        DisplayContentInfo(
+            ComponentName(context, SystemUIService::class.java),
+            Rect(),
+            ActivityTaskManager.INVALID_TASK_ID,
+            Process.myUserHandle().identifier,
+        )
+
+    private val proxyConnector: ServiceConnector<IScreenshotProxy> =
+        ServiceConnector.Impl(
+            context,
+            Intent(context, ScreenshotProxyService::class.java),
+            Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
+            context.userId,
+            IScreenshotProxy.Stub::asInterface
+        )
+
+    override fun getDefaultDisplayId(): Int {
+        return DEFAULT_DISPLAY
+    }
+
+    override suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean {
+        return withContext(bgDispatcher) { userMgr.isManagedProfile(userId) }
+    }
+
+    private fun nonPipVisibleTask(info: RootTaskInfo): Boolean {
+        return info.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED &&
+            info.isVisible &&
+            info.isRunning &&
+            info.numActivities > 0 &&
+            info.topActivity != null &&
+            info.childTaskIds.isNotEmpty()
+    }
+
+    /**
+     * Uses RootTaskInfo from ActivityTaskManager to guess at the primary focused task within a
+     * display. If no task is visible or the top task is covered by a system window, the info
+     * reported will reference a SystemUI component instead.
+     */
+    override suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo {
+        // Determine if the notification shade is expanded. If so, task windows are not
+        // visible behind it, so the screenshot should instead be associated with SystemUI.
+        if (isNotificationShadeExpanded()) {
+            return systemUiContent
+        }
+
+        val taskInfoList = getAllRootTaskInfosOnDisplay(displayId)
+        if (DEBUG) {
+            debugLogRootTaskInfos(taskInfoList)
+        }
+
+        // If no visible task is located, then report SystemUI as the foreground content
+        val target = taskInfoList.firstOrNull(::nonPipVisibleTask) ?: return systemUiContent
+
+        val topActivity: ComponentName = target.topActivity ?: error("should not be null")
+        val topChildTask = target.childTaskIds.size - 1
+        val childTaskId = target.childTaskIds[topChildTask]
+        val childTaskUserId = target.childTaskUserIds[topChildTask]
+        val childTaskBounds = target.childTaskBounds[topChildTask]
+
+        return DisplayContentInfo(topActivity, childTaskBounds, childTaskId, childTaskUserId)
+    }
+
+    private fun debugLogRootTaskInfos(taskInfoList: List<RootTaskInfo>) {
+        for (info in taskInfoList) {
+            Log.d(
+                TAG,
+                "[root task info] " +
+                    "taskId=${info.taskId} " +
+                    "parentTaskId=${info.parentTaskId} " +
+                    "position=${info.position} " +
+                    "positionInParent=${info.positionInParent} " +
+                    "isVisible=${info.isVisible()} " +
+                    "visible=${info.visible} " +
+                    "isFocused=${info.isFocused} " +
+                    "isSleeping=${info.isSleeping} " +
+                    "isRunning=${info.isRunning} " +
+                    "windowMode=${windowingModeToString(info.windowingMode)} " +
+                    "activityType=${activityTypeToString(info.activityType)} " +
+                    "topActivity=${info.topActivity} " +
+                    "topActivityInfo=${info.topActivityInfo} " +
+                    "numActivities=${info.numActivities} " +
+                    "childTaskIds=${Arrays.toString(info.childTaskIds)} " +
+                    "childUserIds=${Arrays.toString(info.childTaskUserIds)} " +
+                    "childTaskBounds=${Arrays.toString(info.childTaskBounds)} " +
+                    "childTaskNames=${Arrays.toString(info.childTaskNames)}"
+            )
+
+            for (j in 0 until info.childTaskIds.size) {
+                Log.d(TAG, "    *** [$j] ******")
+                Log.d(TAG, "        ***  childTaskIds[$j]: ${info.childTaskIds[j]}")
+                Log.d(TAG, "        ***  childTaskUserIds[$j]: ${info.childTaskUserIds[j]}")
+                Log.d(TAG, "        ***  childTaskBounds[$j]: ${info.childTaskBounds[j]}")
+                Log.d(TAG, "        ***  childTaskNames[$j]: ${info.childTaskNames[j]}")
+            }
+        }
+    }
+
+    private suspend fun getAllRootTaskInfosOnDisplay(displayId: Int): List<RootTaskInfo> =
+        withContext(bgDispatcher) {
+            try {
+                atmService.getAllRootTaskInfosOnDisplay(displayId)
+            } catch (e: RemoteException) {
+                Log.e(TAG, "getAllRootTaskInfosOnDisplay", e)
+                listOf()
+            }
+        }
+
+    private suspend fun isNotificationShadeExpanded(): Boolean = suspendCoroutine { k ->
+        proxyConnector
+            .postForResult { it.isNotificationShadeExpanded }
+            .whenComplete { expanded, error ->
+                if (error != null) {
+                    Log.e(TAG, "isNotificationShadeExpanded", error)
+                }
+                k.resume(expanded ?: false)
+            }
+    }
+
+    companion object {
+        const val TAG: String = "ScreenshotPolicyImpl"
+        const val DEBUG: Boolean = false
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
new file mode 100644
index 0000000..9654e03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.screenshot
+
+import android.app.Service
+import android.content.Intent
+import android.os.IBinder
+import android.util.Log
+import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
+import javax.inject.Inject
+
+/**
+ * Provides state from the main SystemUI process on behalf of the Screenshot process.
+ */
+internal class ScreenshotProxyService @Inject constructor(
+    private val mExpansionMgr: PanelExpansionStateManager
+) : Service() {
+
+    private val mBinder: IBinder = object : IScreenshotProxy.Stub() {
+        /**
+         * @return true when the notification shade is partially or fully expanded.
+         */
+        override fun isNotificationShadeExpanded(): Boolean {
+            val expanded = !mExpansionMgr.isClosed()
+            Log.d(TAG, "isNotificationShadeExpanded(): $expanded")
+            return expanded
+        }
+    }
+
+    override fun onBind(intent: Intent): IBinder? {
+        Log.d(TAG, "onBind: $intent")
+        return mBinder
+    }
+
+    companion object {
+        const val TAG = "ScreenshotProxyService"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 7bf3217..35f32ca 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -22,6 +22,7 @@
 import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_PROCESS_COMPLETE;
 import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_URI;
 import static com.android.systemui.flags.Flags.SCREENSHOT_REQUEST_PROCESSOR;
+import static com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_SERVICE;
@@ -52,8 +53,7 @@
 import android.view.WindowManager;
 import android.widget.Toast;
 
-import androidx.annotation.NonNull;
-
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.util.ScreenshotHelper;
 import com.android.systemui.R;
@@ -69,7 +69,7 @@
 public class TakeScreenshotService extends Service {
     private static final String TAG = logTag(TakeScreenshotService.class);
 
-    private ScreenshotController mScreenshot;
+    private final ScreenshotController mScreenshot;
 
     private final UserManager mUserManager;
     private final DevicePolicyManager mDevicePolicyManager;
@@ -97,7 +97,7 @@
     };
 
     /** Informs about coarse grained state of the Controller. */
-    interface RequestCallback {
+    public interface RequestCallback {
         /** Respond to the current request indicating the screenshot request failed. */
         void reportError();
 
@@ -124,6 +124,7 @@
         mBgExecutor = bgExecutor;
         mFeatureFlags = featureFlags;
         mFeatureFlags.addListener(SCREENSHOT_REQUEST_PROCESSOR, FlagEvent::requestNoRestart);
+        mFeatureFlags.addListener(SCREENSHOT_WORK_PROFILE_POLICY, FlagEvent::requestNoRestart);
         mProcessor = processor;
     }
 
@@ -135,7 +136,7 @@
     }
 
     @Override
-    public IBinder onBind(@NonNull Intent intent) {
+    public IBinder onBind(Intent intent) {
         registerReceiver(mCloseSystemDialogs, new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS),
                 Context.RECEIVER_EXPORTED);
         final Messenger m = new Messenger(mHandler);
@@ -150,10 +151,7 @@
         if (DEBUG_SERVICE) {
             Log.d(TAG, "onUnbind");
         }
-        if (mScreenshot != null) {
-            mScreenshot.removeWindow();
-            mScreenshot = null;
-        }
+        mScreenshot.removeWindow();
         unregisterReceiver(mCloseSystemDialogs);
         return false;
     }
@@ -161,10 +159,7 @@
     @Override
     public void onDestroy() {
         super.onDestroy();
-        if (mScreenshot != null) {
-            mScreenshot.onDestroy();
-            mScreenshot = null;
-        }
+        mScreenshot.onDestroy();
         if (DEBUG_SERVICE) {
             Log.d(TAG, "onDestroy");
         }
@@ -188,13 +183,23 @@
         }
     }
 
-    /** Respond to incoming Message via Binder (Messenger) */
     @MainThread
     private boolean handleMessage(Message msg) {
         final Messenger replyTo = msg.replyTo;
-        final Consumer<Uri> uriConsumer = (uri) -> reportUri(replyTo, uri);
-        RequestCallback requestCallback = new RequestCallbackImpl(replyTo);
+        final Consumer<Uri> onSaved = (uri) -> reportUri(replyTo, uri);
+        RequestCallback callback = new RequestCallbackImpl(replyTo);
 
+        ScreenshotHelper.ScreenshotRequest request =
+                (ScreenshotHelper.ScreenshotRequest) msg.obj;
+
+        handleRequest(request, onSaved, callback);
+        return true;
+    }
+
+    @MainThread
+    @VisibleForTesting
+    void handleRequest(ScreenshotHelper.ScreenshotRequest request, Consumer<Uri> onSaved,
+            RequestCallback callback) {
         // If the storage for this user is locked, we have no place to store
         // the screenshot, so skip taking it instead of showing a misleading
         // animation and error notification.
@@ -202,8 +207,8 @@
             Log.w(TAG, "Skipping screenshot because storage is locked!");
             mNotificationsController.notifyScreenshotError(
                     R.string.screenshot_failed_to_save_user_locked_text);
-            requestCallback.reportError();
-            return true;
+            callback.reportError();
+            return;
         }
 
         if (mDevicePolicyManager.getScreenCaptureDisabled(null, UserHandle.USER_ALL)) {
@@ -215,63 +220,64 @@
                         () -> mContext.getString(R.string.screenshot_blocked_by_admin));
                 mHandler.post(() ->
                         Toast.makeText(mContext, blockedByAdminText, Toast.LENGTH_SHORT).show());
-                requestCallback.reportError();
+                callback.reportError();
             });
-            return true;
+            return;
         }
 
-        ScreenshotHelper.ScreenshotRequest screenshotRequest =
-                (ScreenshotHelper.ScreenshotRequest) msg.obj;
-
-        ComponentName topComponent = screenshotRequest.getTopComponent();
-        mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()), 0,
-                topComponent == null ? "" : topComponent.getPackageName());
-
         if (mFeatureFlags.isEnabled(SCREENSHOT_REQUEST_PROCESSOR)) {
             Log.d(TAG, "handleMessage: Using request processor");
-            mProcessor.processRequest(screenshotRequest, uriConsumer, requestCallback);
-            return true;
+            mProcessor.processAsync(request,
+                    (r) -> dispatchToController(r, onSaved, callback));
         }
 
-        switch (screenshotRequest.getType()) {
+        dispatchToController(request, onSaved, callback);
+    }
+
+    private void dispatchToController(ScreenshotHelper.ScreenshotRequest request,
+            Consumer<Uri> uriConsumer, RequestCallback callback) {
+
+        ComponentName topComponent = request.getTopComponent();
+        mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(request.getSource()), 0,
+                topComponent == null ? "" : topComponent.getPackageName());
+
+        switch (request.getType()) {
             case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
                 if (DEBUG_SERVICE) {
                     Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_FULLSCREEN");
                 }
-                mScreenshot.takeScreenshotFullscreen(topComponent, uriConsumer, requestCallback);
+                mScreenshot.takeScreenshotFullscreen(topComponent, uriConsumer, callback);
                 break;
             case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
                 if (DEBUG_SERVICE) {
                     Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_SELECTED_REGION");
                 }
-                mScreenshot.takeScreenshotPartial(topComponent, uriConsumer, requestCallback);
+                mScreenshot.takeScreenshotPartial(topComponent, uriConsumer, callback);
                 break;
             case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
                 if (DEBUG_SERVICE) {
                     Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE");
                 }
                 Bitmap screenshot = ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap(
-                        screenshotRequest.getBitmapBundle());
-                Rect screenBounds = screenshotRequest.getBoundsInScreen();
-                Insets insets = screenshotRequest.getInsets();
-                int taskId = screenshotRequest.getTaskId();
-                int userId = screenshotRequest.getUserId();
+                        request.getBitmapBundle());
+                Rect screenBounds = request.getBoundsInScreen();
+                Insets insets = request.getInsets();
+                int taskId = request.getTaskId();
+                int userId = request.getUserId();
 
                 if (screenshot == null) {
                     Log.e(TAG, "Got null bitmap from screenshot message");
                     mNotificationsController.notifyScreenshotError(
                             R.string.screenshot_failed_to_capture_text);
-                    requestCallback.reportError();
+                    callback.reportError();
                 } else {
                     mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
-                            taskId, userId, topComponent, uriConsumer, requestCallback);
+                            taskId, userId, topComponent, uriConsumer, callback);
                 }
                 break;
             default:
-                Log.w(TAG, "Invalid screenshot option: " + msg.what);
-                return false;
+                Log.w(TAG, "Invalid screenshot option: " + request.getType());
         }
-        return true;
     }
 
     private static void sendComplete(Messenger target) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 3e442587..fdb0100 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,6 +20,9 @@
 
 import com.android.systemui.screenshot.ImageCapture;
 import com.android.systemui.screenshot.ImageCaptureImpl;
+import com.android.systemui.screenshot.ScreenshotPolicy;
+import com.android.systemui.screenshot.ScreenshotPolicyImpl;
+import com.android.systemui.screenshot.ScreenshotProxyService;
 import com.android.systemui.screenshot.TakeScreenshotService;
 
 import dagger.Binds;
@@ -33,12 +36,20 @@
 @Module
 public abstract class ScreenshotModule {
 
-    /** */
     @Binds
     @IntoMap
     @ClassKey(TakeScreenshotService.class)
-    public abstract Service bindTakeScreenshotService(TakeScreenshotService service);
+    abstract Service bindTakeScreenshotService(TakeScreenshotService service);
 
     @Binds
-    public abstract ImageCapture bindImageCapture(ImageCaptureImpl capture);
+    @IntoMap
+    @ClassKey(ScreenshotProxyService.class)
+    abstract Service bindScreenshotProxyService(ScreenshotProxyService service);
+
+    @Binds
+    abstract ScreenshotPolicy bindScreenshotPolicyImpl(ScreenshotPolicyImpl impl);
+
+    @Binds
+    abstract ImageCapture bindImageCaptureImpl(ImageCaptureImpl capture);
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index 5e908d9..1558ac5 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -76,6 +76,6 @@
          * Notifies that the current user's profiles have changed.
          */
         @JvmDefault
-        fun onProfilesChanged(profiles: List<UserInfo>) {}
+        fun onProfilesChanged(profiles: List<@JvmSuppressWildcards UserInfo>) {}
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 0f9ac36..fab70fc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -77,6 +77,7 @@
 class LargeScreenShadeHeaderController @Inject constructor(
     @Named(LARGE_SCREEN_SHADE_HEADER) private val header: View,
     private val statusBarIconController: StatusBarIconController,
+    private val tintedIconManagerFactory: StatusBarIconController.TintedIconManager.Factory,
     private val privacyIconsController: HeaderPrivacyIconsController,
     private val insetsProvider: StatusBarContentInsetsProvider,
     private val configurationController: ConfigurationController,
@@ -259,7 +260,7 @@
         batteryMeterViewController.ignoreTunerUpdates()
         batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
 
-        iconManager = StatusBarIconController.TintedIconManager(iconContainer, featureFlags)
+        iconManager = tintedIconManagerFactory.create(iconContainer)
         iconManager.setTint(
             Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
         )
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 7a4c877..29c7633 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -54,7 +54,6 @@
 import android.graphics.Insets;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.drawable.Drawable;
@@ -98,6 +97,7 @@
 import com.android.internal.policy.SystemBarUtils;
 import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.ActiveUnlockConfig;
+import com.android.keyguard.KeyguardClockSwitch.ClockSize;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardStatusViewController;
 import com.android.keyguard.KeyguardUnfoldTransition;
@@ -1416,19 +1416,10 @@
     private void updateClockAppearance() {
         int userSwitcherPreferredY = mStatusBarHeaderHeightKeyguard;
         boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
-        final boolean hasVisibleNotifications = mNotificationStackScrollLayoutController
-                .getVisibleNotificationCount() != 0
-                || mMediaDataManager.hasActiveMediaOrRecommendation();
-        boolean splitShadeWithActiveMedia =
-                mSplitShadeEnabled && mMediaDataManager.hasActiveMediaOrRecommendation();
         boolean shouldAnimateClockChange = mScreenOffAnimationController.shouldAnimateClockChange();
-        if ((hasVisibleNotifications && !mSplitShadeEnabled)
-                || (splitShadeWithActiveMedia && !mDozing)) {
-            mKeyguardStatusViewController.displayClock(SMALL, shouldAnimateClockChange);
-        } else {
-            mKeyguardStatusViewController.displayClock(LARGE, shouldAnimateClockChange);
-        }
-        updateKeyguardStatusViewAlignment(true /* animate */);
+        mKeyguardStatusViewController.displayClock(computeDesiredClockSize(),
+                shouldAnimateClockChange);
+        updateKeyguardStatusViewAlignment(/* animate= */true);
         int userSwitcherHeight = mKeyguardQsUserSwitchController != null
                 ? mKeyguardQsUserSwitchController.getUserIconHeight() : 0;
         if (mKeyguardUserSwitcherController != null) {
@@ -1437,7 +1428,7 @@
         float expandedFraction =
                 mScreenOffAnimationController.shouldExpandNotifications()
                         ? 1.0f : getExpandedFraction();
-        float darkamount =
+        float darkAmount =
                 mScreenOffAnimationController.shouldExpandNotifications()
                         ? 1.0f : mInterpolatedDarkAmount;
 
@@ -1455,7 +1446,7 @@
                 mKeyguardStatusViewController.getLockscreenHeight(),
                 userSwitcherHeight,
                 userSwitcherPreferredY,
-                darkamount, mOverStretchAmount,
+                darkAmount, mOverStretchAmount,
                 bypassEnabled, getUnlockedStackScrollerPadding(),
                 computeQsExpansionFraction(),
                 mDisplayTopInset,
@@ -1487,6 +1478,34 @@
         updateClock();
     }
 
+    @ClockSize
+    private int computeDesiredClockSize() {
+        if (mSplitShadeEnabled) {
+            return computeDesiredClockSizeForSplitShade();
+        }
+        return computeDesiredClockSizeForSingleShade();
+    }
+
+    @ClockSize
+    private int computeDesiredClockSizeForSingleShade() {
+        if (hasVisibleNotifications()) {
+            return SMALL;
+        }
+        return LARGE;
+    }
+
+    @ClockSize
+    private int computeDesiredClockSizeForSplitShade() {
+        // Media is not visible to the user on AOD.
+        boolean isMediaVisibleToUser =
+                mMediaDataManager.hasActiveMediaOrRecommendation() && !isOnAod();
+        if (isMediaVisibleToUser) {
+            // When media is visible, it overlaps with the large clock. Use small clock instead.
+            return SMALL;
+        }
+        return LARGE;
+    }
+
     private void updateKeyguardStatusViewAlignment(boolean animate) {
         boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
         if (mStatusViewCentered != shouldBeCentered) {
@@ -1513,12 +1532,35 @@
     }
 
     private boolean shouldKeyguardStatusViewBeCentered() {
-        boolean hasVisibleNotifications = mNotificationStackScrollLayoutController
+        if (mSplitShadeEnabled) {
+            return shouldKeyguardStatusViewBeCenteredInSplitShade();
+        }
+        return true;
+    }
+
+    private boolean shouldKeyguardStatusViewBeCenteredInSplitShade() {
+        if (!hasVisibleNotifications()) {
+            // No notifications visible. It is safe to have the clock centered as there will be no
+            // overlap.
+            return true;
+        }
+        if (hasPulsingNotifications()) {
+            // Pulsing notification appears on the right. Move clock left to avoid overlap.
+            return false;
+        }
+        // "Visible" notifications are actually not visible on AOD (unless pulsing), so it is safe
+        // to center the clock without overlap.
+        return isOnAod();
+    }
+
+    private boolean isOnAod() {
+        return mDozing && mDozeParameters.getAlwaysOn();
+    }
+
+    private boolean hasVisibleNotifications() {
+        return mNotificationStackScrollLayoutController
                 .getVisibleNotificationCount() != 0
                 || mMediaDataManager.hasActiveMediaOrRecommendation();
-        boolean isOnAod = mDozing && mDozeParameters.getAlwaysOn();
-        return !mSplitShadeEnabled || !hasVisibleNotifications || isOnAod
-                || hasPulsingNotifications();
     }
 
     /**
@@ -3752,13 +3794,12 @@
      *
      * @param dozing              {@code true} when dozing.
      * @param animate             if transition should be animated.
-     * @param wakeUpTouchLocation touch event location - if woken up by SLPI sensor.
      */
-    public void setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation) {
+    public void setDozing(boolean dozing, boolean animate) {
         if (dozing == mDozing) return;
         mView.setDozing(dozing);
         mDozing = dozing;
-        mNotificationStackScrollLayoutController.setDozing(mDozing, animate, wakeUpTouchLocation);
+        mNotificationStackScrollLayoutController.setDozing(mDozing, animate);
         mKeyguardBottomArea.setDozing(mDozing, animate);
         mKeyguardBottomAreaInteractorProvider.get().setAnimateDozingTransitions(animate);
         mKeyguardStatusBarViewController.setDozing(mDozing);
@@ -3791,6 +3832,8 @@
             mAnimateNextPositionUpdate = false;
         }
         mNotificationStackScrollLayoutController.setPulsing(pulsing, animatePulse);
+
+        updateKeyguardStatusViewAlignment(/* animate= */ true);
     }
 
     public void setAmbientIndicationTop(int ambientIndicationTop, boolean ambientTextVisible) {
@@ -4683,7 +4726,7 @@
      * change.
      */
     public void showAodUi() {
-        setDozing(true /* dozing */, false /* animate */, null);
+        setDozing(true /* dozing */, false /* animate */);
         mStatusBarStateController.setUpcomingState(KEYGUARD);
         mEntryManager.updateNotifications("showAodUi");
         mStatusBarStateListener.onStateChanged(KEYGUARD);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index 0898d63..a72b7f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -24,6 +24,7 @@
 import android.util.ArraySet;
 import android.view.accessibility.AccessibilityEvent;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -49,7 +50,8 @@
 
     protected int mMinimumDisplayTime;
     protected int mAutoDismissNotificationDecay;
-    private final Handler mHandler;
+    @VisibleForTesting
+    public Handler mHandler;
 
     /**
      * Called when posting a new notification that should alert the user and appear on screen.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt
new file mode 100644
index 0000000..4d53064
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.FrameLayout
+
+/**
+ * A temporary base class that's shared between our old status bar wifi view implementation
+ * ([StatusBarWifiView]) and our new status bar wifi view implementation
+ * ([ModernStatusBarWifiView]).
+ *
+ * Once our refactor is over, we should be able to delete this go-between class and the old view
+ * class.
+ */
+abstract class BaseStatusBarWifiView @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttrs: Int = 0,
+) : FrameLayout(context, attrs, defStyleAttrs), StatusIconDisplayable
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 0280e0b..c04bc82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -303,11 +303,6 @@
     }
 
     @Override
-    public void setDozeAmount(float dozeAmount, boolean animated) {
-        setAndInstrumentDozeAmount(null, dozeAmount, animated);
-    }
-
-    @Override
     public void setAndInstrumentDozeAmount(View view, float dozeAmount, boolean animated) {
         if (mDarkAnimator != null && mDarkAnimator.isRunning()) {
             if (animated && mDozeAmountTarget == dozeAmount) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
index a6986d7..5aee62e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
@@ -28,7 +28,6 @@
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 
@@ -41,8 +40,7 @@
 /**
  * Start small: StatusBarWifiView will be able to layout from a WifiIconState
  */
-public class StatusBarWifiView extends FrameLayout implements DarkReceiver,
-        StatusIconDisplayable {
+public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkReceiver {
     private static final String TAG = "StatusBarWifiView";
 
     /// Used to show etc dots
@@ -80,11 +78,6 @@
         super(context, attrs, defStyleAttr);
     }
 
-    public StatusBarWifiView(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
     public void setSlot(String slot) {
         mSlot = slot;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index 2b31901..2cc7738 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -99,14 +99,6 @@
     boolean setIsDozing(boolean isDozing);
 
     /**
-     * Changes the current doze amount.
-     *
-     * @param dozeAmount New doze/dark amount.
-     * @param animated If change should be animated or not. This will cancel current animations.
-     */
-    void setDozeAmount(float dozeAmount, boolean animated);
-
-    /**
      * Changes the current doze amount, also starts the
      * {@link com.android.internal.jank.InteractionJankMonitor InteractionJankMonitor} as possible.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java
index 3c449ad..7097568 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java
@@ -23,7 +23,7 @@
 /** */
 public class WifiIcons {
 
-    static final int[] WIFI_FULL_ICONS = {
+    public static final int[] WIFI_FULL_ICONS = {
             com.android.internal.R.drawable.ic_wifi_signal_0,
             com.android.internal.R.drawable.ic_wifi_signal_1,
             com.android.internal.R.drawable.ic_wifi_signal_2,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
index 9e5dab1..f8449ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
@@ -90,6 +90,18 @@
         stableIndex = -1
     }
 
+    /**
+     * Erases bookkeeping traces stored on an entry when it is removed from the notif list.
+     * This can happen if the entry is removed from a group that was broken up or if the entry was
+     * filtered out during any of the filtering steps.
+     */
+    fun detach() {
+        parent = null
+        section = null
+        promoter = null
+        // stableIndex = -1  // TODO(b/241229236): Clear this once we fix the stability fragility
+    }
+
     companion object {
         @JvmStatic
         fun create(): ListAttachState {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 14cc6bf..3eaa988 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -958,9 +958,7 @@
      * filtered out during any of the filtering steps.
      */
     private void annulAddition(ListEntry entry) {
-        entry.setParent(null);
-        entry.getAttachState().setSection(null);
-        entry.getAttachState().setPromoter(null);
+        entry.getAttachState().detach();
     }
 
     private void assignSections() {
@@ -1198,9 +1196,9 @@
                 o2.getSectionIndex());
         if (cmp != 0) return cmp;
 
-        int index1 = canReorder(o1) ? -1 : o1.getPreviousAttachState().getStableIndex();
-        int index2 = canReorder(o2) ? -1 : o2.getPreviousAttachState().getStableIndex();
-        cmp = Integer.compare(index1, index2);
+        cmp = Integer.compare(
+                getStableOrderIndex(o1),
+                getStableOrderIndex(o2));
         if (cmp != 0) return cmp;
 
         NotifComparator sectionComparator = getSectionComparator(o1, o2);
@@ -1214,31 +1212,32 @@
             if (cmp != 0) return cmp;
         }
 
-        final NotificationEntry rep1 = o1.getRepresentativeEntry();
-        final NotificationEntry rep2 = o2.getRepresentativeEntry();
-            cmp = rep1.getRanking().getRank() - rep2.getRanking().getRank();
+        cmp = Integer.compare(
+                o1.getRepresentativeEntry().getRanking().getRank(),
+                o2.getRepresentativeEntry().getRanking().getRank());
         if (cmp != 0) return cmp;
 
-        cmp = Long.compare(
-                rep2.getSbn().getNotification().when,
-                rep1.getSbn().getNotification().when);
+        cmp = -1 * Long.compare(
+                o1.getRepresentativeEntry().getSbn().getNotification().when,
+                o2.getRepresentativeEntry().getSbn().getNotification().when);
         return cmp;
     };
 
 
     private final Comparator<NotificationEntry> mGroupChildrenComparator = (o1, o2) -> {
-        int index1 = canReorder(o1) ? -1 : o1.getPreviousAttachState().getStableIndex();
-        int index2 = canReorder(o2) ? -1 : o2.getPreviousAttachState().getStableIndex();
-        int cmp = Integer.compare(index1, index2);
+        int cmp = Integer.compare(
+                getStableOrderIndex(o1),
+                getStableOrderIndex(o2));
         if (cmp != 0) return cmp;
 
-        cmp = o1.getRepresentativeEntry().getRanking().getRank()
-                - o2.getRepresentativeEntry().getRanking().getRank();
+        cmp = Integer.compare(
+                o1.getRepresentativeEntry().getRanking().getRank(),
+                o2.getRepresentativeEntry().getRanking().getRank());
         if (cmp != 0) return cmp;
 
-        cmp = Long.compare(
-                o2.getRepresentativeEntry().getSbn().getNotification().when,
-                o1.getRepresentativeEntry().getSbn().getNotification().when);
+        cmp = -1 * Long.compare(
+                o1.getRepresentativeEntry().getSbn().getNotification().when,
+                o2.getRepresentativeEntry().getSbn().getNotification().when);
         return cmp;
     };
 
@@ -1248,8 +1247,16 @@
      */
     private boolean mForceReorderable = false;
 
-    private boolean canReorder(ListEntry entry) {
-        return mForceReorderable || getStabilityManager().isEntryReorderingAllowed(entry);
+    private int getStableOrderIndex(ListEntry entry) {
+        if (mForceReorderable) {
+            // this is used to determine if the list is correctly sorted
+            return -1;
+        }
+        if (getStabilityManager().isEntryReorderingAllowed(entry)) {
+            // let the stability manager constrain or allow reordering
+            return -1;
+        }
+        return entry.getPreviousAttachState().getStableIndex();
     }
 
     private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9ad906c..855390d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -20,7 +20,6 @@
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -236,11 +235,6 @@
      */
     private boolean mIsHeadsUp;
 
-    /**
-     * Whether or not the notification should be redacted on the lock screen, i.e has sensitive
-     * content which should be redacted on the lock screen.
-     */
-    private boolean mNeedsRedaction;
     private boolean mLastChronometerRunning = true;
     private ViewStub mChildrenContainerStub;
     private GroupMembershipManager mGroupMembershipManager;
@@ -1502,23 +1496,6 @@
         mUseIncreasedHeadsUpHeight = use;
     }
 
-    /** @deprecated TODO: Remove this when the old pipeline code is removed. */
-    @Deprecated
-    public void setNeedsRedaction(boolean needsRedaction) {
-        if (mNeedsRedaction != needsRedaction) {
-            mNeedsRedaction = needsRedaction;
-            if (!isRemoved()) {
-                RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
-                if (needsRedaction) {
-                    params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC);
-                } else {
-                    params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
-                }
-                mRowContentBindStage.requestRebind(mEntry, null /* callback */);
-            }
-        }
-    }
-
     public interface ExpansionLogger {
         void logNotificationExpansion(String key, boolean userAction, boolean expanded);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 952bafb..79d883b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -42,7 +42,6 @@
 import android.graphics.Outline;
 import android.graphics.Paint;
 import android.graphics.Path;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.provider.Settings;
@@ -4405,8 +4404,7 @@
      * See {@link AmbientState#setDozing}.
      */
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setDozing(boolean dozing, boolean animate,
-            @Nullable PointF touchWakeUpScreenLocation) {
+    public void setDozing(boolean dozing, boolean animate) {
         if (mAmbientState.isDozing() == dozing) {
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 9998fe4..3f6586c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -33,7 +33,6 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Point;
-import android.graphics.PointF;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -1235,8 +1234,8 @@
         mView.setAnimationsEnabled(enabled);
     }
 
-    public void setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation) {
-        mView.setDozing(dozing, animate, wakeUpTouchLocation);
+    public void setDozing(boolean dozing, boolean animate) {
+        mView.setDozing(dozing, animate);
     }
 
     public void setPulsing(boolean pulsing, boolean animatePulse) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 65ba5ad..e754d5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -512,7 +512,8 @@
                     Trace.beginSection("MODE_WAKE_AND_UNLOCK");
                 } else {
                     Trace.beginSection("MODE_WAKE_AND_UNLOCK_FROM_DREAM");
-                    mUpdateMonitor.awakenFromDream();
+                    // Don't call awaken from Dream here. In order to avoid flickering, wait until
+                    // later to awaken.
                 }
                 mNotificationShadeWindowController.setNotificationShadeFocusable(false);
                 mKeyguardViewMediator.onWakeAndUnlocking();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index b0a5adb..e444e0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -67,7 +67,6 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.graphics.Point;
-import android.graphics.PointF;
 import android.hardware.devicestate.DeviceStateManager;
 import android.metrics.LogMaker;
 import android.net.Uri;
@@ -446,7 +445,6 @@
     @VisibleForTesting
     DozeServiceHost mDozeServiceHost;
     private boolean mWakeUpComingFromTouch;
-    private PointF mWakeUpTouchLocation;
     private LightRevealScrim mLightRevealScrim;
     private PowerButtonReveal mPowerButtonReveal;
 
@@ -603,8 +601,6 @@
     private int mLastCameraLaunchSource;
     protected PowerManager.WakeLock mGestureWakeLock;
 
-    private final int[] mTmpInt2 = new int[2];
-
     // Fingerprint (as computed by getLoggingFingerprint() of the last logged state.
     private int mLastLoggedStateFingerprint;
     private boolean mIsLaunchingActivityOverLockscreen;
@@ -1430,16 +1426,6 @@
             mPowerManager.wakeUp(
                     time, PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:" + why);
             mWakeUpComingFromTouch = true;
-
-            // NOTE, the incoming view can sometimes be the entire container... unsure if
-            // this location is valuable enough
-            if (where != null) {
-                where.getLocationInWindow(mTmpInt2);
-                mWakeUpTouchLocation = new PointF(mTmpInt2[0] + where.getWidth() / 2,
-                        mTmpInt2[1] + where.getHeight() / 2);
-            } else {
-                mWakeUpTouchLocation = new PointF(-1, -1);
-            }
             mFalsingCollector.onScreenOnFromTouch();
         }
     }
@@ -1955,7 +1941,6 @@
                     PowerManager.WAKE_REASON_APPLICATION,
                     "com.android.systemui:full_screen_intent");
             mWakeUpComingFromTouch = false;
-            mWakeUpTouchLocation = null;
         }
     }
 
@@ -3225,7 +3210,7 @@
                 || (mDozing && mDozeParameters.shouldControlScreenOff()
                 && visibleNotOccludedOrWillBe);
 
-        mNotificationPanelViewController.setDozing(mDozing, animate, mWakeUpTouchLocation);
+        mNotificationPanelViewController.setDozing(mDozing, animate);
         updateQsExpansionEnabled();
         Trace.endSection();
     }
@@ -3556,7 +3541,6 @@
             mLaunchCameraWhenFinishedWaking = false;
             mDeviceInteractive = false;
             mWakeUpComingFromTouch = false;
-            mWakeUpTouchLocation = null;
             updateVisibleToUser();
 
             updateNotificationPanelTouchState();
@@ -4065,7 +4049,7 @@
             final PendingIntent intent, @Nullable final Runnable intentSentUiThreadCallback,
             @Nullable ActivityLaunchAnimator.Controller animationController) {
         final boolean willLaunchResolverActivity = intent.isActivity()
-                && mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(),
+                && mActivityIntentHelper.wouldPendingLaunchResolverActivity(intent,
                 mLockscreenUserManager.getCurrentUserId());
 
         boolean animate = !willLaunchResolverActivity
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index fc8e7d5..deb6150 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -225,6 +225,7 @@
 
     public void addDemoWifiView(WifiIconState state) {
         Log.d(TAG, "addDemoWifiView: ");
+        // TODO(b/238425913): Migrate this view to {@code ModernStatusBarWifiView}.
         StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, state.slot);
 
         int viewIndex = getChildCount();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 4c9c75b..103e4f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -27,6 +27,7 @@
 import androidx.collection.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
@@ -39,6 +40,7 @@
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
@@ -107,8 +109,10 @@
             GroupMembershipManager groupMembershipManager,
             VisualStabilityProvider visualStabilityProvider,
             ConfigurationController configurationController,
-            @Main Handler handler) {
-        super(context, logger, handler);
+            @Main Handler handler,
+            AccessibilityManagerWrapper accessibilityManagerWrapper,
+            UiEventLogger uiEventLogger) {
+        super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger);
         Resources resources = mContext.getResources();
         mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
         statusBarStateController.addCallback(mStatusBarStateListener);
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 30b640b..ed186ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -40,6 +40,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.BaseStatusBarWifiView;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarMobileView;
 import com.android.systemui.statusbar.StatusBarWifiView;
@@ -47,12 +48,16 @@
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
+import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
 import com.android.systemui.util.Assert;
 
 import java.util.ArrayList;
 import java.util.List;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 public interface StatusBarIconController {
 
@@ -128,8 +133,12 @@
         private final DarkIconDispatcher mDarkIconDispatcher;
         private int mIconHPadding;
 
-        public DarkIconManager(LinearLayout linearLayout, FeatureFlags featureFlags) {
-            super(linearLayout, featureFlags);
+        public DarkIconManager(
+                LinearLayout linearLayout,
+                FeatureFlags featureFlags,
+                StatusBarPipelineFlags statusBarPipelineFlags,
+                Provider<WifiViewModel> wifiViewModelProvider) {
+            super(linearLayout, featureFlags, statusBarPipelineFlags, wifiViewModelProvider);
             mIconHPadding = mContext.getResources().getDimensionPixelSize(
                     R.dimen.status_bar_icon_padding);
             mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
@@ -183,14 +192,40 @@
             mDarkIconDispatcher.removeDarkReceiver(mDemoStatusIcons);
             super.exitDemoMode();
         }
+
+        @SysUISingleton
+        public static class Factory {
+            private final FeatureFlags mFeatureFlags;
+            private final StatusBarPipelineFlags mStatusBarPipelineFlags;
+            private final Provider<WifiViewModel> mWifiViewModelProvider;
+
+            @Inject
+            public Factory(
+                    FeatureFlags featureFlags,
+                    StatusBarPipelineFlags statusBarPipelineFlags,
+                    Provider<WifiViewModel> wifiViewModelProvider) {
+                mFeatureFlags = featureFlags;
+                mStatusBarPipelineFlags = statusBarPipelineFlags;
+                mWifiViewModelProvider = wifiViewModelProvider;
+            }
+
+            public DarkIconManager create(LinearLayout group) {
+                return new DarkIconManager(
+                        group, mFeatureFlags, mStatusBarPipelineFlags, mWifiViewModelProvider);
+            }
+        }
     }
 
     /** */
     class TintedIconManager extends IconManager {
         private int mColor;
 
-        public TintedIconManager(ViewGroup group, FeatureFlags featureFlags) {
-            super(group, featureFlags);
+        public TintedIconManager(
+                ViewGroup group,
+                FeatureFlags featureFlags,
+                StatusBarPipelineFlags statusBarPipelineFlags,
+                Provider<WifiViewModel> wifiViewModelProvider) {
+            super(group, featureFlags, statusBarPipelineFlags, wifiViewModelProvider);
         }
 
         @Override
@@ -223,14 +258,22 @@
         @SysUISingleton
         public static class Factory {
             private final FeatureFlags mFeatureFlags;
+            private final StatusBarPipelineFlags mStatusBarPipelineFlags;
+            private final Provider<WifiViewModel> mWifiViewModelProvider;
 
             @Inject
-            public Factory(FeatureFlags featureFlags) {
+            public Factory(
+                    FeatureFlags featureFlags,
+                    StatusBarPipelineFlags statusBarPipelineFlags,
+                    Provider<WifiViewModel> wifiViewModelProvider) {
                 mFeatureFlags = featureFlags;
+                mStatusBarPipelineFlags = statusBarPipelineFlags;
+                mWifiViewModelProvider = wifiViewModelProvider;
             }
 
             public TintedIconManager create(ViewGroup group) {
-                return new TintedIconManager(group, mFeatureFlags);
+                return new TintedIconManager(
+                        group, mFeatureFlags, mStatusBarPipelineFlags, mWifiViewModelProvider);
             }
         }
     }
@@ -239,8 +282,10 @@
      * Turns info from StatusBarIconController into ImageViews in a ViewGroup.
      */
     class IconManager implements DemoModeCommandReceiver {
-        private final FeatureFlags mFeatureFlags;
         protected final ViewGroup mGroup;
+        private final FeatureFlags mFeatureFlags;
+        private final StatusBarPipelineFlags mStatusBarPipelineFlags;
+        private final Provider<WifiViewModel> mWifiViewModelProvider;
         protected final Context mContext;
         protected final int mIconSize;
         // Whether or not these icons show up in dumpsys
@@ -254,9 +299,15 @@
 
         protected ArrayList<String> mBlockList = new ArrayList<>();
 
-        public IconManager(ViewGroup group, FeatureFlags featureFlags) {
-            mFeatureFlags = featureFlags;
+        public IconManager(
+                ViewGroup group,
+                FeatureFlags featureFlags,
+                StatusBarPipelineFlags statusBarPipelineFlags,
+                Provider<WifiViewModel> wifiViewModelProvider) {
             mGroup = group;
+            mFeatureFlags = featureFlags;
+            mStatusBarPipelineFlags = statusBarPipelineFlags;
+            mWifiViewModelProvider = wifiViewModelProvider;
             mContext = group.getContext();
             mIconSize = mContext.getResources().getDimensionPixelSize(
                     com.android.internal.R.dimen.status_bar_icon_size);
@@ -308,7 +359,7 @@
                     return addIcon(index, slot, blocked, holder.getIcon());
 
                 case TYPE_WIFI:
-                    return addSignalIcon(index, slot, holder.getWifiState());
+                    return addWifiIcon(index, slot, holder.getWifiState());
 
                 case TYPE_MOBILE:
                     return addMobileIcon(index, slot, holder.getMobileState());
@@ -327,9 +378,17 @@
         }
 
         @VisibleForTesting
-        protected StatusBarWifiView addSignalIcon(int index, String slot, WifiIconState state) {
-            StatusBarWifiView view = onCreateStatusBarWifiView(slot);
-            view.applyWifiState(state);
+        protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
+            final BaseStatusBarWifiView view;
+            if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+                view = onCreateModernStatusBarWifiView(slot);
+                // When [ModernStatusBarWifiView] is created, it will automatically apply the
+                // correct view state so we don't need to call applyWifiState.
+            } else {
+                StatusBarWifiView wifiView = onCreateStatusBarWifiView(slot);
+                wifiView.applyWifiState(state);
+                view = wifiView;
+            }
             mGroup.addView(view, index, onCreateLayoutParams());
 
             if (mIsInDemoMode) {
@@ -359,6 +418,11 @@
             return view;
         }
 
+        private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
+            return ModernStatusBarWifiView.constructAndBind(
+                    mContext, slot, mWifiViewModelProvider.get());
+        }
+
         private StatusBarMobileView onCreateStatusBarMobileView(String slot) {
             StatusBarMobileView view = StatusBarMobileView.fromContext(mContext, slot);
             return view;
@@ -415,9 +479,8 @@
                     onSetIcon(viewIndex, holder.getIcon());
                     return;
                 case TYPE_WIFI:
-                    onSetSignalIcon(viewIndex, holder.getWifiState());
+                    onSetWifiIcon(viewIndex, holder.getWifiState());
                     return;
-
                 case TYPE_MOBILE:
                     onSetMobileIcon(viewIndex, holder.getMobileState());
                 default:
@@ -425,10 +488,16 @@
             }
         }
 
-        public void onSetSignalIcon(int viewIndex, WifiIconState state) {
-            StatusBarWifiView wifiView = (StatusBarWifiView) mGroup.getChildAt(viewIndex);
-            if (wifiView != null) {
-                wifiView.applyWifiState(state);
+        public void onSetWifiIcon(int viewIndex, WifiIconState state) {
+            View view = mGroup.getChildAt(viewIndex);
+            if (view instanceof StatusBarWifiView) {
+                ((StatusBarWifiView) view).applyWifiState(state);
+            } else if (view instanceof ModernStatusBarWifiView) {
+                // ModernStatusBarWifiView will automatically apply state based on its callbacks, so
+                // we don't need to call applyWifiState.
+            } else {
+                throw new IllegalStateException("View at " + viewIndex + " must be of type "
+                        + "StatusBarWifiView or ModernStatusBarWifiView");
             }
 
             if (mIsInDemoMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 374f091..5cd2ba1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -223,12 +223,12 @@
 
         boolean isActivityIntent = intent != null && intent.isActivity() && !isBubble;
         final boolean willLaunchResolverActivity = isActivityIntent
-                && mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(),
+                && mActivityIntentHelper.wouldPendingLaunchResolverActivity(intent,
                 mLockscreenUserManager.getCurrentUserId());
         final boolean animate = !willLaunchResolverActivity
                 && mCentralSurfaces.shouldAnimateLaunch(isActivityIntent);
         boolean showOverLockscreen = mKeyguardStateController.isShowing() && intent != null
-                && mActivityIntentHelper.wouldShowOverLockscreen(intent.getIntent(),
+                && mActivityIntentHelper.wouldPendingShowOverLockscreen(intent,
                 mLockscreenUserManager.getCurrentUserId());
         ActivityStarter.OnDismissAction postKeyguardAction = new ActivityStarter.OnDismissAction() {
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 40b9a15..70af77e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -259,8 +259,9 @@
         final boolean isActivity = pendingIntent.isActivity();
         if (isActivity || appRequestedAuth) {
             mActionClickLogger.logWaitingToCloseKeyguard(pendingIntent);
-            final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity(
-                    pendingIntent.getIntent(), mLockscreenUserManager.getCurrentUserId());
+            final boolean afterKeyguardGone = mActivityIntentHelper
+                    .wouldPendingLaunchResolverActivity(pendingIntent,
+                            mLockscreenUserManager.getCurrentUserId());
             mActivityStarter.dismissKeyguardThenExecute(() -> {
                 mActionClickLogger.logKeyguardGone(pendingIntent);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 200f45f..fb5b096 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -286,6 +286,7 @@
             PanelExpansionStateManager panelExpansionStateManager,
             FeatureFlags featureFlags,
             StatusBarIconController statusBarIconController,
+            StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory,
             StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
             KeyguardStateController keyguardStateController,
             NotificationPanelViewController notificationPanelViewController,
@@ -306,6 +307,7 @@
                 panelExpansionStateManager,
                 featureFlags,
                 statusBarIconController,
+                darkIconManagerFactory,
                 statusBarHideIconsForBouncerManager,
                 keyguardStateController,
                 notificationPanelViewController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index f61b488..5fd72b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -124,6 +124,7 @@
     private final StatusBarIconController mStatusBarIconController;
     private final CarrierConfigTracker mCarrierConfigTracker;
     private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
+    private final StatusBarIconController.DarkIconManager.Factory mDarkIconManagerFactory;
     private final SecureSettings mSecureSettings;
     private final Executor mMainExecutor;
     private final DumpManager mDumpManager;
@@ -172,6 +173,7 @@
             PanelExpansionStateManager panelExpansionStateManager,
             FeatureFlags featureFlags,
             StatusBarIconController statusBarIconController,
+            StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory,
             StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
             KeyguardStateController keyguardStateController,
             NotificationPanelViewController notificationPanelViewController,
@@ -193,6 +195,7 @@
         mFeatureFlags = featureFlags;
         mStatusBarIconController = statusBarIconController;
         mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
+        mDarkIconManagerFactory = darkIconManagerFactory;
         mKeyguardStateController = keyguardStateController;
         mNotificationPanelViewController = notificationPanelViewController;
         mStatusBarStateController = statusBarStateController;
@@ -232,7 +235,7 @@
             mStatusBar.restoreHierarchyState(
                     savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
         }
-        mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons), mFeatureFlags);
+        mDarkIconManager = mDarkIconManagerFactory.create(view.findViewById(R.id.statusIcons));
         mDarkIconManager.setShouldLog(true);
         updateBlockedIcons();
         mStatusBarIconController.addIconGroup(mDarkIconManager);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt
deleted file mode 100644
index 780a02d..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline
-
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo
-import kotlinx.coroutines.flow.StateFlow
-
-/**
- * Interface exposing a flow for raw connectivity information. Clients should collect on
- * [rawConnectivityInfoFlow] to get updates on connectivity information.
- *
- * Note: [rawConnectivityInfoFlow] should be a *hot* flow, so that we only create one instance of it
- * and all clients get references to the same flow.
- *
- * This will be used for the new status bar pipeline to compile information we need to display some
- * of the icons in the RHS of the status bar.
- */
-interface ConnectivityInfoCollector {
-    val rawConnectivityInfoFlow: StateFlow<RawConnectivityInfo>
-}
-
-/**
- * An object containing all of the raw connectivity information.
- *
- * Importantly, all the information in this object should not be processed at all (i.e., the data
- * that we receive from callbacks should be piped straight into this object and not be filtered,
- * manipulated, or processed in any way). Instead, any listeners on
- * [ConnectivityInfoCollector.rawConnectivityInfoFlow] can do the processing.
- *
- * This allows us to keep all the processing in one place which is beneficial for logging and
- * debugging purposes.
- */
-data class RawConnectivityInfo(
-        val networkCapabilityInfo: Map<Int, NetworkCapabilityInfo> = emptyMap(),
-)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt
deleted file mode 100644
index 99798f9..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilitiesRepo
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-
-/**
- * The real implementation of [ConnectivityInfoCollector] that will collect information from all the
- * relevant connectivity callbacks and compile it into [rawConnectivityInfoFlow].
- */
-@SysUISingleton
-class ConnectivityInfoCollectorImpl @Inject constructor(
-        networkCapabilitiesRepo: NetworkCapabilitiesRepo,
-        @Application scope: CoroutineScope,
-) : ConnectivityInfoCollector {
-    override val rawConnectivityInfoFlow: StateFlow<RawConnectivityInfo> =
-            // TODO(b/238425913): Collect all the separate flows for individual raw information into
-            //   this final flow.
-            networkCapabilitiesRepo.dataStream
-                    .map {
-                        RawConnectivityInfo(networkCapabilityInfo = it)
-                    }
-                    .stateIn(scope, started = Lazily, initialValue = RawConnectivityInfo())
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
index 64c47f6..fe84674 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
@@ -20,31 +20,21 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
 import javax.inject.Inject
 import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
 import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.emptyFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
 /**
- * A processor that transforms raw connectivity information that we get from callbacks and turns it
- * into a list of displayable connectivity information.
+ * A temporary object that collects on [WifiViewModel] flows for debugging purposes.
  *
- * This will be used for the new status bar pipeline to calculate the list of icons that should be
- * displayed in the RHS of the status bar.
- *
- * Anyone can listen to [processedInfoFlow] to get updates to the processed data.
+ * This will eventually get migrated to a view binder that will use the flow outputs to set state on
+ * views. For now, this just collects on flows so that the information gets logged.
  */
 @SysUISingleton
 class ConnectivityInfoProcessor @Inject constructor(
-        connectivityInfoCollector: ConnectivityInfoCollector,
         context: Context,
         // TODO(b/238425913): Don't use the application scope; instead, use the status bar view's
         // scope so we only do work when there's UI that cares about it.
@@ -52,23 +42,8 @@
         private val statusBarPipelineFlags: StatusBarPipelineFlags,
         private val wifiViewModelProvider: Provider<WifiViewModel>,
 ) : CoreStartable(context) {
-    // Note: This flow will not start running until a client calls `collect` on it, which means that
-    // [connectivityInfoCollector]'s flow will also not start anything until that `collect` call
-    // happens.
-    // TODO(b/238425913): Delete this.
-    val processedInfoFlow: Flow<ProcessedConnectivityInfo> =
-            if (!statusBarPipelineFlags.isNewPipelineEnabled())
-                emptyFlow()
-            else connectivityInfoCollector.rawConnectivityInfoFlow
-                    .map { it.process() }
-                    .stateIn(
-                            scope,
-                            started = Lazily,
-                            initialValue = ProcessedConnectivityInfo()
-                    )
-
     override fun start() {
-        if (!statusBarPipelineFlags.isNewPipelineEnabled()) {
+        if (!statusBarPipelineFlags.isNewPipelineBackendEnabled()) {
             return
         }
         // TODO(b/238425913): The view binder should do this instead. For now, do it here so we can
@@ -77,17 +52,4 @@
             wifiViewModelProvider.get().isActivityInVisible.collect { }
         }
     }
-
-    private fun RawConnectivityInfo.process(): ProcessedConnectivityInfo {
-        // TODO(b/238425913): Actually process the raw info into meaningful data.
-        return ProcessedConnectivityInfo(this.networkCapabilityInfo)
-    }
 }
-
-/**
- * An object containing connectivity info that has been processed into data that can be directly
- * used by the status bar (and potentially other SysUI areas) to display icons.
- */
-data class ProcessedConnectivityInfo(
-        val networkCapabilityInfo: Map<Int, NetworkCapabilityInfo> = emptyMap(),
-)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
index 589cdb8..9b8b643 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -25,13 +25,28 @@
 @SysUISingleton
 class StatusBarPipelineFlags @Inject constructor(private val featureFlags: FeatureFlags) {
     /**
-     * Returns true if we should run the new pipeline.
+     * Returns true if we should run the new pipeline backend.
      *
-     * TODO(b/238425913): We may want to split this out into:
-     *   (1) isNewPipelineLoggingEnabled(), where the new pipeline runs and logs its decisions but
-     *       doesn't change the UI at all.
-     *   (2) isNewPipelineEnabled(), where the new pipeline runs and does change the UI (and the old
-     *       pipeline doesn't change the UI).
+     * The new pipeline backend hooks up to all our external callbacks, logs those callback inputs,
+     * and logs the output state.
      */
-    fun isNewPipelineEnabled(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE)
+    fun isNewPipelineBackendEnabled(): Boolean =
+        featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE_BACKEND)
+
+    /**
+     * Returns true if we should run the new pipeline frontend *and* backend.
+     *
+     * The new pipeline frontend will use the outputted state from the new backend and will make the
+     * correct changes to the UI.
+     */
+    fun isNewPipelineFrontendEnabled(): Boolean =
+        isNewPipelineBackendEnabled() &&
+            featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE_FRONTEND)
+
+    /**
+     * Returns true if we should apply some coloring to icons that were rendered with the new
+     * pipeline to help with debugging.
+     */
+    // For now, just always apply the debug coloring if we've enabled frontend rendering.
+    fun useNewPipelineDebugColoring(): Boolean = isNewPipelineFrontendEnabled()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 7abe19e7b..88eccb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -17,8 +17,6 @@
 package com.android.systemui.statusbar.pipeline.dagger
 
 import com.android.systemui.CoreStartable
-import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollector
-import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollectorImpl
 import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl
@@ -36,10 +34,5 @@
     abstract fun bindConnectivityInfoProcessor(cip: ConnectivityInfoProcessor): CoreStartable
 
     @Binds
-    abstract fun provideConnectivityInfoCollector(
-            impl: ConnectivityInfoCollectorImpl
-    ): ConnectivityInfoCollector
-
-    @Binds
     abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
index a5fff5e..2a89309 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -31,6 +31,9 @@
 class ConnectivityPipelineLogger @Inject constructor(
     @StatusBarConnectivityLog private val buffer: LogBuffer,
 ) {
+    /**
+     * Logs a change in one of the **raw inputs** to the connectivity pipeline.
+     */
     fun logInputChange(callbackName: String, changeInfo: String) {
         buffer.log(
                 SB_LOGGING_TAG,
@@ -45,6 +48,41 @@
         )
     }
 
+    /**
+     * Logs a **data transformation** that we performed within the connectivity pipeline.
+     */
+    fun logTransformation(transformationName: String, oldValue: Any?, newValue: Any?) {
+        if (oldValue == newValue) {
+            buffer.log(
+                SB_LOGGING_TAG,
+                LogLevel.INFO,
+                {
+                    str1 = transformationName
+                    str2 = oldValue.toString()
+                },
+                {
+                    "Transform: $str1: $str2 (transformation didn't change it)"
+                }
+            )
+        } else {
+            buffer.log(
+                SB_LOGGING_TAG,
+                LogLevel.INFO,
+                {
+                    str1 = transformationName
+                    str2 = oldValue.toString()
+                    str3 = newValue.toString()
+                },
+                {
+                    "Transform: $str1: $str2 -> $str3"
+                }
+            )
+        }
+    }
+
+    /**
+     * Logs a change in one of the **outputs** to the connectivity pipeline.
+     */
     fun logOutputChange(outputParamName: String, changeInfo: String) {
         buffer.log(
                 SB_LOGGING_TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiModel.kt
deleted file mode 100644
index 1b73322..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiModel.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.wifi.data.model
-
-/** Provides information about the current wifi state. */
-data class WifiModel(
-    /** See [android.net.wifi.WifiInfo.ssid]. */
-    val ssid: String? = null,
-    /** See [android.net.wifi.WifiInfo.isPasspointAp]. */
-    val isPasspointAccessPoint: Boolean = false,
-    /** See [android.net.wifi.WifiInfo.isOsuAp]. */
-    val isOnlineSignUpForPasspointAccessPoint: Boolean = false,
-    /** See [android.net.wifi.WifiInfo.passpointProviderFriendlyName]. */
-    val passpointProviderFriendlyName: String? = null,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
new file mode 100644
index 0000000..5566fa6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.data.model
+
+/** Provides information about the current wifi network. */
+sealed class WifiNetworkModel {
+    /** A model representing that we have no active wifi network. */
+    object Inactive : WifiNetworkModel()
+
+    /** Provides information about an active wifi network. */
+    class Active(
+        /**
+         * The [android.net.Network.netId] we received from
+         * [android.net.ConnectivityManager.NetworkCallback] in association with this wifi network.
+         *
+         * Importantly, **not** [android.net.wifi.WifiInfo.getNetworkId].
+         */
+        val networkId: Int,
+
+        /** See [android.net.wifi.WifiInfo.ssid]. */
+        val ssid: String? = null,
+
+        /** See [android.net.wifi.WifiInfo.isPasspointAp]. */
+        val isPasspointAccessPoint: Boolean = false,
+
+        /** See [android.net.wifi.WifiInfo.isOsuAp]. */
+        val isOnlineSignUpForPasspointAccessPoint: Boolean = false,
+
+        /** See [android.net.wifi.WifiInfo.passpointProviderFriendlyName]. */
+        val passpointProviderFriendlyName: String? = null,
+    ) : WifiNetworkModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepo.kt
deleted file mode 100644
index 6c0a445..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepo.kt
+++ /dev/null
@@ -1,94 +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.
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.statusbar.pipeline.wifi.data.repository
-
-import android.annotation.SuppressLint
-import android.net.ConnectivityManager
-import android.net.Network
-import android.net.NetworkCapabilities
-import android.net.NetworkRequest
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.stateIn
-
-/**
- * Repository that contains all relevant [NetworkCapabilities] for the current networks.
- *
- * TODO(b/238425913): Figure out how to merge this with [WifiRepository].
- */
-@SysUISingleton
-class NetworkCapabilitiesRepo @Inject constructor(
-    connectivityManager: ConnectivityManager,
-    @Application scope: CoroutineScope,
-    logger: ConnectivityPipelineLogger,
-) {
-    @SuppressLint("MissingPermission")
-    val dataStream: StateFlow<Map<Int, NetworkCapabilityInfo>> = run {
-        var state = emptyMap<Int, NetworkCapabilityInfo>()
-        callbackFlow {
-                val networkRequest: NetworkRequest =
-                    NetworkRequest.Builder()
-                        .clearCapabilities()
-                        .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
-                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                        .build()
-                val callback =
-                    // TODO (b/240569788): log these using [LogBuffer]
-                    object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
-                        override fun onCapabilitiesChanged(
-                            network: Network,
-                            networkCapabilities: NetworkCapabilities
-                        ) {
-                            logger.logOnCapabilitiesChanged(network, networkCapabilities)
-                            state =
-                                state.toMutableMap().also {
-                                    it[network.getNetId()] =
-                                        NetworkCapabilityInfo(network, networkCapabilities)
-                                }
-                            trySend(state)
-                        }
-
-                        override fun onLost(network: Network) {
-                            logger.logOnLost(network)
-                            state = state.toMutableMap().also { it.remove(network.getNetId()) }
-                            trySend(state)
-                        }
-                    }
-                connectivityManager.registerNetworkCallback(networkRequest, callback)
-
-                awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
-            }
-            .stateIn(scope, started = Lazily, initialValue = state)
-    }
-}
-
-/** contains info about network capabilities. */
-data class NetworkCapabilityInfo(
-    val network: Network,
-    val capabilities: NetworkCapabilities,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index 012dde5..43fbabc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -16,16 +16,26 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.data.repository
 
+import android.annotation.SuppressLint
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.net.wifi.WifiInfo
 import android.net.wifi.WifiManager
 import android.net.wifi.WifiManager.TrafficStateCallback
 import android.util.Log
+import com.android.settingslib.Utils
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -38,9 +48,9 @@
  */
 interface WifiRepository {
     /**
-     * Observable for the current state of wifi; `null` when there is no active wifi.
+     * Observable for the current wifi network.
      */
-    val wifiModel: Flow<WifiModel?>
+    val wifiNetwork: Flow<WifiNetworkModel>
 
     /**
      * Observable for the current wifi network activity.
@@ -51,14 +61,57 @@
 /** Real implementation of [WifiRepository]. */
 @OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
+@SuppressLint("MissingPermission")
 class WifiRepositoryImpl @Inject constructor(
+        connectivityManager: ConnectivityManager,
         wifiManager: WifiManager?,
         @Main mainExecutor: Executor,
         logger: ConnectivityPipelineLogger,
 ) : WifiRepository {
+    override val wifiNetwork: Flow<WifiNetworkModel> = conflatedCallbackFlow {
+        var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
 
-    // TODO(b/238425913): Actually implement the wifiModel flow.
-    override val wifiModel: Flow<WifiModel?> = flowOf(WifiModel(ssid = "AB"))
+        val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
+            override fun onCapabilitiesChanged(
+                network: Network,
+                networkCapabilities: NetworkCapabilities
+            ) {
+                logger.logOnCapabilitiesChanged(network, networkCapabilities)
+
+                val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
+                if (wifiInfo?.isPrimary == true) {
+                    val wifiNetworkModel = wifiInfoToModel(wifiInfo, network.getNetId())
+                    logger.logTransformation(
+                        WIFI_NETWORK_CALLBACK_NAME,
+                        oldValue = currentWifi,
+                        newValue = wifiNetworkModel
+                    )
+                    currentWifi = wifiNetworkModel
+                    trySend(wifiNetworkModel)
+                }
+            }
+
+            override fun onLost(network: Network) {
+                logger.logOnLost(network)
+                val wifi = currentWifi
+                if (wifi is WifiNetworkModel.Active && wifi.networkId == network.getNetId()) {
+                    val newNetworkModel = WifiNetworkModel.Inactive
+                    logger.logTransformation(
+                        WIFI_NETWORK_CALLBACK_NAME,
+                        oldValue = wifi,
+                        newValue = newNetworkModel
+                    )
+                    currentWifi = newNetworkModel
+                    trySend(newNetworkModel)
+                }
+            }
+        }
+
+        trySend(WIFI_NETWORK_DEFAULT)
+        connectivityManager.registerNetworkCallback(WIFI_NETWORK_CALLBACK_REQUEST, callback)
+
+        awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+    }
 
     override val wifiActivity: Flow<WifiActivityModel> =
             if (wifiManager == null) {
@@ -71,8 +124,8 @@
                         trySend(trafficStateToWifiActivityModel(state))
                     }
 
-                    wifiManager.registerTrafficStateCallback(mainExecutor, callback)
                     trySend(ACTIVITY_DEFAULT)
+                    wifiManager.registerTrafficStateCallback(mainExecutor, callback)
 
                     awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
                 }
@@ -80,6 +133,13 @@
 
     companion object {
         val ACTIVITY_DEFAULT = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+        // Start out with no known wifi network.
+        // Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an
+        // initial fetch to get a starting wifi network. But, it uses a deprecated API
+        // [WifiManager.getConnectionInfo()], and the deprecation doc indicates to just use
+        // [ConnectivityManager.NetworkCallback] results instead. So, for now we'll just rely on the
+        // NetworkCallback inside [wifiNetwork] for our wifi network information.
+        val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive
 
         private fun trafficStateToWifiActivityModel(state: Int): WifiActivityModel {
             return WifiActivityModel(
@@ -90,6 +150,30 @@
             )
         }
 
+        private fun networkCapabilitiesToWifiInfo(
+            networkCapabilities: NetworkCapabilities
+        ): WifiInfo? {
+            return when {
+                networkCapabilities.hasTransport(TRANSPORT_WIFI) ->
+                    networkCapabilities.transportInfo as WifiInfo?
+                networkCapabilities.hasTransport(TRANSPORT_CELLULAR) ->
+                    // Sometimes, cellular networks can act as wifi networks (known as VCN --
+                    // virtual carrier network). So, see if this cellular network has wifi info.
+                    Utils.tryGetWifiInfoForVcn(networkCapabilities)
+                else -> null
+            }
+        }
+
+        private fun wifiInfoToModel(wifiInfo: WifiInfo, networkId: Int): WifiNetworkModel {
+            return WifiNetworkModel.Active(
+                networkId,
+                wifiInfo.ssid,
+                wifiInfo.isPasspointAp,
+                wifiInfo.isOsuAp,
+                wifiInfo.passpointProviderFriendlyName
+            )
+        }
+
         private fun prettyPrintActivity(activity: Int): String {
             return when (activity) {
                 TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
@@ -99,5 +183,15 @@
                 else -> "INVALID"
             }
         }
+
+        private val WIFI_NETWORK_CALLBACK_REQUEST: NetworkRequest =
+            NetworkRequest.Builder()
+                .clearCapabilities()
+                .addCapability(NET_CAPABILITY_NOT_VPN)
+                .addTransportType(TRANSPORT_WIFI)
+                .addTransportType(TRANSPORT_CELLULAR)
+                .build()
+
+        private const val WIFI_NETWORK_CALLBACK_NAME = "wifiNetworkModel"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index f705399..a9da63b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -18,6 +18,7 @@
 
 import android.net.wifi.WifiManager
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -34,13 +35,15 @@
 class WifiInteractor @Inject constructor(
         repository: WifiRepository,
 ) {
-    private val ssid: Flow<String?> = repository.wifiModel.map { info ->
-        when {
-            info == null -> null
-            info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint ->
-                info.passpointProviderFriendlyName
-            info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid
-            else -> null
+    private val ssid: Flow<String?> = repository.wifiNetwork.map { info ->
+        when (info) {
+            is WifiNetworkModel.Inactive -> null
+            is WifiNetworkModel.Active -> when {
+                info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint ->
+                    info.passpointProviderFriendlyName
+                info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid
+                else -> null
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
new file mode 100644
index 0000000..b06aaf4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.ui.binder
+
+import android.content.res.ColorStateList
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/**
+ * Binds a wifi icon in the status bar to its view-model.
+ *
+ * To use this properly, users should maintain a one-to-one relationship between the [View] and the
+ * view-binding, binding each view only once. It is okay and expected for the same instance of the
+ * view-model to be reused for multiple view/view-binder bindings.
+ */
+@OptIn(InternalCoroutinesApi::class)
+object WifiViewBinder {
+    /** Binds the view to the view-model, continuing to update the former based on the latter. */
+    @JvmStatic
+    fun bind(
+        view: ViewGroup,
+        viewModel: WifiViewModel,
+    ) {
+        val iconView = view.requireViewById<ImageView>(R.id.wifi_signal)
+
+        view.isVisible = true
+        iconView.isVisible = true
+        iconView.setImageDrawable(view.context.getDrawable(WIFI_FULL_ICONS[2]))
+
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.tint.collect { tint ->
+                        iconView.imageTintList = ColorStateList.valueOf(tint)
+                    }
+                }
+            }
+        }
+
+        // TODO(b/238425913): Hook up to [viewModel] to render actual changes to the wifi icon.
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
new file mode 100644
index 0000000..c14a897
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.ui.view
+
+import android.content.Context
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import com.android.systemui.R
+import com.android.systemui.statusbar.BaseStatusBarWifiView
+import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
+
+/**
+ * A new and more modern implementation of [com.android.systemui.statusbar.StatusBarWifiView] that
+ * is updated by [WifiViewBinder].
+ */
+class ModernStatusBarWifiView(
+    context: Context,
+    attrs: AttributeSet?
+) : BaseStatusBarWifiView(context, attrs) {
+
+    private lateinit var slot: String
+
+    override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
+        // TODO(b/238425913)
+    }
+
+    override fun getSlot() = slot
+
+    override fun setStaticDrawableColor(color: Int) {
+        // TODO(b/238425913)
+    }
+
+    override fun setDecorColor(color: Int) {
+        // TODO(b/238425913)
+    }
+
+    override fun setVisibleState(state: Int, animate: Boolean) {
+        // TODO(b/238425913)
+    }
+
+    override fun getVisibleState(): Int {
+        // TODO(b/238425913)
+        return STATE_ICON
+    }
+
+    override fun isIconVisible(): Boolean {
+        // TODO(b/238425913)
+        return true
+    }
+
+    /** Set the slot name for this view. */
+    private fun setSlot(slotName: String) {
+        this.slot = slotName
+    }
+
+    companion object {
+        /**
+         * Inflates a new instance of [ModernStatusBarWifiView], binds it to [viewModel], and
+         * returns it.
+         */
+        @JvmStatic
+        fun constructAndBind(
+            context: Context,
+            slot: String,
+            viewModel: WifiViewModel,
+        ): ModernStatusBarWifiView {
+            return (
+                LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
+                    as ModernStatusBarWifiView
+                ).also {
+                    it.setSlot(slot)
+                    WifiViewBinder.bind(it, viewModel)
+                }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index b990eb7..7a26260 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -16,12 +16,15 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
+import android.graphics.Color
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.flowOf
 
 /**
@@ -30,9 +33,10 @@
  * TODO(b/238425913): Hook this up to the real status bar wifi view using a view binder.
  */
 class WifiViewModel @Inject constructor(
-        private val constants: WifiConstants,
-        private val logger: ConnectivityPipelineLogger,
-        private val interactor: WifiInteractor,
+    statusBarPipelineFlags: StatusBarPipelineFlags,
+    private val constants: WifiConstants,
+    private val logger: ConnectivityPipelineLogger,
+    private val interactor: WifiInteractor,
 ) {
     val isActivityInVisible: Flow<Boolean>
         get() =
@@ -42,4 +46,11 @@
                 interactor.hasActivityIn
             }
                 .logOutputChange(logger, "activityInVisible")
+
+    /** The tint that should be applied to the icon. */
+    val tint: Flow<Int> = if (!statusBarPipelineFlags.useNewPipelineDebugColoring()) {
+        emptyFlow()
+    } else {
+        flowOf(Color.CYAN)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 699414c..e4e59a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -32,7 +32,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.Dependency;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -81,12 +80,15 @@
         }
     }
 
-    public HeadsUpManager(@NonNull final Context context, HeadsUpManagerLogger logger,
-            @Main Handler handler) {
+    public HeadsUpManager(@NonNull final Context context,
+            HeadsUpManagerLogger logger,
+            @Main Handler handler,
+            AccessibilityManagerWrapper accessibilityManagerWrapper,
+            UiEventLogger uiEventLogger) {
         super(logger, handler);
         mContext = context;
-        mAccessibilityMgr = Dependency.get(AccessibilityManagerWrapper.class);
-        mUiEventLogger = Dependency.get(UiEventLogger.class);
+        mAccessibilityMgr = accessibilityManagerWrapper;
+        mUiEventLogger = uiEventLogger;
         Resources resources = context.getResources();
         mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
         mAutoDismissNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay);
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 dfcdaef..836d571 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -45,7 +45,6 @@
 import android.provider.Settings;
 import android.telephony.TelephonyCallback;
 import android.text.TextUtils;
-import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -287,10 +286,6 @@
         refreshUsers(UserHandle.USER_NULL);
     }
 
-    private static boolean isEnableGuestModeUxChanges(Context context) {
-        return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_GUEST_MODE_UX_CHANGES);
-    }
-
     /**
      * Refreshes users from UserManager.
      *
@@ -549,17 +544,9 @@
         }
 
         if (currUserInfo != null && currUserInfo.isGuest()) {
-            if (isEnableGuestModeUxChanges(mContext)) {
-                showExitGuestDialog(currUserId, currUserInfo.isEphemeral(),
-                        record.resolveId(), dialogShower);
-                return;
-            } else {
-                if (currUserInfo.isEphemeral()) {
-                    showExitGuestDialog(currUserId, currUserInfo.isEphemeral(),
-                            record.resolveId(), dialogShower);
-                    return;
-                }
-            }
+            showExitGuestDialog(currUserId, currUserInfo.isEphemeral(),
+                    record.resolveId(), dialogShower);
+            return;
         }
 
         if (dialogShower != null) {
@@ -1056,14 +1043,8 @@
         public String getName(Context context, UserRecord item) {
             if (item.isGuest) {
                 if (item.isCurrent) {
-                    if (isEnableGuestModeUxChanges(context)) {
-                        return context.getString(
-                                com.android.settingslib.R.string.guest_exit_quick_settings_button);
-                    } else {
-                        return context.getString(mController.mGuestUserAutoCreated
-                            ? com.android.settingslib.R.string.guest_reset_guest
-                            : com.android.settingslib.R.string.guest_exit_guest);
-                    }
+                    return context.getString(
+                            com.android.settingslib.R.string.guest_exit_quick_settings_button);
                 } else {
                     if (item.info != null) {
                         return context.getString(com.android.internal.R.string.guest_name);
@@ -1080,13 +1061,8 @@
                                             ? com.android.settingslib.R.string.guest_resetting
                                             : com.android.internal.R.string.guest_name);
                         } else {
-                            if (isEnableGuestModeUxChanges(context)) {
-                                // we always show "guest" as string, instead of "add guest"
-                                return context.getString(com.android.internal.R.string.guest_name);
-                            } else {
-                                return context.getString(
-                                        com.android.settingslib.R.string.guest_new_guest);
-                            }
+                            // we always show "guest" as string, instead of "add guest"
+                            return context.getString(com.android.internal.R.string.guest_name);
                         }
                     }
                 }
@@ -1108,11 +1084,7 @@
         protected static Drawable getIconDrawable(Context context, UserRecord item) {
             int iconRes;
             if (item.isAddUser) {
-                if (isEnableGuestModeUxChanges(context)) {
-                    iconRes = R.drawable.ic_add;
-                } else {
-                    iconRes = R.drawable.ic_account_circle_filled;
-                }
+                iconRes = R.drawable.ic_add;
             } else if (item.isGuest) {
                 iconRes = R.drawable.ic_account_circle;
             } else if (item.isAddSupervisedUser) {
@@ -1289,46 +1261,32 @@
         ExitGuestDialog(Context context, int guestId, boolean isGuestEphemeral,
                     int targetId) {
             super(context);
-            if (isEnableGuestModeUxChanges(context)) {
-                if (isGuestEphemeral) {
-                    setTitle(context.getString(
-                                com.android.settingslib.R.string.guest_exit_dialog_title));
-                    setMessage(context.getString(
-                                com.android.settingslib.R.string.guest_exit_dialog_message));
-                    setButton(DialogInterface.BUTTON_NEUTRAL,
-                            context.getString(android.R.string.cancel), this);
-                    setButton(DialogInterface.BUTTON_POSITIVE,
-                            context.getString(
-                                com.android.settingslib.R.string.guest_exit_dialog_button), this);
-                } else {
-                    setTitle(context.getString(
-                                com.android.settingslib
-                                    .R.string.guest_exit_dialog_title_non_ephemeral));
-                    setMessage(context.getString(
-                                com.android.settingslib
-                                    .R.string.guest_exit_dialog_message_non_ephemeral));
-                    setButton(DialogInterface.BUTTON_NEUTRAL,
-                            context.getString(android.R.string.cancel), this);
-                    setButton(DialogInterface.BUTTON_NEGATIVE,
-                            context.getString(
-                                com.android.settingslib.R.string.guest_exit_clear_data_button),
-                            this);
-                    setButton(DialogInterface.BUTTON_POSITIVE,
-                            context.getString(
-                                com.android.settingslib.R.string.guest_exit_save_data_button),
-                            this);
-                }
-            } else {
-                setTitle(mGuestUserAutoCreated
-                        ? com.android.settingslib.R.string.guest_reset_guest_dialog_title
-                        : com.android.settingslib.R.string.guest_remove_guest_dialog_title);
-                setMessage(context.getString(R.string.guest_exit_guest_dialog_message));
+            if (isGuestEphemeral) {
+                setTitle(context.getString(
+                            com.android.settingslib.R.string.guest_exit_dialog_title));
+                setMessage(context.getString(
+                            com.android.settingslib.R.string.guest_exit_dialog_message));
                 setButton(DialogInterface.BUTTON_NEUTRAL,
                         context.getString(android.R.string.cancel), this);
                 setButton(DialogInterface.BUTTON_POSITIVE,
-                        context.getString(mGuestUserAutoCreated
-                            ? com.android.settingslib.R.string.guest_reset_guest_confirm_button
-                            : com.android.settingslib.R.string.guest_remove_guest_confirm_button),
+                        context.getString(
+                            com.android.settingslib.R.string.guest_exit_dialog_button), this);
+            } else {
+                setTitle(context.getString(
+                            com.android.settingslib
+                                .R.string.guest_exit_dialog_title_non_ephemeral));
+                setMessage(context.getString(
+                            com.android.settingslib
+                                .R.string.guest_exit_dialog_message_non_ephemeral));
+                setButton(DialogInterface.BUTTON_NEUTRAL,
+                        context.getString(android.R.string.cancel), this);
+                setButton(DialogInterface.BUTTON_NEGATIVE,
+                        context.getString(
+                            com.android.settingslib.R.string.guest_exit_clear_data_button),
+                        this);
+                setButton(DialogInterface.BUTTON_POSITIVE,
+                        context.getString(
+                            com.android.settingslib.R.string.guest_exit_save_data_button),
                         this);
             }
             SystemUIDialog.setWindowOnTop(this, mKeyguardStateController.isShowing());
@@ -1345,39 +1303,29 @@
             if (mFalsingManager.isFalseTap(penalty)) {
                 return;
             }
-            if (isEnableGuestModeUxChanges(getContext())) {
-                if (mIsGuestEphemeral) {
-                    if (which == DialogInterface.BUTTON_POSITIVE) {
-                        mDialogLaunchAnimator.dismissStack(this);
-                        // Ephemeral guest: exit guest, guest is removed by the system
-                        // on exit, since its marked ephemeral
-                        exitGuestUser(mGuestId, mTargetId, false);
-                    } else if (which == DialogInterface.BUTTON_NEGATIVE) {
-                        // Cancel clicked, do nothing
-                        cancel();
-                    }
-                } else {
-                    if (which == DialogInterface.BUTTON_POSITIVE) {
-                        mDialogLaunchAnimator.dismissStack(this);
-                        // Non-ephemeral guest: exit guest, guest is not removed by the system
-                        // on exit, since its marked non-ephemeral
-                        exitGuestUser(mGuestId, mTargetId, false);
-                    } else if (which == DialogInterface.BUTTON_NEGATIVE) {
-                        mDialogLaunchAnimator.dismissStack(this);
-                        // Non-ephemeral guest: remove guest and then exit
-                        exitGuestUser(mGuestId, mTargetId, true);
-                    } else if (which == DialogInterface.BUTTON_NEUTRAL) {
-                        // Cancel clicked, do nothing
-                        cancel();
-                    }
+            if (mIsGuestEphemeral) {
+                if (which == DialogInterface.BUTTON_POSITIVE) {
+                    mDialogLaunchAnimator.dismissStack(this);
+                    // Ephemeral guest: exit guest, guest is removed by the system
+                    // on exit, since its marked ephemeral
+                    exitGuestUser(mGuestId, mTargetId, false);
+                } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+                    // Cancel clicked, do nothing
+                    cancel();
                 }
             } else {
-                if (which == BUTTON_NEUTRAL) {
-                    cancel();
-                } else {
-                    mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
+                if (which == DialogInterface.BUTTON_POSITIVE) {
                     mDialogLaunchAnimator.dismissStack(this);
-                    removeGuestUser(mGuestId, mTargetId);
+                    // Non-ephemeral guest: exit guest, guest is not removed by the system
+                    // on exit, since its marked non-ephemeral
+                    exitGuestUser(mGuestId, mTargetId, false);
+                } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+                    mDialogLaunchAnimator.dismissStack(this);
+                    // Non-ephemeral guest: remove guest and then exit
+                    exitGuestUser(mGuestId, mTargetId, true);
+                } else if (which == DialogInterface.BUTTON_NEUTRAL) {
+                    // Cancel clicked, do nothing
+                    cancel();
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 094490b..adef182 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -244,7 +244,8 @@
         final int currentUser = mUserTracker.getUserId();
         final boolean hadWallpaperColors = mCurrentColors.get(userId) != null;
         int latestWallpaperType = getLatestWallpaperType(userId);
-        if ((flags & latestWallpaperType) != 0) {
+        boolean eventForLatestWallpaper = (flags & latestWallpaperType) != 0;
+        if (eventForLatestWallpaper) {
             mCurrentColors.put(userId, wallpaperColors);
             if (DEBUG) Log.d(TAG, "got new colors: " + wallpaperColors + " where: " + flags);
         }
@@ -280,14 +281,19 @@
                 currentUser);
         boolean isDestinationBoth = (flags == (WallpaperManager.FLAG_SYSTEM
                 | WallpaperManager.FLAG_LOCK));
+        boolean isDestinationHomeOnly = (flags == WallpaperManager.FLAG_SYSTEM);
         try {
             JSONObject jsonObject = (overlayPackageJson == null) ? new JSONObject()
                     : new JSONObject(overlayPackageJson);
             // The latest applied wallpaper should be the source of system colors when:
             // There is not preset color applied and the incoming wallpaper color is not applied
-            if (!COLOR_SOURCE_PRESET.equals(jsonObject.optString(OVERLAY_COLOR_SOURCE))
-                    && ((flags & latestWallpaperType) != 0 && !isSeedColorSet(jsonObject,
-                    wallpaperColors))) {
+            String wallpaperPickerColorSource = jsonObject.optString(OVERLAY_COLOR_SOURCE);
+            boolean userChosePresetColor = COLOR_SOURCE_PRESET.equals(wallpaperPickerColorSource);
+            boolean userChoseLockScreenColor = COLOR_SOURCE_LOCK.equals(wallpaperPickerColorSource);
+            boolean preserveLockScreenColor = isDestinationHomeOnly && userChoseLockScreenColor;
+
+            if (!userChosePresetColor && !preserveLockScreenColor && eventForLatestWallpaper
+                    && !isSeedColorSet(jsonObject, wallpaperColors)) {
                 mSkipSettingChange = true;
                 if (jsonObject.has(OVERLAY_CATEGORY_ACCENT_COLOR) || jsonObject.has(
                         OVERLAY_CATEGORY_SYSTEM_PALETTE)) {
@@ -642,7 +648,7 @@
         }
         if (mNeedsOverlayCreation) {
             mNeedsOverlayCreation = false;
-            mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] {
+            mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[]{
                     mSecondaryOverlay, mNeutralOverlay
             }, currentUser, managedProfiles);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 0fe10cb..abffe555 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -26,6 +26,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.keyguard.KeyguardViewController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
@@ -63,6 +64,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryControllerImpl;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -173,7 +175,9 @@
             GroupMembershipManager groupManager,
             VisualStabilityProvider visualStabilityProvider,
             ConfigurationController configurationController,
-            @Main Handler handler) {
+            @Main Handler handler,
+            AccessibilityManagerWrapper accessibilityManagerWrapper,
+            UiEventLogger uiEventLogger) {
         return new HeadsUpManagerPhone(
                 context,
                 headsUpManagerLogger,
@@ -182,7 +186,9 @@
                 groupManager,
                 visualStabilityProvider,
                 configurationController,
-                handler
+                handler,
+                accessibilityManagerWrapper,
+                uiEventLogger
         );
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/SysuiLifecycle.java b/packages/SystemUI/src/com/android/systemui/util/SysuiLifecycle.java
deleted file mode 100644
index d731753..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/SysuiLifecycle.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util;
-
-import static androidx.lifecycle.Lifecycle.State.DESTROYED;
-import static androidx.lifecycle.Lifecycle.State.RESUMED;
-
-import android.view.View;
-import android.view.View.OnAttachStateChangeListener;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.LifecycleRegistry;
-
-/**
- * Tools for generating lifecycle from sysui objects.
- */
-public class SysuiLifecycle {
-
-    private SysuiLifecycle() {
-    }
-
-    /**
-     * Get a lifecycle that will be put into the resumed state when the view is attached
-     * and goes to the destroyed state when the view is detached.
-     */
-    public static LifecycleOwner viewAttachLifecycle(View v) {
-        return new ViewLifecycle(v);
-    }
-
-    private static class ViewLifecycle implements LifecycleOwner, OnAttachStateChangeListener {
-        private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
-
-        ViewLifecycle(View v) {
-            v.addOnAttachStateChangeListener(this);
-            if (v.isAttachedToWindow()) {
-                mLifecycle.markState(RESUMED);
-            }
-        }
-
-        @NonNull
-        @Override
-        public Lifecycle getLifecycle() {
-            return mLifecycle;
-        }
-
-        @Override
-        public void onViewAttachedToWindow(View v) {
-            mLifecycle.markState(RESUMED);
-        }
-
-        @Override
-        public void onViewDetachedFromWindow(View v) {
-            mLifecycle.markState(DESTROYED);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/IpcSerializer.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/IpcSerializer.kt
new file mode 100644
index 0000000..c0331e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/IpcSerializer.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.runBlocking
+
+/**
+ * A utility for handling incoming IPCs from a Binder interface in the order that they are received.
+ *
+ * This class serves as a replacement for the common [android.os.Handler] message-queue pattern,
+ * where IPCs can arrive on arbitrary threads and are all enqueued onto a queue and processed by the
+ * Handler in-order.
+ *
+ *     class MyService : Service() {
+ *
+ *       private val serializer = IpcSerializer()
+ *
+ *       // Need to invoke process() in order to actually process IPCs sent over the serializer.
+ *       override fun onStart(...) = lifecycleScope.launch {
+ *         serializer.process()
+ *       }
+ *
+ *       // In your binder implementation, use runSerializedBlocking to enqueue a function onto
+ *       // the serializer.
+ *       override fun onBind(intent: Intent?) = object : IAidlService.Stub() {
+ *         override fun ipcMethodFoo() = serializer.runSerializedBlocking {
+ *           ...
+ *         }
+ *
+ *         override fun ipcMethodBar() = serializer.runSerializedBlocking {
+ *           ...
+ *         }
+ *       }
+ *     }
+ */
+class IpcSerializer {
+
+    private val channel = Channel<Pair<CompletableDeferred<Unit>, Job>>()
+
+    /**
+     * Runs functions enqueued via usage of [runSerialized] and [runSerializedBlocking] serially.
+     * This method will never complete normally, so it must be launched in its own coroutine; if
+     * this is not actively running, no enqueued functions will be evaluated.
+     */
+    suspend fun process(): Nothing {
+        for ((start, finish) in channel) {
+            // Signal to the sender that serializer has reached this message
+            start.complete(Unit)
+            // Wait to hear from the sender that it has finished running it's work, before handling
+            // the next message
+            finish.join()
+        }
+        error("Unexpected end of serialization channel")
+    }
+
+    /**
+     * Enqueues [block] for evaluation by the serializer, suspending the caller until it has
+     * completed. It is up to the caller to define what thread this is evaluated in, determined
+     * by the [kotlin.coroutines.CoroutineContext] used.
+     */
+    suspend fun <R> runSerialized(block: suspend () -> R): R {
+        val start = CompletableDeferred(Unit)
+        val finish = CompletableDeferred(Unit)
+        // Enqueue our message on the channel.
+        channel.send(start to finish)
+        // Wait for the serializer to reach our message
+        start.await()
+        // Now evaluate the block
+        val result = block()
+        // Notify the serializer that we've completed evaluation
+        finish.complete(Unit)
+        return result
+    }
+
+    /**
+     * Enqueues [block] for evaluation by the serializer, blocking the binder thread until it has
+     * completed. Evaluation occurs on the binder thread, so methods like
+     * [android.os.Binder.getCallingUid] that depend on the current thread will work as expected.
+     */
+    fun <R> runSerializedBlocking(block: suspend () -> R): R = runBlocking { runSerialized(block) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 4c76270..199048e 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -38,6 +38,7 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.service.dreams.IDreamManager;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.ZenModeConfig;
 import android.util.Log;
@@ -49,9 +50,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dump.DumpManager;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -76,7 +75,6 @@
 import com.android.wm.shell.bubbles.BubbleEntry;
 import com.android.wm.shell.bubbles.Bubbles;
 
-import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -91,7 +89,7 @@
  * The SysUi side bubbles manager which communicate with other SysUi components.
  */
 @SysUISingleton
-public class BubblesManager implements Dumpable {
+public class BubblesManager {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubblesManager" : TAG_BUBBLES;
 
@@ -101,6 +99,7 @@
     private final ShadeController mShadeController;
     private final IStatusBarService mBarService;
     private final INotificationManager mNotificationManager;
+    private final IDreamManager mDreamManager;
     private final NotificationVisibilityProvider mVisibilityProvider;
     private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     private final NotificationLockscreenUserManager mNotifUserManager;
@@ -126,6 +125,7 @@
             ShadeController shadeController,
             @Nullable IStatusBarService statusBarService,
             INotificationManager notificationManager,
+            IDreamManager dreamManager,
             NotificationVisibilityProvider visibilityProvider,
             NotificationInterruptStateProvider interruptionStateProvider,
             ZenModeController zenModeController,
@@ -134,7 +134,6 @@
             CommonNotifCollection notifCollection,
             NotifPipeline notifPipeline,
             SysUiState sysUiState,
-            DumpManager dumpManager,
             Executor sysuiMainExecutor) {
         if (bubblesOptional.isPresent()) {
             return new BubblesManager(context,
@@ -144,6 +143,7 @@
                     shadeController,
                     statusBarService,
                     notificationManager,
+                    dreamManager,
                     visibilityProvider,
                     interruptionStateProvider,
                     zenModeController,
@@ -152,7 +152,6 @@
                     notifCollection,
                     notifPipeline,
                     sysUiState,
-                    dumpManager,
                     sysuiMainExecutor);
         } else {
             return null;
@@ -167,6 +166,7 @@
             ShadeController shadeController,
             @Nullable IStatusBarService statusBarService,
             INotificationManager notificationManager,
+            IDreamManager dreamManager,
             NotificationVisibilityProvider visibilityProvider,
             NotificationInterruptStateProvider interruptionStateProvider,
             ZenModeController zenModeController,
@@ -175,13 +175,13 @@
             CommonNotifCollection notifCollection,
             NotifPipeline notifPipeline,
             SysUiState sysUiState,
-            DumpManager dumpManager,
             Executor sysuiMainExecutor) {
         mContext = context;
         mBubbles = bubbles;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mShadeController = shadeController;
         mNotificationManager = notificationManager;
+        mDreamManager = dreamManager;
         mVisibilityProvider = visibilityProvider;
         mNotificationInterruptStateProvider = interruptionStateProvider;
         mNotifUserManager = notifUserManager;
@@ -197,13 +197,11 @@
 
         setupNotifPipeline();
 
-        dumpManager.registerDumpable(TAG, this);
-
         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
             public void onKeyguardShowingChanged() {
                 boolean isUnlockedShade = !keyguardStateController.isShowing()
-                        && !keyguardStateController.isOccluded();
+                        && !isDreamingOrInPreview();
                 bubbles.onStatusBarStateChanged(isUnlockedShade);
             }
         });
@@ -397,6 +395,15 @@
         mBubbles.setSysuiProxy(mSysuiProxy);
     }
 
+    private boolean isDreamingOrInPreview() {
+        try {
+            return mDreamManager.isDreamingOrInPreview();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to query dream manager.", e);
+            return false;
+        }
+    }
+
     private void setupNotifPipeline() {
         mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
             @Override
@@ -633,11 +640,6 @@
         }
     }
 
-    @Override
-    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
-        mBubbles.dump(pw, args);
-    }
-
     /** Checks whether bubbles are enabled for this user, handles negative userIds. */
     public static boolean areBubblesEnabled(@NonNull Context context, @NonNull UserHandle user) {
         if (user.getIdentifier() < 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index eba2795..a4a59fc 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -29,14 +29,16 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
 
 import android.content.Context;
+import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.inputmethodservice.InputMethodService;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.view.KeyEvent;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -47,11 +49,11 @@
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.tracing.ProtoTraceable;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.tracing.nano.SystemUiTraceProto;
 import com.android.wm.shell.nano.WmShellTraceProto;
@@ -66,6 +68,7 @@
 
 import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Optional;
 import java.util.concurrent.Executor;
 
@@ -115,7 +118,7 @@
     private final SysUiState mSysUiState;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
     private final ProtoTracer mProtoTracer;
-    private final UserInfoController mUserInfoController;
+    private final UserTracker mUserTracker;
     private final Executor mSysUiMainExecutor;
 
     // Listeners and callbacks. Note that we prefer member variable over anonymous class here to
@@ -144,9 +147,20 @@
                     mShell.onKeyguardDismissAnimationFinished();
                 }
             };
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    mShell.onUserChanged(newUser, userContext);
+                }
+
+                @Override
+                public void onProfilesChanged(@NonNull List<UserInfo> profiles) {
+                    mShell.onUserProfilesChanged(profiles);
+                }
+            };
 
     private boolean mIsSysUiStateValid;
-    private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback;
     private WakefulnessLifecycle.Observer mWakefulnessObserver;
 
     @Inject
@@ -163,7 +177,7 @@
             SysUiState sysUiState,
             ProtoTracer protoTracer,
             WakefulnessLifecycle wakefulnessLifecycle,
-            UserInfoController userInfoController,
+            UserTracker userTracker,
             @Main Executor sysUiMainExecutor) {
         super(context);
         mShell = shell;
@@ -178,7 +192,7 @@
         mOneHandedOptional = oneHandedOptional;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mProtoTracer = protoTracer;
-        mUserInfoController = userInfoController;
+        mUserTracker = userTracker;
         mSysUiMainExecutor = sysUiMainExecutor;
     }
 
@@ -192,8 +206,9 @@
         mKeyguardStateController.addCallback(mKeyguardStateCallback);
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
 
-        // TODO: Consider piping config change and other common calls to a shell component to
-        //  delegate internally
+        // Subscribe to user changes
+        mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+
         mProtoTracer.add(this);
         mCommandQueue.addCallback(this);
         mPipOptional.ifPresent(this::initPip);
@@ -214,10 +229,6 @@
             mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
             pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
         });
-
-        // The media session listener needs to be re-registered when switching users
-        mUserInfoController.addCallback((String name, Drawable picture, String userAccount) ->
-                pip.registerSessionListenerForCurrentUser());
     }
 
     @VisibleForTesting
@@ -267,15 +278,6 @@
             }
         });
 
-        // TODO: Either move into ShellInterface or register a receiver on the Shell side directly
-        mOneHandedKeyguardCallback = new KeyguardUpdateMonitorCallback() {
-            @Override
-            public void onUserSwitchComplete(int userId) {
-                oneHanded.onUserSwitch(userId);
-            }
-        };
-        mKeyguardUpdateMonitor.registerCallback(mOneHandedKeyguardCallback);
-
         mWakefulnessObserver =
                 new WakefulnessLifecycle.Observer() {
                     @Override
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 763a5cb..ba28045 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -53,7 +53,8 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.REGISTER_WINDOW_MANAGER_LISTENERS" />
 
-    <application android:debuggable="true" android:largeHeap="true">
+    <application android:debuggable="true" android:largeHeap="true"
+            android:enableOnBackInvokedCallback="true" >
         <uses-library android:name="android.test.runner" />
 
         <receiver android:name="com.android.systemui.SliceBroadcastRelayHandlerTest$Receiver"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index c281965..3e00aec 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
 import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
 import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -1006,7 +1007,7 @@
 
         // WHEN udfps is now enrolled
         when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
-        callback.onEnrollmentsChanged();
+        callback.onEnrollmentsChanged(TYPE_FINGERPRINT);
 
         // THEN isUdfspEnrolled is TRUE
         assertThat(mKeyguardUpdateMonitor.isUdfpsEnrolled()).isTrue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index d158892..e0d1f7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -60,6 +60,9 @@
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.display.DisplayManager;
 import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -154,7 +157,9 @@
     @Mock
     private InteractionJankMonitor mInteractionJankMonitor;
     @Captor
-    ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mAuthenticatorsRegisteredCaptor;
+    ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mFpAuthenticatorsRegisteredCaptor;
+    @Captor
+    ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mFaceAuthenticatorsRegisteredCaptor;
     @Captor
     ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor;
     @Captor
@@ -193,25 +198,38 @@
         when(mDisplayManager.getStableDisplaySize()).thenReturn(new Point());
 
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFaceManager.isHardwareDetected()).thenReturn(true);
 
-        final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
-        componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
-                "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
-                "00000001" /* serialNumber */, "" /* softwareVersion */));
-        componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
-                "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
-                "vendor/version/revision" /* softwareVersion */));
+        final List<ComponentInfoInternal> fpComponentInfo = List.of(
+                new ComponentInfoInternal("faceSensor" /* componentId */,
+                        "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
+                        "00000001" /* serialNumber */, "" /* softwareVersion */));
+        final List<ComponentInfoInternal> faceComponentInfo = List.of(
+                new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
+                        "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
+                        "vendor/version/revision" /* softwareVersion */));
 
-        FingerprintSensorPropertiesInternal prop = new FingerprintSensorPropertiesInternal(
-                1 /* sensorId */,
-                SensorProperties.STRENGTH_STRONG,
-                1 /* maxEnrollmentsPerUser */,
-                componentInfo,
-                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
-                true /* resetLockoutRequireHardwareAuthToken */);
-        List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
-        props.add(prop);
-        when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
+        final List<FingerprintSensorPropertiesInternal> fpProps = List.of(
+                new FingerprintSensorPropertiesInternal(
+                        1 /* sensorId */,
+                        SensorProperties.STRENGTH_STRONG,
+                        1 /* maxEnrollmentsPerUser */,
+                        fpComponentInfo,
+                        FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+                        true /* resetLockoutRequireHardwareAuthToken */));
+        when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(fpProps);
+
+        final List<FaceSensorPropertiesInternal> faceProps = List.of(
+                new FaceSensorPropertiesInternal(
+                        2 /* sensorId */,
+                        SensorProperties.STRENGTH_STRONG,
+                        1 /* maxEnrollmentsPerUser */,
+                        fpComponentInfo,
+                        FaceSensorProperties.TYPE_RGB,
+                        true /* supportsFaceDetection */,
+                        true /* supportsSelfIllumination */,
+                        true /* resetLockoutRequireHardwareAuthToken */));
+        when(mFaceManager.getSensorPropertiesInternal()).thenReturn(faceProps);
 
         mAuthController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue,
                 mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager,
@@ -219,12 +237,15 @@
 
         mAuthController.start();
         verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
-                mAuthenticatorsRegisteredCaptor.capture());
+                mFpAuthenticatorsRegisteredCaptor.capture());
+        verify(mFaceManager).addAuthenticatorsRegisteredCallback(
+                mFaceAuthenticatorsRegisteredCaptor.capture());
 
         when(mStatusBarStateController.isDozing()).thenReturn(false);
         verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
 
-        mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(props);
+        mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(fpProps);
+        mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(faceProps);
 
         // Ensures that the operations posted on the handler get executed.
         mTestableLooper.processAllMessages();
@@ -237,6 +258,7 @@
             throws RemoteException {
         // This test is sensitive to prior FingerprintManager interactions.
         reset(mFingerprintManager);
+        reset(mFaceManager);
 
         // This test requires an uninitialized AuthController.
         AuthController authController = new TestableAuthController(mContextSpy, mExecution,
@@ -246,21 +268,27 @@
         authController.start();
 
         verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
-                mAuthenticatorsRegisteredCaptor.capture());
+                mFpAuthenticatorsRegisteredCaptor.capture());
+        verify(mFaceManager).addAuthenticatorsRegisteredCallback(
+                mFaceAuthenticatorsRegisteredCaptor.capture());
         mTestableLooper.processAllMessages();
 
         verify(mFingerprintManager, never()).registerBiometricStateListener(any());
+        verify(mFaceManager, never()).registerBiometricStateListener(any());
 
-        mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(new ArrayList<>());
+        mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
+        mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
         mTestableLooper.processAllMessages();
 
         verify(mFingerprintManager).registerBiometricStateListener(any());
+        verify(mFaceManager).registerBiometricStateListener(any());
     }
 
     @Test
     public void testDoesNotCrash_afterEnrollmentsChangedForUnknownSensor() throws RemoteException {
         // This test is sensitive to prior FingerprintManager interactions.
         reset(mFingerprintManager);
+        reset(mFaceManager);
 
         // This test requires an uninitialized AuthController.
         AuthController authController = new TestableAuthController(mContextSpy, mExecution,
@@ -270,18 +298,25 @@
         authController.start();
 
         verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
-                mAuthenticatorsRegisteredCaptor.capture());
+                mFpAuthenticatorsRegisteredCaptor.capture());
+        verify(mFaceManager).addAuthenticatorsRegisteredCallback(
+                mFaceAuthenticatorsRegisteredCaptor.capture());
 
         // Emulates a device with no authenticators (empty list).
-        mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(new ArrayList<>());
+        mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
+        mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
         mTestableLooper.processAllMessages();
 
         verify(mFingerprintManager).registerBiometricStateListener(
                 mBiometricStateCaptor.capture());
+        verify(mFaceManager).registerBiometricStateListener(
+                mBiometricStateCaptor.capture());
 
         // Enrollments changed for an unknown sensor.
-        mBiometricStateCaptor.getValue().onEnrollmentsChanged(0 /* userId */,
-                0xbeef /* sensorId */, true /* hasEnrollments */);
+        for (BiometricStateListener listener : mBiometricStateCaptor.getAllValues()) {
+            listener.onEnrollmentsChanged(0 /* userId */,
+                    0xbeef /* sensorId */, true /* hasEnrollments */);
+        }
         mTestableLooper.processAllMessages();
 
         // Nothing should crash.
@@ -827,4 +862,3 @@
         }
     }
 }
-
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 9ffc5a5..b33f9a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.doze;
 
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
 import static com.android.systemui.doze.DozeLog.REASON_SENSOR_TAP;
 import static com.android.systemui.doze.DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS;
 import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
@@ -412,7 +414,7 @@
 
         // WHEN enrollment changes to TRUE
         when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
-        mAuthControllerCallback.onEnrollmentsChanged();
+        mAuthControllerCallback.onEnrollmentsChanged(TYPE_FINGERPRINT);
 
         // THEN mConfigured = TRUE
         assertTrue(triggerSensor.mConfigured);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index 01309f8..7f6b79b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -308,6 +308,12 @@
     }
 
     @Test
+    public void testOnViewDetachedRemovesViews() {
+        mController.onViewDetached();
+        verify(mView).removeAllStatusBarItemViews();
+    }
+
+    @Test
     public void testWifiIconHiddenWhenWifiBecomesAvailable() {
         // Make sure wifi starts out unavailable when onViewAttached is called, and then returns
         // true on the second query.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
index 09976e0..571dd3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
@@ -106,7 +106,7 @@
 
     private ContentObserver captureSettingsObserver() {
         verify(mSecureSettings).registerContentObserverForUser(
-                eq(Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS),
+                eq(Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED),
                 mSettingsObserverCaptor.capture(), eq(UserHandle.myUserId()));
         return mSettingsObserverCaptor.getValue();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 141a213..b42b769 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -42,8 +42,11 @@
 import android.testing.TestableLooper;
 import android.view.GestureDetector;
 import android.view.IWindowManager;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.WindowManagerPolicyConstants;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
 
 import androidx.test.filters.SmallTest;
 
@@ -73,6 +76,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -117,6 +122,8 @@
     @Mock private CentralSurfaces mCentralSurfaces;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock private DialogLaunchAnimator mDialogLaunchAnimator;
+    @Mock private OnBackInvokedDispatcher mOnBackInvokedDispatcher;
+    @Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
 
     private TestableLooper mTestableLooper;
 
@@ -203,6 +210,58 @@
     }
 
     @Test
+    public void testPredictiveBackCallbackRegisteredAndUnregistered() {
+        mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+        doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+        doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+        doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
+        String[] actions = {
+                GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY,
+                GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN,
+                GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+                GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART,
+        };
+        doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+
+        GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog();
+        dialog.setBackDispatcherOverride(mOnBackInvokedDispatcher);
+        dialog.create();
+        mTestableLooper.processAllMessages();
+        verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+                eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any());
+        dialog.onDetachedFromWindow();
+        mTestableLooper.processAllMessages();
+        verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(any());
+    }
+
+    @Test
+    public void testPredictiveBackInvocationDismissesDialog() {
+        mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+        doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+        doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+        doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
+        String[] actions = {
+                GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY,
+                GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN,
+                GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+                GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART,
+        };
+        doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+
+        GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog();
+        dialog.create();
+        dialog.show();
+        mTestableLooper.processAllMessages();
+        dialog.getWindow().injectInputEvent(
+                new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK));
+        dialog.getWindow().injectInputEvent(
+                new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK));
+        mTestableLooper.processAllMessages();
+        verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_BACK);
+        assertThat(dialog.isShowing()).isFalse();
+    }
+
+    @Test
     public void testSingleTap_logAndDismiss() {
         mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
         doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index 24d0515..5ec6bdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.keyguard;
 
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.keyguard.LockIconView.ICON_LOCK;
 import static com.android.keyguard.LockIconView.ICON_UNLOCK;
@@ -219,7 +221,7 @@
         Pair<Float, PointF> udfps = setupUdfps();
 
         // WHEN all authenticators are registered
-        mAuthControllerCallback.onAllAuthenticatorsRegistered();
+        mAuthControllerCallback.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT);
         mDelayableExecutor.runAllReady();
 
         // THEN lock icon view location is updated with the same coordinates as auth controller vals
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 19491f4..14b85b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -37,6 +37,8 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
+import kotlin.math.max
+import kotlin.math.min
 import kotlin.reflect.KClass
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -127,6 +129,7 @@
         val testConfig =
             TestConfig(
                 isVisible = true,
+                isClickable = true,
                 icon = mock(),
                 canShowWhileLocked = false,
                 intent = Intent("action"),
@@ -154,6 +157,7 @@
         val config =
             TestConfig(
                 isVisible = true,
+                isClickable = true,
                 icon = mock(),
                 canShowWhileLocked = false,
                 intent = null, // This will cause it to tell the system that the click was handled.
@@ -201,6 +205,7 @@
         val testConfig =
             TestConfig(
                 isVisible = true,
+                isClickable = true,
                 icon = mock(),
                 canShowWhileLocked = false,
                 intent = Intent("action"),
@@ -260,6 +265,7 @@
             testConfig =
                 TestConfig(
                     isVisible = true,
+                    isClickable = true,
                     icon = mock(),
                     canShowWhileLocked = true,
                 )
@@ -269,6 +275,7 @@
             testConfig =
                 TestConfig(
                     isVisible = true,
+                    isClickable = true,
                     icon = mock(),
                     canShowWhileLocked = false,
                 )
@@ -342,6 +349,129 @@
         job.cancel()
     }
 
+    @Test
+    fun `isClickable - true when alpha at threshold`() = runBlockingTest {
+        repository.setKeyguardShowing(true)
+        repository.setBottomAreaAlpha(
+            KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD
+        )
+
+        val testConfig =
+            TestConfig(
+                isVisible = true,
+                isClickable = true,
+                icon = mock(),
+                canShowWhileLocked = false,
+                intent = Intent("action"),
+            )
+        val configKey =
+            setUpQuickAffordanceModel(
+                position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                testConfig = testConfig,
+            )
+
+        var latest: KeyguardQuickAffordanceViewModel? = null
+        val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+        assertQuickAffordanceViewModel(
+            viewModel = latest,
+            testConfig = testConfig,
+            configKey = configKey,
+        )
+        job.cancel()
+    }
+
+    @Test
+    fun `isClickable - true when alpha above threshold`() = runBlockingTest {
+        repository.setKeyguardShowing(true)
+        var latest: KeyguardQuickAffordanceViewModel? = null
+        val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+        repository.setBottomAreaAlpha(
+            min(1f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + 0.1f),
+        )
+
+        val testConfig =
+            TestConfig(
+                isVisible = true,
+                isClickable = true,
+                icon = mock(),
+                canShowWhileLocked = false,
+                intent = Intent("action"),
+            )
+        val configKey =
+            setUpQuickAffordanceModel(
+                position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                testConfig = testConfig,
+            )
+
+        assertQuickAffordanceViewModel(
+            viewModel = latest,
+            testConfig = testConfig,
+            configKey = configKey,
+        )
+        job.cancel()
+    }
+
+    @Test
+    fun `isClickable - false when alpha below threshold`() = runBlockingTest {
+        repository.setKeyguardShowing(true)
+        var latest: KeyguardQuickAffordanceViewModel? = null
+        val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+        repository.setBottomAreaAlpha(
+            max(0f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD - 0.1f),
+        )
+
+        val testConfig =
+            TestConfig(
+                isVisible = true,
+                isClickable = false,
+                icon = mock(),
+                canShowWhileLocked = false,
+                intent = Intent("action"),
+            )
+        val configKey =
+            setUpQuickAffordanceModel(
+                position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                testConfig = testConfig,
+            )
+
+        assertQuickAffordanceViewModel(
+            viewModel = latest,
+            testConfig = testConfig,
+            configKey = configKey,
+        )
+        job.cancel()
+    }
+
+    @Test
+    fun `isClickable - false when alpha at zero`() = runBlockingTest {
+        repository.setKeyguardShowing(true)
+        var latest: KeyguardQuickAffordanceViewModel? = null
+        val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+        repository.setBottomAreaAlpha(0f)
+
+        val testConfig =
+            TestConfig(
+                isVisible = true,
+                isClickable = false,
+                icon = mock(),
+                canShowWhileLocked = false,
+                intent = Intent("action"),
+            )
+        val configKey =
+            setUpQuickAffordanceModel(
+                position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                testConfig = testConfig,
+            )
+
+        assertQuickAffordanceViewModel(
+            viewModel = latest,
+            testConfig = testConfig,
+            configKey = configKey,
+        )
+        job.cancel()
+    }
+
     private suspend fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
         repository.setDozeAmount(dozeAmount)
         return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
@@ -384,6 +514,7 @@
     ) {
         checkNotNull(viewModel)
         assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
+        assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable)
         if (testConfig.isVisible) {
             assertThat(viewModel.icon).isEqualTo(testConfig.icon)
             viewModel.onClicked.invoke(
@@ -404,6 +535,7 @@
 
     private data class TestConfig(
         val isVisible: Boolean,
+        val isClickable: Boolean = false,
         val icon: ContainedDrawable? = null,
         val canShowWhileLocked: Boolean = false,
         val intent: Intent? = null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt
new file mode 100644
index 0000000..373af5c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt
@@ -0,0 +1,57 @@
+/*
+ *  Copyright (C) 2022 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.lifecycle
+
+import androidx.arch.core.executor.ArchTaskExecutor
+import androidx.arch.core.executor.TaskExecutor
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+/**
+ * Test rule that makes ArchTaskExecutor main thread assertions pass. There is one such assert
+ * in LifecycleRegistry.
+ */
+class InstantTaskExecutorRule : TestWatcher() {
+    // TODO(b/240620122): This is a copy of
+    //  androidx/arch/core/executor/testing/InstantTaskExecutorRule which should be replaced
+    //  with a dependency on the real library once b/ is cleared.
+    override fun starting(description: Description) {
+        super.starting(description)
+        ArchTaskExecutor.getInstance()
+            .setDelegate(
+                object : TaskExecutor() {
+                    override fun executeOnDiskIO(runnable: Runnable) {
+                        runnable.run()
+                    }
+
+                    override fun postToMainThread(runnable: Runnable) {
+                        runnable.run()
+                    }
+
+                    override fun isMainThread(): Boolean {
+                        return true
+                    }
+                }
+            )
+    }
+
+    override fun finished(description: Description) {
+        super.finished(description)
+        ArchTaskExecutor.getInstance().setDelegate(null)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt
index 80f3e46..91a6de6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt
@@ -20,8 +20,6 @@
 import android.testing.TestableLooper.RunWithLooper
 import android.view.View
 import android.view.ViewTreeObserver
-import androidx.arch.core.executor.ArchTaskExecutor
-import androidx.arch.core.executor.TaskExecutor
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import androidx.test.filters.SmallTest
@@ -35,8 +33,6 @@
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
-import org.junit.rules.TestWatcher
-import org.junit.runner.Description
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import org.mockito.Mock
@@ -282,38 +278,4 @@
             _invocations.add(Invocation(lifecycleOwner))
         }
     }
-
-    /**
-     * Test rule that makes ArchTaskExecutor main thread assertions pass. There is one such assert
-     * in LifecycleRegistry.
-     */
-    class InstantTaskExecutorRule : TestWatcher() {
-        // TODO(b/240620122): This is a copy of
-        //  androidx/arch/core/executor/testing/InstantTaskExecutorRule which should be replaced
-        //  with a dependency on the real library once b/ is cleared.
-        override fun starting(description: Description) {
-            super.starting(description)
-            ArchTaskExecutor.getInstance()
-                .setDelegate(
-                    object : TaskExecutor() {
-                        override fun executeOnDiskIO(runnable: Runnable) {
-                            runnable.run()
-                        }
-
-                        override fun postToMainThread(runnable: Runnable) {
-                            runnable.run()
-                        }
-
-                        override fun isMainThread(): Boolean {
-                            return true
-                        }
-                    }
-                )
-        }
-
-        override fun finished(description: Description) {
-            super.finished(description)
-            ArchTaskExecutor.getInstance().setDelegate(null)
-        }
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index 1cce7cf..d1ed8e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -50,11 +50,10 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
-import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
 
 private const val KEY = "KEY"
 private const val KEY_2 = "KEY_2"
@@ -287,6 +286,30 @@
     }
 
     @Test
+    fun testOnNotificationAdded_hasSubstituteName_isUsed() {
+        val subName = "Substitute Name"
+        val notif = SbnBuilder().run {
+            modifyNotification(context).also {
+                it.extras = Bundle().apply {
+                    putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, subName)
+                }
+                it.setStyle(MediaStyle().apply {
+                    setMediaSession(session.sessionToken)
+                })
+            }
+            build()
+        }
+
+        mediaDataManager.onNotificationAdded(KEY, notif)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+            eq(0), eq(false))
+
+        assertThat(mediaDataCaptor.value!!.app).isEqualTo(subName)
+    }
+
+    @Test
     fun testLoadMediaDataInBg_invalidTokenNoCrash() {
         val bundle = Bundle()
         // wrong data type
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 260bb87..22ecb4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -78,7 +78,7 @@
 
         when(mMediaOutputController.getMediaDevices()).thenReturn(mMediaDevices);
         when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false);
-        when(mMediaOutputController.isTransferring()).thenReturn(false);
+        when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false);
         when(mMediaOutputController.getDeviceIconCompat(mMediaDevice1)).thenReturn(mIconCompat);
         when(mMediaOutputController.getDeviceIconCompat(mMediaDevice2)).thenReturn(mIconCompat);
         when(mMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice1);
@@ -208,7 +208,7 @@
 
     @Test
     public void onBindViewHolder_inTransferring_bindTransferringDevice_verifyView() {
-        when(mMediaOutputController.isTransferring()).thenReturn(true);
+        when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true);
         when(mMediaDevice1.getState()).thenReturn(
                 LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
         mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -224,7 +224,7 @@
 
     @Test
     public void onBindViewHolder_inTransferring_bindNonTransferringDevice_verifyView() {
-        when(mMediaOutputController.isTransferring()).thenReturn(true);
+        when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true);
         when(mMediaDevice2.getState()).thenReturn(
                 LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
         mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index be14cc5..cb4f08e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -93,6 +93,10 @@
     private lateinit var featureFlags: FeatureFlags
     @Mock
     private lateinit var insetsProvider: StatusBarContentInsetsProvider
+    @Mock
+    private lateinit var iconManagerFactory: StatusBarIconController.TintedIconManager.Factory
+    @Mock
+    private lateinit var iconManager: StatusBarIconController.TintedIconManager
 
     private val qsExpansionPathInterpolator = QSExpansionPathInterpolator()
 
@@ -106,6 +110,7 @@
         `when`(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
         `when`(variableDateViewControllerFactory.create(any()))
                 .thenReturn(variableDateViewController)
+        `when`(iconManagerFactory.create(any())).thenReturn(iconManager)
         `when`(view.resources).thenReturn(mContext.resources)
         `when`(view.isAttachedToWindow).thenReturn(true)
         `when`(view.context).thenReturn(context)
@@ -122,7 +127,8 @@
                 featureFlags,
                 variableDateViewControllerFactory,
                 batteryMeterViewController,
-                insetsProvider
+                insetsProvider,
+                iconManagerFactory,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
new file mode 100644
index 0000000..d91baa5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
+import com.android.systemui.statusbar.connectivity.AccessPointController;
+import com.android.systemui.statusbar.connectivity.NetworkController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class InternetTileTest extends SysuiTestCase {
+
+    @Mock
+    private QSTileHost mHost;
+    @Mock
+    private NetworkController mNetworkController;
+    @Mock
+    private AccessPointController mAccessPointController;
+    @Mock
+    private InternetDialogFactory mInternetDialogFactory;
+
+    private TestableLooper mTestableLooper;
+    private InternetTile mTile;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mTestableLooper = TestableLooper.get(this);
+        when(mHost.getContext()).thenReturn(mContext);
+        when(mHost.getUserContext()).thenReturn(mContext);
+
+        mTile = new InternetTile(
+            mHost,
+            mTestableLooper.getLooper(),
+            new Handler(mTestableLooper.getLooper()),
+            new FalsingManagerFake(),
+            mock(MetricsLogger.class),
+            mock(StatusBarStateController.class),
+            mock(ActivityStarter.class),
+            mock(QSLogger.class),
+            mNetworkController,
+            mAccessPointController,
+            mInternetDialogFactory
+        );
+
+        mTile.initialize();
+        mTestableLooper.processAllMessages();
+    }
+
+    @Test
+    public void setConnectivityStatus_defaultNetworkNotExists_updateTile() {
+        mTile.mSignalCallback.setConnectivityStatus(
+            /* noDefaultNetwork= */ true,
+            /* noValidatedNetwork= */ true,
+            /* noNetworksAvailable= */ true);
+        mTestableLooper.processAllMessages();
+        assertThat(String.valueOf(mTile.getState().secondaryLabel))
+            .isEqualTo(mContext.getString(R.string.quick_settings_networks_unavailable));
+        assertThat(mTile.getLastTileState()).isEqualTo(1);
+    }
+
+    @Test
+    public void setConnectivityStatus_defaultNetworkExists_notUpdateTile() {
+        mTile.mSignalCallback.setConnectivityStatus(
+            /* noDefaultNetwork= */ false,
+            /* noValidatedNetwork= */ true,
+            /* noNetworksAvailable= */ true);
+        mTestableLooper.processAllMessages();
+        assertThat(String.valueOf(mTile.getState().secondaryLabel))
+            .isNotEqualTo(mContext.getString(R.string.quick_settings_networks_unavailable));
+        assertThat(String.valueOf(mTile.getState().secondaryLabel))
+            .isNotEqualTo(mContext.getString(R.string.quick_settings_networks_available));
+        assertThat(mTile.getLastTileState()).isEqualTo(-1);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeImageCapture.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeImageCapture.kt
new file mode 100644
index 0000000..447e28c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeImageCapture.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.screenshot
+
+import android.graphics.Bitmap
+import android.graphics.Rect
+
+internal class FakeImageCapture : ImageCapture {
+
+    var requestedDisplayId: Int? = null
+    var requestedDisplayCrop: Rect? = null
+    var requestedTaskId: Int? = null
+
+    var image: Bitmap? = null
+
+    override fun captureDisplay(displayId: Int, crop: Rect?): Bitmap? {
+        requestedDisplayId = displayId
+        requestedDisplayCrop = crop
+        return image
+    }
+
+    override suspend fun captureTask(taskId: Int): Bitmap? {
+        requestedTaskId = taskId
+        return image
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt
new file mode 100644
index 0000000..28d53c7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.screenshot
+
+import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
+
+internal class FakeScreenshotPolicy : ScreenshotPolicy {
+
+    private val userTypes = mutableMapOf<Int, Boolean>()
+    private val contentInfo = mutableMapOf<Int, DisplayContentInfo?>()
+
+    fun setManagedProfile(userId: Int, managedUser: Boolean) {
+        userTypes[userId] = managedUser
+    }
+    override suspend fun isManagedProfile(userId: Int): Boolean {
+        return userTypes[userId] ?: error("No managedProfile value set for userId $userId")
+    }
+
+    fun setDisplayContentInfo(userId: Int, contentInfo: DisplayContentInfo) {
+        this.contentInfo[userId] = contentInfo
+    }
+
+    override suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo {
+        return contentInfo[displayId] ?: error("No DisplayContentInfo set for displayId $displayId")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt
index ce3f20d..00f3808 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt
@@ -27,6 +27,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -37,7 +39,10 @@
 class ImageCaptureImplTest : SysuiTestCase() {
     private val displayManager = mock<DisplayManager>()
     private val atmService = mock<IActivityTaskManager>()
-    private val capture = TestableImageCaptureImpl(displayManager, atmService)
+    private val capture = TestableImageCaptureImpl(
+        displayManager,
+        atmService,
+        Dispatchers.Unconfined)
 
     @Test
     fun captureDisplayWithCrop() {
@@ -59,9 +64,10 @@
 
     class TestableImageCaptureImpl(
         displayManager: DisplayManager,
-        atmService: IActivityTaskManager
+        atmService: IActivityTaskManager,
+        bgDispatcher: CoroutineDispatcher
     ) :
-        ImageCaptureImpl(displayManager, atmService) {
+        ImageCaptureImpl(displayManager, atmService, bgDispatcher) {
 
         var token: IBinder? = null
         var width: Int? = null
@@ -81,4 +87,4 @@
             return ScreenshotHardwareBuffer(null, null, false, false)
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 024d3bd..48fbd35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -22,86 +22,253 @@
 import android.graphics.Insets
 import android.graphics.Rect
 import android.hardware.HardwareBuffer
-import android.net.Uri
+import android.os.Bundle
 import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
 import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
 import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
-import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-
+import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
 import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
+import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap
 import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
-import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
 import com.google.common.truth.Truth.assertThat
-import java.util.function.Consumer
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
 import org.junit.Test
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.isNull
+
+private const val USER_ID = 1
+private const val TASK_ID = 1
 
 class RequestProcessorTest {
-    private val controller = mock<ScreenshotController>()
-    private val bitmapCaptor = argumentCaptor<Bitmap>()
+    private val imageCapture = FakeImageCapture()
+    private val component = ComponentName("android.test", "android.test.Component")
+    private val bounds = Rect(25, 25, 75, 75)
 
+    private val scope = CoroutineScope(Dispatchers.Unconfined)
+    private val dispatcher = Dispatchers.Unconfined
+    private val policy = FakeScreenshotPolicy()
+    private val flags = FakeFeatureFlags()
+
+    /** Tests the Java-compatible function wrapper, ensures callback is invoked. */
     @Test
-    fun testFullScreenshot() {
+    fun testProcessAsync() {
+        flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
+
         val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
-        val onSavedListener = mock<Consumer<Uri>>()
-        val callback = mock<RequestCallback>()
-        val processor = RequestProcessor(controller)
+        val processor = RequestProcessor(imageCapture, policy, flags, scope)
 
-        processor.processRequest(request, onSavedListener, callback)
+        var result: ScreenshotRequest? = null
+        var callbackCount = 0
+        val callback: (ScreenshotRequest) -> Unit = { processedRequest: ScreenshotRequest ->
+            result = processedRequest
+            callbackCount++
+        }
 
-        verify(controller).takeScreenshotFullscreen(/* topComponent */ isNull(),
-            eq(onSavedListener), eq(callback))
+        // runs synchronously, using Unconfined Dispatcher
+        processor.processAsync(request, callback)
+
+        // Callback invoked once returning the same request (no changes)
+        assertThat(callbackCount).isEqualTo(1)
+        assertThat(result).isEqualTo(request)
     }
 
     @Test
-    fun testSelectedRegionScreenshot() {
+    fun testFullScreenshot_workProfilePolicyDisabled() = runBlocking {
+        flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
+
+        val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+        val processedRequest = processor.process(request)
+
+        // No changes
+        assertThat(processedRequest).isEqualTo(request)
+    }
+
+    @Test
+    fun testFullScreenshot() = runBlocking {
+        flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+
+        // Indicate that the primary content belongs to a normal user
+        policy.setManagedProfile(USER_ID, false)
+        policy.setDisplayContentInfo(
+            policy.getDefaultDisplayId(),
+            DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
+
+        val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+        val processedRequest = processor.process(request)
+
+        // Request has topComponent added, but otherwise unchanged.
+        assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN)
+        assertThat(processedRequest.topComponent).isEqualTo(component)
+    }
+
+    @Test
+    fun testFullScreenshot_managedProfile() = runBlocking {
+        flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+
+        // Provide a fake task bitmap when asked
+        val bitmap = makeHardwareBitmap(100, 100)
+        imageCapture.image = bitmap
+
+        // Indicate that the primary content belongs to a manged profile
+        policy.setManagedProfile(USER_ID, true)
+        policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
+            DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
+
+        val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+        val processedRequest = processor.process(request)
+
+        // Expect a task snapshot is taken, overriding the full screen mode
+        assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
+        assertThat(bitmap.equalsHardwareBitmapBundle(processedRequest.bitmapBundle)).isTrue()
+        assertThat(processedRequest.boundsInScreen).isEqualTo(bounds)
+        assertThat(processedRequest.insets).isEqualTo(Insets.NONE)
+        assertThat(processedRequest.taskId).isEqualTo(TASK_ID)
+        assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
+        assertThat(processedRequest.userId).isEqualTo(USER_ID)
+        assertThat(processedRequest.topComponent).isEqualTo(component)
+    }
+
+    @Test
+    fun testSelectedRegionScreenshot_workProfilePolicyDisabled() = runBlocking {
+        flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
+
         val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD)
-        val onSavedListener = mock<Consumer<Uri>>()
-        val callback = mock<RequestCallback>()
-        val processor = RequestProcessor(controller)
+        val processor = RequestProcessor(imageCapture, policy, flags, scope)
 
-        processor.processRequest(request, onSavedListener, callback)
+        val processedRequest = processor.process(request)
 
-        verify(controller).takeScreenshotPartial(/* topComponent */ isNull(),
-            eq(onSavedListener), eq(callback))
+        // No changes
+        assertThat(processedRequest).isEqualTo(request)
+     }
+
+    @Test
+    fun testSelectedRegionScreenshot() = runBlocking {
+        flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+
+        val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD)
+        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+        policy.setManagedProfile(USER_ID, false)
+        policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
+            DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
+
+        val processedRequest = processor.process(request)
+
+        // Request has topComponent added, but otherwise unchanged.
+        assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN)
+        assertThat(processedRequest.topComponent).isEqualTo(component)
     }
 
     @Test
-    fun testProvidedImageScreenshot() {
-        val taskId = 1111
-        val userId = 2222
-        val bounds = Rect(50, 50, 150, 150)
-        val topComponent = ComponentName("test", "test")
-        val processor = RequestProcessor(controller)
+    fun testSelectedRegionScreenshot_managedProfile() = runBlocking {
+        flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
 
-        val buffer = HardwareBuffer.create(100, 100, HardwareBuffer.RGBA_8888, 1,
-            HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
-        val bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
+        // Provide a fake task bitmap when asked
+        val bitmap = makeHardwareBitmap(100, 100)
+        imageCapture.image = bitmap
+
+        val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD)
+        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+        // Indicate that the primary content belongs to a manged profile
+        policy.setManagedProfile(USER_ID, true)
+        policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
+            DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
+
+        val processedRequest = processor.process(request)
+
+        // Expect a task snapshot is taken, overriding the selected region mode
+        assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
+        assertThat(bitmap.equalsHardwareBitmapBundle(processedRequest.bitmapBundle)).isTrue()
+        assertThat(processedRequest.boundsInScreen).isEqualTo(bounds)
+        assertThat(processedRequest.insets).isEqualTo(Insets.NONE)
+        assertThat(processedRequest.taskId).isEqualTo(TASK_ID)
+        assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
+        assertThat(processedRequest.userId).isEqualTo(USER_ID)
+        assertThat(processedRequest.topComponent).isEqualTo(component)
+    }
+
+    @Test
+    fun testProvidedImageScreenshot_workProfilePolicyDisabled() = runBlocking {
+        flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
+
+        val bounds = Rect(50, 50, 150, 150)
+        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+        val bitmap = makeHardwareBitmap(100, 100)
         val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
 
         val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
-            bitmapBundle, bounds, Insets.NONE, taskId, userId, topComponent)
+            bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
 
-        val onSavedListener = mock<Consumer<Uri>>()
-        val callback = mock<RequestCallback>()
+        val processedRequest = processor.process(request)
 
-        processor.processRequest(request, onSavedListener, callback)
-
-        verify(controller).handleImageAsScreenshot(
-            bitmapCaptor.capture(), eq(bounds), eq(Insets.NONE), eq(taskId), eq(userId),
-            eq(topComponent), eq(onSavedListener), eq(callback)
-        )
-
-        assertThat(bitmapCaptor.value.equalsHardwareBitmap(bitmap)).isTrue()
+        // No changes
+        assertThat(processedRequest).isEqualTo(request)
     }
 
-    private fun Bitmap.equalsHardwareBitmap(bitmap: Bitmap): Boolean {
-        return bitmap.hardwareBuffer == this.hardwareBuffer &&
-                bitmap.colorSpace == this.colorSpace
+    @Test
+    fun testProvidedImageScreenshot() = runBlocking {
+        flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+
+        val bounds = Rect(50, 50, 150, 150)
+        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+        policy.setManagedProfile(USER_ID, false)
+
+        val bitmap = makeHardwareBitmap(100, 100)
+        val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
+
+        val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
+            bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
+
+        val processedRequest = processor.process(request)
+
+        // No changes
+        assertThat(processedRequest).isEqualTo(request)
+    }
+
+    @Test
+    fun testProvidedImageScreenshot_managedProfile() = runBlocking {
+        flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+
+        val bounds = Rect(50, 50, 150, 150)
+        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+        // Indicate that the screenshot belongs to a manged profile
+        policy.setManagedProfile(USER_ID, true)
+
+        val bitmap = makeHardwareBitmap(100, 100)
+        val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
+
+        val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
+            bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
+
+        val processedRequest = processor.process(request)
+
+        // Work profile, but already a task snapshot, so no changes
+        assertThat(processedRequest).isEqualTo(request)
+    }
+
+    private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
+        val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1,
+            HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
+        return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
+    }
+
+    private fun Bitmap.equalsHardwareBitmapBundle(bundle: Bundle): Boolean {
+        val provided = bundleToHardwareBitmap(bundle)
+        return provided.hardwareBuffer == this.hardwareBuffer &&
+                provided.colorSpace == this.colorSpace
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
new file mode 100644
index 0000000..83e56da
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.screenshot
+
+import android.app.Application
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResources.Strings.SystemUi.SCREENSHOT_BLOCKED_BY_ADMIN
+import android.app.admin.DevicePolicyResourcesManager
+import android.content.ComponentName
+import android.graphics.Bitmap
+import android.graphics.Bitmap.Config.HARDWARE
+import android.graphics.ColorSpace
+import android.graphics.Insets
+import android.graphics.Rect
+import android.hardware.HardwareBuffer
+import android.os.UserHandle
+import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW
+import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
+import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.util.ScreenshotHelper
+import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.SCREENSHOT_REQUEST_PROCESSOR
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_CHORD
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW
+import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argThat
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
+
+private const val USER_ID = 1
+private const val TASK_ID = 1
+
+@RunWith(AndroidTestingRunner::class)
+class TakeScreenshotServiceTest : SysuiTestCase() {
+
+    private val application = mock<Application>()
+    private val controller = mock<ScreenshotController>()
+    private val userManager = mock<UserManager>()
+    private val requestProcessor = mock<RequestProcessor>()
+    private val devicePolicyManager = mock<DevicePolicyManager>()
+    private val devicePolicyResourcesManager = mock<DevicePolicyResourcesManager>()
+    private val notificationsController = mock<ScreenshotNotificationsController>()
+    private val callback = mock<RequestCallback>()
+
+    private val eventLogger = UiEventLoggerFake()
+    private val flags = FakeFeatureFlags()
+    private val topComponent = ComponentName(mContext, TakeScreenshotServiceTest::class.java)
+
+    private val service = TakeScreenshotService(
+        controller, userManager, devicePolicyManager, eventLogger,
+        notificationsController, mContext, Runnable::run, flags, requestProcessor)
+
+    @Before
+    fun setUp() {
+        whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager)
+        whenever(devicePolicyManager.getScreenCaptureDisabled(
+            /* admin component (null: any admin) */ isNull(), eq(UserHandle.USER_ALL)))
+            .thenReturn(false)
+        whenever(userManager.isUserUnlocked).thenReturn(true)
+
+        flags.set(SCREENSHOT_REQUEST_PROCESSOR, false)
+
+        service.attach(
+            mContext,
+            /* thread = */ null,
+            /* className = */ null,
+            /* token = */ null,
+            application,
+            /* activityManager = */ null)
+    }
+
+    @Test
+    fun testServiceLifecycle() {
+        service.onCreate()
+        service.onBind(null /* unused: Intent */)
+
+        service.onUnbind(null /* unused: Intent */)
+        verify(controller).removeWindow()
+
+        service.onDestroy()
+        verify(controller).onDestroy()
+    }
+
+    @Test
+    fun takeScreenshotFullscreen() {
+        val request = ScreenshotRequest(
+            TAKE_SCREENSHOT_FULLSCREEN,
+            SCREENSHOT_KEY_CHORD,
+            topComponent)
+
+        service.handleRequest(request, { /* onSaved */ }, callback)
+
+        verify(controller).takeScreenshotFullscreen(
+            eq(topComponent),
+            /* onSavedListener = */ any(),
+            /* requestCallback = */ any())
+
+        assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
+        val logEvent = eventLogger.get(0)
+
+        assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
+            logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id)
+        assertEquals("Expected supplied package name",
+            topComponent.packageName, eventLogger.get(0).packageName)
+    }
+
+    @Test
+    fun takeScreenshotPartial() {
+        val request = ScreenshotRequest(
+            TAKE_SCREENSHOT_SELECTED_REGION,
+            SCREENSHOT_KEY_CHORD,
+            /* topComponent = */ null)
+
+        service.handleRequest(request, { /* onSaved */ }, callback)
+
+        verify(controller).takeScreenshotPartial(
+            /* topComponent = */ isNull(),
+            /* onSavedListener = */ any(),
+            /* requestCallback = */ any())
+
+        assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
+        val logEvent = eventLogger.get(0)
+
+        assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
+            logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id)
+        assertEquals("Expected empty package name in UiEvent", "", eventLogger.get(0).packageName)
+    }
+
+    @Test
+    fun takeScreenshotProvidedImage() {
+        val bounds = Rect(50, 50, 150, 150)
+        val bitmap = makeHardwareBitmap(100, 100)
+        val bitmapBundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
+
+        val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OVERVIEW,
+            bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, topComponent)
+
+        service.handleRequest(request, { /* onSaved */ }, callback)
+
+        verify(controller).handleImageAsScreenshot(
+            argThat { b -> b.equalsHardwareBitmap(bitmap) },
+            eq(bounds),
+            eq(Insets.NONE), eq(TASK_ID), eq(USER_ID), eq(topComponent),
+            /* onSavedListener = */ any(), /* requestCallback = */ any())
+
+        assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
+        val logEvent = eventLogger.get(0)
+
+        assertEquals("Expected SCREENSHOT_REQUESTED_* UiEvent",
+            logEvent.eventId, SCREENSHOT_REQUESTED_OVERVIEW.id)
+        assertEquals("Expected supplied package name",
+            topComponent.packageName, eventLogger.get(0).packageName)
+    }
+
+    @Test
+    fun takeScreenshotFullscreen_userLocked() {
+        whenever(userManager.isUserUnlocked).thenReturn(false)
+
+        val request = ScreenshotRequest(
+            TAKE_SCREENSHOT_FULLSCREEN,
+            SCREENSHOT_KEY_CHORD,
+            topComponent)
+
+        service.handleRequest(request, { /* onSaved */ }, callback)
+
+        verify(notificationsController).notifyScreenshotError(anyInt())
+        verify(callback).reportError()
+        verifyZeroInteractions(controller)
+    }
+
+    @Test
+    fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() {
+        whenever(devicePolicyManager.getScreenCaptureDisabled(
+            isNull(), eq(UserHandle.USER_ALL))
+        ).thenReturn(true)
+
+        whenever(devicePolicyResourcesManager.getString(
+            eq(SCREENSHOT_BLOCKED_BY_ADMIN),
+            /* Supplier<String> */ any(),
+        )).thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
+
+        val request = ScreenshotRequest(
+            TAKE_SCREENSHOT_FULLSCREEN,
+            SCREENSHOT_KEY_CHORD,
+            topComponent)
+
+        service.handleRequest(request, { /* onSaved */ }, callback)
+
+        // error shown: Toast.makeText(...).show(), untestable
+        verify(callback).reportError()
+        verifyZeroInteractions(controller)
+    }
+}
+
+private fun Bitmap.equalsHardwareBitmap(other: Bitmap): Boolean {
+    return config == HARDWARE &&
+            other.config == HARDWARE &&
+            hardwareBuffer == other.hardwareBuffer &&
+            colorSpace == other.colorSpace
+}
+
+/** A hardware Bitmap is mandated by use of ScreenshotHelper.HardwareBitmapBundler */
+private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
+    val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1,
+        HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
+    return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index ed1a13b..20c6d9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -91,6 +91,10 @@
     @Mock
     private lateinit var statusBarIconController: StatusBarIconController
     @Mock
+    private lateinit var iconManagerFactory: StatusBarIconController.TintedIconManager.Factory
+    @Mock
+    private lateinit var iconManager: StatusBarIconController.TintedIconManager
+    @Mock
     private lateinit var qsCarrierGroupController: QSCarrierGroupController
     @Mock
     private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
@@ -169,6 +173,8 @@
         }
         whenever(view.visibility).thenAnswer { _ -> viewVisibility }
 
+        whenever(iconManagerFactory.create(any())).thenReturn(iconManager)
+
         whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(true)
         whenever(featureFlags.isEnabled(Flags.NEW_HEADER)).thenReturn(true)
 
@@ -178,6 +184,7 @@
         controller = LargeScreenShadeHeaderController(
             view,
             statusBarIconController,
+            iconManagerFactory,
             privacyIconsController,
             insetsProvider,
             configurationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index 02b26db..eeb61bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -43,6 +43,8 @@
     @Mock private lateinit var view: View
     @Mock private lateinit var statusIcons: StatusIconContainer
     @Mock private lateinit var statusBarIconController: StatusBarIconController
+    @Mock private lateinit var iconManagerFactory: StatusBarIconController.TintedIconManager.Factory
+    @Mock private lateinit var iconManager: StatusBarIconController.TintedIconManager
     @Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController
     @Mock private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
     @Mock private lateinit var featureFlags: FeatureFlags
@@ -91,10 +93,12 @@
         whenever(view.visibility).thenAnswer { _ -> viewVisibility }
         whenever(variableDateViewControllerFactory.create(any()))
             .thenReturn(variableDateViewController)
+        whenever(iconManagerFactory.create(any())).thenReturn(iconManager)
         whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(false)
         mLargeScreenShadeHeaderController = LargeScreenShadeHeaderController(
                 view,
                 statusBarIconController,
+                iconManagerFactory,
                 privacyIconsController,
                 insetsProvider,
                 configurationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 95211c0..7d28871 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -47,7 +47,6 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.ContentObserver;
-import android.graphics.PointF;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.PowerManager;
@@ -764,10 +763,8 @@
 
     @Test
     public void testSetDozing_notifiesNsslAndStateController() {
-        mNotificationPanelViewController.setDozing(true /* dozing */, false /* animate */,
-                null /* touch */);
-        verify(mNotificationStackScrollLayoutController)
-                .setDozing(eq(true), eq(false), eq(null));
+        mNotificationPanelViewController.setDozing(true /* dozing */, false /* animate */);
+        verify(mNotificationStackScrollLayoutController).setDozing(eq(true), eq(false));
         assertThat(mStatusBarStateController.getDozeAmount()).isEqualTo(1f);
     }
 
@@ -908,7 +905,7 @@
 
         setDozing(/* dozing= */ true, /* dozingAlwaysOn= */ true);
 
-        assertThat(isKeyguardStatusViewCentered()).isTrue();
+        assertKeyguardStatusViewCentered();
     }
 
     @Test
@@ -919,7 +916,7 @@
 
         setDozing(/* dozing= */ true, /* dozingAlwaysOn= */ false);
 
-        assertThat(isKeyguardStatusViewCentered()).isFalse();
+        assertKeyguardStatusViewNotCentered();
     }
 
     @Test
@@ -930,19 +927,19 @@
 
         setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ true);
 
-        assertThat(isKeyguardStatusViewCentered()).isFalse();
+        assertKeyguardStatusViewNotCentered();
     }
 
     @Test
-    public void keyguardStatusView_splitShade_pulsing_isCentered() {
+    public void keyguardStatusView_splitShade_pulsing_isNotCentered() {
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
         when(mNotificationListContainer.hasPulsingNotifications()).thenReturn(true);
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
 
-        setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ true);
+        setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
 
-        assertThat(isKeyguardStatusViewCentered()).isFalse();
+        assertKeyguardStatusViewNotCentered();
     }
 
     @Test
@@ -952,9 +949,9 @@
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
 
-        setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ true);
+        setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
 
-        assertThat(isKeyguardStatusViewCentered()).isFalse();
+        assertKeyguardStatusViewNotCentered();
     }
 
     @Test
@@ -967,7 +964,7 @@
         mStatusBarStateController.setState(KEYGUARD);
         setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
 
-        assertThat(isKeyguardStatusViewCentered()).isFalse();
+        assertKeyguardStatusViewCentered();
     }
 
     @Test
@@ -1212,16 +1209,31 @@
     }
 
     @Test
-    public void testSwitchesToBigClockInSplitShadeOnAod() {
+    public void clockSize_mediaShowing_inSplitShade_onAod_isLarge() {
+        when(mDozeParameters.getAlwaysOn()).thenReturn(true);
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
         when(mMediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true);
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
         clearInvocations(mKeyguardStatusViewController);
 
-        mNotificationPanelViewController.setDozing(true, false, null);
+        mNotificationPanelViewController.setDozing(/* dozing= */ true, /* animate= */ false);
 
-        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
+        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate= */ true);
+    }
+
+    @Test
+    public void clockSize_mediaShowing_inSplitShade_screenOff_notAod_isSmall() {
+        when(mDozeParameters.getAlwaysOn()).thenReturn(false);
+        mStatusBarStateController.setState(KEYGUARD);
+        enableSplitShade(/* enabled= */ true);
+        when(mMediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true);
+        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+        clearInvocations(mKeyguardStatusViewController);
+
+        mNotificationPanelViewController.setDozing(/* dozing= */ true, /* animate= */ false);
+
+        verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate= */ true);
     }
 
     @Test
@@ -1233,7 +1245,7 @@
         when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
 
-        mNotificationPanelViewController.setDozing(true, false, null);
+        mNotificationPanelViewController.setDozing(true, false);
 
         verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ false);
     }
@@ -1547,14 +1559,19 @@
         when(mDozeParameters.getAlwaysOn()).thenReturn(dozingAlwaysOn);
         mNotificationPanelViewController.setDozing(
                 /* dozing= */ dozing,
-                /* animate= */ false,
-                /* wakeUpTouchLocation= */ new PointF()
+                /* animate= */ false
         );
     }
 
-    private boolean isKeyguardStatusViewCentered() {
+    private void assertKeyguardStatusViewCentered() {
         mNotificationPanelViewController.updateResources();
-        return getConstraintSetLayout(R.id.keyguard_status_view).endToEnd
-                == ConstraintSet.PARENT_ID;
+        assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd).isAnyOf(
+                ConstraintSet.PARENT_ID, ConstraintSet.UNSET);
+    }
+
+    private void assertKeyguardStatusViewNotCentered() {
+        mNotificationPanelViewController.updateResources();
+        assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd).isEqualTo(
+                R.id.qs_edge_guideline);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt
new file mode 100644
index 0000000..9393a4f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shared.rotation
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Display
+import android.view.WindowInsetsController
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper
+class RotationButtonControllerTest : SysuiTestCase() {
+
+  private lateinit var mController: RotationButtonController
+
+  @Before
+  fun setUp() {
+    mController = RotationButtonController(
+      mContext,
+      /* lightIconColor = */ 0,
+      /* darkIconColor = */ 0,
+      /* iconCcwStart0ResId = */ 0,
+      /* iconCcwStart90ResId = */ 0,
+      /* iconCwStart0ResId = */ 0,
+      /* iconCwStart90ResId = */ 0
+    ) { 0 }
+  }
+
+  @Test
+  fun ifGestural_showRotationSuggestion() {
+    mController.onNavigationBarWindowVisibilityChange( /* showing = */ false)
+    mController.onBehaviorChanged(Display.DEFAULT_DISPLAY,
+                                  WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE)
+    mController.onNavigationModeChanged(WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON)
+    mController.onTaskbarStateChange( /* visible = */ false, /* stashed = */ false)
+    assertThat(mController.canShowRotationButton()).isFalse()
+
+    mController.onNavigationModeChanged(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL)
+
+    assertThat(mController.canShowRotationButton()).isTrue()
+  }
+
+  @Test
+  fun ifTaskbarVisible_showRotationSuggestion() {
+    mController.onNavigationBarWindowVisibilityChange( /* showing = */ false)
+    mController.onBehaviorChanged(Display.DEFAULT_DISPLAY,
+                                    WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE)
+    mController.onNavigationModeChanged(WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON)
+    mController.onTaskbarStateChange( /* visible = */ false, /* stashed = */ false)
+    assertThat(mController.canShowRotationButton()).isFalse()
+
+    mController.onTaskbarStateChange( /* visible = */ true, /* stashed = */ false)
+
+    assertThat(mController.canShowRotationButton()).isTrue()
+  }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index be631af..1d8e5de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -38,8 +38,8 @@
 import org.mockito.Mock
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -89,8 +89,8 @@
         val listener = mock(StatusBarStateController.StateListener::class.java)
         controller.addCallback(listener)
 
-        controller.setDozeAmount(0.5f, false /* animated */)
-        controller.setDozeAmount(0.5f, false /* animated */)
+        controller.setAndInstrumentDozeAmount(null, 0.5f, false /* animated */)
+        controller.setAndInstrumentDozeAmount(null, 0.5f, false /* animated */)
         verify(listener).onDozeAmountChanged(eq(0.5f), anyFloat())
     }
 
@@ -135,7 +135,7 @@
     @Test
     fun testSetDozeAmount_immediatelyChangesDozeAmount_lockscreenTransitionFromAod() {
         // Put controller in AOD state
-        controller.setDozeAmount(1f, false)
+        controller.setAndInstrumentDozeAmount(null, 1f, false)
 
         // When waking from doze, CentralSurfaces#updateDozingState will update the dozing state
         // before the doze amount changes
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
index 214ba16..64d0256 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
@@ -98,7 +98,7 @@
 
     @Test
     fun testPrepareDialogForApp_onlyDefaultChannel() {
-        group.channels = listOf(channelDefault)
+        group.addChannel(channelDefault)
 
         controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID,
                 setOf(channelDefault), appIcon, clickListener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index f8b39e8..137842e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -28,7 +28,6 @@
 
 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 org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -58,8 +57,8 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
 import com.android.systemui.statusbar.notification.FeedbackIcon;
-import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
 
 import org.junit.Assert;
@@ -260,17 +259,6 @@
     }
 
     @Test
-    public void setNeedsRedactionFreesViewWhenFalse() throws Exception {
-        ExpandableNotificationRow row = mNotificationTestHelper.createRow(FLAG_CONTENT_VIEW_ALL);
-        row.setNeedsRedaction(true);
-        row.getPublicLayout().setVisibility(View.GONE);
-
-        row.setNeedsRedaction(false);
-        TestableLooper.get(this).processAllMessages();
-        assertNull(row.getPublicLayout().getContractedChild());
-    }
-
-    @Test
     public void testAboveShelfChangedListenerCalled() throws Exception {
         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
         AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class);
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 c25737d..cc4cbbf 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
@@ -48,6 +48,7 @@
 import android.widget.RemoteViews;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.TestableDependency;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
@@ -75,6 +76,7 @@
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
@@ -145,7 +147,9 @@
                 mock(GroupMembershipManager.class),
                 mock(VisualStabilityProvider.class),
                 mock(ConfigurationControllerImpl.class),
-                new Handler(mTestLooper.getLooper())
+                new Handler(mTestLooper.getLooper()),
+                mock(AccessibilityManagerWrapper.class),
+                mock(UiEventLogger.class)
         );
         mIconManager = new IconManager(
                 mock(CommonNotifCollection.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index ac3d0c2..e252401 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -29,6 +29,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
@@ -65,6 +66,8 @@
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private KeyguardBypassController mBypassController;
     @Mock private ConfigurationControllerImpl mConfigurationController;
+    @Mock private AccessibilityManagerWrapper mAccessibilityManagerWrapper;
+    @Mock private UiEventLogger mUiEventLogger;
     private boolean mLivesPastNormalTime;
 
     private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
@@ -76,7 +79,9 @@
                 StatusBarStateController statusBarStateController,
                 KeyguardBypassController keyguardBypassController,
                 ConfigurationController configurationController,
-                Handler handler
+                Handler handler,
+                AccessibilityManagerWrapper accessibilityManagerWrapper,
+                UiEventLogger uiEventLogger
         ) {
             super(
                     context,
@@ -86,7 +91,9 @@
                     groupManager,
                     visualStabilityProvider,
                     configurationController,
-                    handler
+                    handler,
+                    accessibilityManagerWrapper,
+                    uiEventLogger
             );
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
             mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
@@ -116,7 +123,9 @@
                 mStatusBarStateController,
                 mBypassController,
                 mConfigurationController,
-                mTestHandler
+                mTestHandler,
+                mAccessibilityManagerWrapper,
+                mUiEventLogger
         );
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 11e502f..6cf1a12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -47,7 +47,6 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.battery.BatteryMeterViewController;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -89,7 +88,9 @@
     @Mock
     private StatusBarIconController mStatusBarIconController;
     @Mock
-    private FeatureFlags mFeatureFlags;
+    private StatusBarIconController.TintedIconManager.Factory mIconManagerFactory;
+    @Mock
+    private StatusBarIconController.TintedIconManager mIconManager;
     @Mock
     private BatteryMeterViewController mBatteryMeterViewController;
     @Mock
@@ -129,6 +130,8 @@
 
         MockitoAnnotations.initMocks(this);
 
+        when(mIconManagerFactory.create(any())).thenReturn(mIconManager);
+
         allowTestableLooperAsMainThread();
         TestableLooper.get(this).runWithLooper(() -> {
             mKeyguardStatusBarView =
@@ -148,7 +151,7 @@
                 mBatteryController,
                 mUserInfoController,
                 mStatusBarIconController,
-                new StatusBarIconController.TintedIconManager.Factory(mFeatureFlags),
+                mIconManagerFactory,
                 mBatteryMeterViewController,
                 mNotificationPanelViewStateProvider,
                 mKeyguardStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 0f1c40b..a6b7e51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -40,12 +40,16 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import javax.inject.Provider;
+
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 @SmallTest
@@ -67,7 +71,11 @@
     @Test
     public void testSetCalledOnAdd_DarkIconManager() {
         LinearLayout layout = new LinearLayout(mContext);
-        TestDarkIconManager manager = new TestDarkIconManager(layout, mock(FeatureFlags.class));
+        TestDarkIconManager manager = new TestDarkIconManager(
+                layout,
+                mock(FeatureFlags.class),
+                mock(StatusBarPipelineFlags.class),
+                () -> mock(WifiViewModel.class));
         testCallOnAdd_forManager(manager);
     }
 
@@ -104,8 +112,12 @@
     private static class TestDarkIconManager extends DarkIconManager
             implements TestableIconManager {
 
-        TestDarkIconManager(LinearLayout group, FeatureFlags featureFlags) {
-            super(group, featureFlags);
+        TestDarkIconManager(
+                LinearLayout group,
+                FeatureFlags featureFlags,
+                StatusBarPipelineFlags statusBarPipelineFlags,
+                Provider<WifiViewModel> wifiViewModelProvider) {
+            super(group, featureFlags, statusBarPipelineFlags, wifiViewModelProvider);
         }
 
         @Override
@@ -123,7 +135,7 @@
         }
 
         @Override
-        protected StatusBarWifiView addSignalIcon(int index, String slot, WifiIconState state) {
+        protected StatusBarWifiView addWifiIcon(int index, String slot, WifiIconState state) {
             StatusBarWifiView mock = mock(StatusBarWifiView.class);
             mGroup.addView(mock, index);
             return mock;
@@ -140,7 +152,10 @@
 
     private static class TestIconManager extends IconManager implements TestableIconManager {
         TestIconManager(ViewGroup group) {
-            super(group, mock(FeatureFlags.class));
+            super(group,
+                    mock(FeatureFlags.class),
+                    mock(StatusBarPipelineFlags.class),
+                    () -> mock(WifiViewModel.class));
         }
 
         @Override
@@ -158,7 +173,7 @@
         }
 
         @Override
-        protected StatusBarWifiView addSignalIcon(int index, String slot, WifiIconState state) {
+        protected StatusBarWifiView addWifiIcon(int index, String slot, WifiIconState state) {
             StatusBarWifiView mock = mock(StatusBarWifiView.class);
             mGroup.addView(mock, index);
             return mock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index de43a1f..2b80508 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -37,7 +37,6 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.content.Intent;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -135,8 +134,6 @@
     @Mock
     private PendingIntent mContentIntent;
     @Mock
-    private Intent mContentIntentInner;
-    @Mock
     private OnUserInteractionCallback mOnUserInteractionCallback;
     @Mock
     private Runnable mFutureDismissalRunnable;
@@ -159,7 +156,6 @@
         MockitoAnnotations.initMocks(this);
         when(mContentIntent.isActivity()).thenReturn(true);
         when(mContentIntent.getCreatorUserHandle()).thenReturn(UserHandle.of(1));
-        when(mContentIntent.getIntent()).thenReturn(mContentIntentInner);
 
         NotificationTestHelper notificationTestHelper = new NotificationTestHelper(
                 mContext,
@@ -374,7 +370,6 @@
                 eq(entry.getKey()), any(NotificationVisibility.class));
 
         // The content intent should NOT be sent on click.
-        verify(mContentIntent).getIntent();
         verify(mContentIntent).isActivity();
         verifyNoMoreInteractions(mContentIntent);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 4c8599d..ceaceb4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -111,6 +111,10 @@
     @Mock
     private NotificationPanelViewController mNotificationPanelViewController;
     @Mock
+    private StatusBarIconController.DarkIconManager.Factory mIconManagerFactory;
+    @Mock
+    private StatusBarIconController.DarkIconManager mIconManager;
+    @Mock
     private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
     @Mock
     private DumpManager mDumpManager;
@@ -424,6 +428,7 @@
         mOperatorNameViewControllerFactory = mock(OperatorNameViewController.Factory.class);
         when(mOperatorNameViewControllerFactory.create(any()))
                 .thenReturn(mOperatorNameViewController);
+        when(mIconManagerFactory.create(any())).thenReturn(mIconManager);
         mSecureSettings = mock(SecureSettings.class);
 
         setUpNotificationIconAreaController();
@@ -436,6 +441,7 @@
                 new PanelExpansionStateManager(),
                 mock(FeatureFlags.class),
                 mStatusBarIconController,
+                mIconManagerFactory,
                 mStatusBarHideIconsForBouncerManager,
                 mKeyguardStateController,
                 mNotificationPanelViewController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt
deleted file mode 100644
index 7b492cb..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline
-
-import android.net.NetworkCapabilities
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.InternalCoroutinesApi
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.yield
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.`when` as whenever
-
-@OptIn(InternalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class ConnectivityInfoProcessorTest : SysuiTestCase() {
-
-    private val statusBarPipelineFlags = mock<StatusBarPipelineFlags>()
-
-    @Before
-    fun setUp() {
-        whenever(statusBarPipelineFlags.isNewPipelineEnabled()).thenReturn(true)
-    }
-
-    @Test
-    fun collectorInfoUpdated_processedInfoAlsoUpdated() = runBlocking {
-        // GIVEN a processor hooked up to a collector
-        val scope = CoroutineScope(Dispatchers.Unconfined)
-        val collector = FakeConnectivityInfoCollector()
-        val processor = ConnectivityInfoProcessor(
-                collector,
-                context,
-                scope,
-                statusBarPipelineFlags,
-                mock(),
-        )
-
-        var mostRecentValue: ProcessedConnectivityInfo? = null
-        val job = launch(start = CoroutineStart.UNDISPATCHED) {
-            processor.processedInfoFlow.collect {
-                mostRecentValue = it
-            }
-        }
-
-        // WHEN the collector emits a value
-        val networkCapabilityInfo = mapOf(
-                10 to NetworkCapabilityInfo(mock(), NetworkCapabilities.Builder().build())
-        )
-        collector.emitValue(RawConnectivityInfo(networkCapabilityInfo))
-        // Because our job uses [CoroutineStart.UNDISPATCHED], it executes in the same thread as
-        // this test. So, our test needs to yield to let the job run.
-        // Note: Once we upgrade our Kotlin coroutines testing library, we won't need this.
-        yield()
-
-        // THEN the processor receives it
-        assertThat(mostRecentValue?.networkCapabilityInfo).isEqualTo(networkCapabilityInfo)
-
-        job.cancel()
-        scope.cancel()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/FakeConnectivityInfoCollector.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/FakeConnectivityInfoCollector.kt
deleted file mode 100644
index 710e5f6..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/FakeConnectivityInfoCollector.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline
-
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-/**
- * A test-friendly implementation of [ConnectivityInfoCollector] that just emits whatever value it
- * receives in [emitValue].
- */
-class FakeConnectivityInfoCollector : ConnectivityInfoCollector {
-    private val _rawConnectivityInfoFlow = MutableStateFlow(RawConnectivityInfo())
-    override val rawConnectivityInfoFlow = _rawConnectivityInfoFlow.asStateFlow()
-
-    suspend fun emitValue(value: RawConnectivityInfo) {
-        _rawConnectivityInfoFlow.emit(value)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index df389bc..6b8d4aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -17,21 +17,22 @@
 package com.android.systemui.statusbar.pipeline.wifi.data.repository
 
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
 /** Fake implementation of [WifiRepository] exposing set methods for all the flows. */
 class FakeWifiRepository : WifiRepository {
-    private val _wifiModel: MutableStateFlow<WifiModel?> = MutableStateFlow(null)
-    override val wifiModel: Flow<WifiModel?> = _wifiModel
+    private val _wifiNetwork: MutableStateFlow<WifiNetworkModel> =
+        MutableStateFlow(WifiNetworkModel.Inactive)
+    override val wifiNetwork: Flow<WifiNetworkModel> = _wifiNetwork
 
     private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT)
     override val wifiActivity: Flow<WifiActivityModel> = _wifiActivity
 
-    fun setWifiModel(wifiModel: WifiModel?) {
-        _wifiModel.value = wifiModel
+    fun setWifiNetwork(wifiNetworkModel: WifiNetworkModel) {
+        _wifiNetwork.value = wifiNetworkModel
     }
 
     fun setWifiActivity(activity: WifiActivityModel) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepoTest.kt
deleted file mode 100644
index 6edf76c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepoTest.kt
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.wifi.data.repository
-
-import android.net.ConnectivityManager
-import android.net.ConnectivityManager.NetworkCallback
-import android.net.Network
-import android.net.NetworkCapabilities
-import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED
-import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
-import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
-import android.net.NetworkCapabilities.TRANSPORT_WIFI
-import android.net.NetworkRequest
-import android.test.suitebuilder.annotation.SmallTest
-import android.testing.AndroidTestingRunner
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.withArgCaptor
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-// TODO(b/240619365): Update this test to use `runTest` when we update the testing library
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class NetworkCapabilitiesRepoTest : SysuiTestCase() {
-    @Mock private lateinit var connectivityManager: ConnectivityManager
-    @Mock private lateinit var logger: ConnectivityPipelineLogger
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-    }
-
-    @Test
-    fun testOnCapabilitiesChanged_oneNewNetwork_networkStored() = runBlocking {
-        // GIVEN a repo hooked up to [ConnectivityManager]
-        val scope = CoroutineScope(Dispatchers.Unconfined)
-        val repo = NetworkCapabilitiesRepo(
-            connectivityManager = connectivityManager,
-            scope = scope,
-            logger = logger,
-        )
-
-        val job = launch(start = CoroutineStart.UNDISPATCHED) {
-            repo.dataStream.collect {
-            }
-        }
-
-        val callback: NetworkCallback = withArgCaptor {
-            verify(connectivityManager)
-                .registerNetworkCallback(any(NetworkRequest::class.java), capture())
-        }
-
-        // WHEN a new network is added
-        callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
-
-        val currentMap = repo.dataStream.value
-
-        // THEN it is emitted from the flow
-        assertThat(currentMap[NET_1_ID]?.network).isEqualTo(NET_1)
-        assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_1_CAPS)
-
-        job.cancel()
-        scope.cancel()
-    }
-
-    @Test
-    fun testOnCapabilitiesChanged_twoNewNetworks_bothStored() = runBlocking {
-        // GIVEN a repo hooked up to [ConnectivityManager]
-        val scope = CoroutineScope(Dispatchers.Unconfined)
-        val repo = NetworkCapabilitiesRepo(
-            connectivityManager = connectivityManager,
-            scope = scope,
-            logger = logger,
-        )
-
-        val job = launch(start = CoroutineStart.UNDISPATCHED) {
-            repo.dataStream.collect {
-            }
-        }
-
-        val callback: NetworkCallback = withArgCaptor {
-            verify(connectivityManager)
-                .registerNetworkCallback(any(NetworkRequest::class.java), capture())
-        }
-
-        // WHEN two new networks are added
-        callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
-        callback.onCapabilitiesChanged(NET_2, NET_2_CAPS)
-
-        val currentMap = repo.dataStream.value
-
-        // THEN the current state of the flow reflects 2 networks
-        assertThat(currentMap[NET_1_ID]?.network).isEqualTo(NET_1)
-        assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_1_CAPS)
-        assertThat(currentMap[NET_2_ID]?.network).isEqualTo(NET_2)
-        assertThat(currentMap[NET_2_ID]?.capabilities).isEqualTo(NET_2_CAPS)
-
-        job.cancel()
-        scope.cancel()
-    }
-
-    @Test
-    fun testOnCapabilitesChanged_newCapabilitiesForExistingNetwork_areCaptured() = runBlocking {
-        // GIVEN a repo hooked up to [ConnectivityManager]
-        val scope = CoroutineScope(Dispatchers.Unconfined)
-        val repo = NetworkCapabilitiesRepo(
-            connectivityManager = connectivityManager,
-            scope = scope,
-            logger = logger,
-        )
-
-        val job = launch(start = CoroutineStart.UNDISPATCHED) {
-            repo.dataStream.collect {
-            }
-        }
-
-        val callback: NetworkCallback = withArgCaptor {
-            verify(connectivityManager)
-                .registerNetworkCallback(any(NetworkRequest::class.java), capture())
-        }
-
-        // WHEN a network is added, and then its capabilities are changed
-        callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
-        callback.onCapabilitiesChanged(NET_1, NET_2_CAPS)
-
-        val currentMap = repo.dataStream.value
-
-        // THEN the current state of the flow reflects the new capabilities
-        assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_2_CAPS)
-
-        job.cancel()
-        scope.cancel()
-    }
-
-    @Test
-    fun testOnLost_networkIsRemoved() = runBlocking {
-        // GIVEN a repo hooked up to [ConnectivityManager]
-        val scope = CoroutineScope(Dispatchers.Unconfined)
-        val repo = NetworkCapabilitiesRepo(
-            connectivityManager = connectivityManager,
-            scope = scope,
-            logger = logger,
-        )
-
-        val job = launch(start = CoroutineStart.UNDISPATCHED) {
-            repo.dataStream.collect {
-            }
-        }
-
-        val callback: NetworkCallback = withArgCaptor {
-            verify(connectivityManager)
-                .registerNetworkCallback(any(NetworkRequest::class.java), capture())
-        }
-
-        // WHEN two new networks are added, and one is removed
-        callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
-        callback.onCapabilitiesChanged(NET_2, NET_2_CAPS)
-        callback.onLost(NET_1)
-
-        val currentMap = repo.dataStream.value
-
-        // THEN the current state of the flow reflects only the remaining network
-        assertThat(currentMap[NET_1_ID]).isNull()
-        assertThat(currentMap[NET_2_ID]?.network).isEqualTo(NET_2)
-        assertThat(currentMap[NET_2_ID]?.capabilities).isEqualTo(NET_2_CAPS)
-
-        job.cancel()
-        scope.cancel()
-    }
-
-    @Test
-    fun testOnLost_noNetworks_doesNotCrash() = runBlocking {
-        // GIVEN a repo hooked up to [ConnectivityManager]
-        val scope = CoroutineScope(Dispatchers.Unconfined)
-        val repo = NetworkCapabilitiesRepo(
-            connectivityManager = connectivityManager,
-            scope = scope,
-            logger = logger,
-        )
-
-        val job = launch(start = CoroutineStart.UNDISPATCHED) {
-            repo.dataStream.collect {
-            }
-        }
-
-        val callback: NetworkCallback = withArgCaptor {
-            verify(connectivityManager)
-                .registerNetworkCallback(any(NetworkRequest::class.java), capture())
-        }
-
-        // WHEN no networks are added, and one is removed
-        callback.onLost(NET_1)
-
-        val currentMap = repo.dataStream.value
-
-        // THEN the current state of the flow shows no networks
-        assertThat(currentMap).isEmpty()
-
-        job.cancel()
-        scope.cancel()
-    }
-
-    private val NET_1_ID = 100
-    private val NET_1 = mock<Network>().also {
-        whenever(it.getNetId()).thenReturn(NET_1_ID)
-    }
-    private val NET_2_ID = 200
-    private val NET_2 = mock<Network>().also {
-        whenever(it.getNetId()).thenReturn(NET_2_ID)
-    }
-
-    private val NET_1_CAPS = NetworkCapabilities.Builder()
-        .addTransportType(TRANSPORT_CELLULAR)
-        .addCapability(NET_CAPABILITY_VALIDATED)
-        .build()
-
-    private val NET_2_CAPS = NetworkCapabilities.Builder()
-        .addTransportType(TRANSPORT_WIFI)
-        .addCapability(NET_CAPABILITY_NOT_METERED)
-        .addCapability(NET_CAPABILITY_VALIDATED)
-        .build()
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
index 8b61364..d0a3808 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
@@ -16,16 +16,26 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.data.repository
 
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.vcn.VcnTransportInfo
+import android.net.wifi.WifiInfo
 import android.net.wifi.WifiManager
 import android.net.wifi.WifiManager.TrafficStateCallback
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.Executor
@@ -38,6 +48,7 @@
 import org.junit.Test
 import org.mockito.Mock
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -47,6 +58,7 @@
     private lateinit var underTest: WifiRepositoryImpl
 
     @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var wifiManager: WifiManager
     private lateinit var executor: Executor
 
@@ -54,11 +66,347 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         executor = FakeExecutor(FakeSystemClock())
+
+        underTest = WifiRepositoryImpl(
+            connectivityManager,
+            wifiManager,
+            executor,
+            logger,
+        )
+    }
+
+    @Test
+    fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) {
+        var latest: WifiNetworkModel? = null
+        val job = underTest
+            .wifiNetwork
+            .onEach { latest = it }
+            .launchIn(this)
+
+        assertThat(latest).isEqualTo(WIFI_NETWORK_DEFAULT)
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() = runBlocking(IMMEDIATE) {
+        var latest: WifiNetworkModel? = null
+        val job = underTest
+            .wifiNetwork
+            .onEach { latest = it }
+            .launchIn(this)
+
+        val wifiInfo = mock<WifiInfo>().apply {
+            whenever(this.ssid).thenReturn(SSID)
+            whenever(this.isPrimary).thenReturn(true)
+        }
+        val network = mock<Network>().apply {
+            whenever(this.getNetId()).thenReturn(NETWORK_ID)
+        }
+
+        getNetworkCallback().onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
+
+        assertThat(latest is WifiNetworkModel.Active).isTrue()
+        val latestActive = latest as WifiNetworkModel.Active
+        assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
+        assertThat(latestActive.ssid).isEqualTo(SSID)
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() = runBlocking(IMMEDIATE) {
+        var latest: WifiNetworkModel? = null
+        val job = underTest
+            .wifiNetwork
+            .onEach { latest = it }
+            .launchIn(this)
+
+        val wifiInfo = mock<WifiInfo>().apply {
+            whenever(this.ssid).thenReturn(SSID)
+            whenever(this.isPrimary).thenReturn(false)
+        }
+
+        getNetworkCallback().onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+        assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() = runBlocking(IMMEDIATE) {
+        var latest: WifiNetworkModel? = null
+        val job = underTest
+            .wifiNetwork
+            .onEach { latest = it }
+            .launchIn(this)
+
+        val capabilities = mock<NetworkCapabilities>().apply {
+            whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+            whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
+        }
+
+        getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+        assertThat(latest is WifiNetworkModel.Active).isTrue()
+        val latestActive = latest as WifiNetworkModel.Active
+        assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
+        assertThat(latestActive.ssid).isEqualTo(SSID)
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiNetwork_nonPrimaryCellularVcnNetworkAdded_flowHasNoNetwork() = runBlocking(IMMEDIATE) {
+        var latest: WifiNetworkModel? = null
+        val job = underTest
+            .wifiNetwork
+            .onEach { latest = it }
+            .launchIn(this)
+
+        val wifiInfo = mock<WifiInfo>().apply {
+            whenever(this.ssid).thenReturn(SSID)
+            whenever(this.isPrimary).thenReturn(false)
+        }
+        val capabilities = mock<NetworkCapabilities>().apply {
+            whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+            whenever(this.transportInfo).thenReturn(VcnTransportInfo(wifiInfo))
+        }
+
+        getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+        assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiNetwork_cellularNotVcnNetworkAdded_flowHasNoNetwork() = runBlocking(IMMEDIATE) {
+        var latest: WifiNetworkModel? = null
+        val job = underTest
+            .wifiNetwork
+            .onEach { latest = it }
+            .launchIn(this)
+
+        val capabilities = mock<NetworkCapabilities>().apply {
+            whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+            whenever(this.transportInfo).thenReturn(mock())
+        }
+
+        getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+        assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() = runBlocking(IMMEDIATE) {
+        var latest: WifiNetworkModel? = null
+        val job = underTest
+            .wifiNetwork
+            .onEach { latest = it }
+            .launchIn(this)
+
+        // Start with the original network
+        getNetworkCallback()
+            .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+
+        // WHEN we update to a new primary network
+        val newNetworkId = 456
+        val newNetwork = mock<Network>().apply {
+            whenever(this.getNetId()).thenReturn(newNetworkId)
+        }
+        val newSsid = "CD"
+        val newWifiInfo = mock<WifiInfo>().apply {
+            whenever(this.ssid).thenReturn(newSsid)
+            whenever(this.isPrimary).thenReturn(true)
+        }
+
+        getNetworkCallback().onCapabilitiesChanged(
+            newNetwork, createWifiNetworkCapabilities(newWifiInfo)
+        )
+
+        // THEN we use the new network
+        assertThat(latest is WifiNetworkModel.Active).isTrue()
+        val latestActive = latest as WifiNetworkModel.Active
+        assertThat(latestActive.networkId).isEqualTo(newNetworkId)
+        assertThat(latestActive.ssid).isEqualTo(newSsid)
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiNetwork_newNonPrimaryWifiNetwork_flowHasOldNetwork() = runBlocking(IMMEDIATE) {
+        var latest: WifiNetworkModel? = null
+        val job = underTest
+            .wifiNetwork
+            .onEach { latest = it }
+            .launchIn(this)
+
+        // Start with the original network
+        getNetworkCallback()
+            .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+
+        // WHEN we notify of a new but non-primary network
+        val newNetworkId = 456
+        val newNetwork = mock<Network>().apply {
+            whenever(this.getNetId()).thenReturn(newNetworkId)
+        }
+        val newSsid = "EF"
+        val newWifiInfo = mock<WifiInfo>().apply {
+            whenever(this.ssid).thenReturn(newSsid)
+            whenever(this.isPrimary).thenReturn(false)
+        }
+
+        getNetworkCallback().onCapabilitiesChanged(
+            newNetwork, createWifiNetworkCapabilities(newWifiInfo)
+        )
+
+        // THEN we still use the original network
+        assertThat(latest is WifiNetworkModel.Active).isTrue()
+        val latestActive = latest as WifiNetworkModel.Active
+        assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
+        assertThat(latestActive.ssid).isEqualTo(SSID)
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiNetwork_newNetworkCapabilities_flowHasNewData() = runBlocking(IMMEDIATE) {
+        var latest: WifiNetworkModel? = null
+        val job = underTest
+            .wifiNetwork
+            .onEach { latest = it }
+            .launchIn(this)
+
+        val wifiInfo = mock<WifiInfo>().apply {
+        whenever(this.ssid).thenReturn(SSID)
+        whenever(this.isPrimary).thenReturn(true)
+    }
+
+        // Start with the original network
+        getNetworkCallback()
+            .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+        // WHEN we keep the same network ID but change the SSID
+        val newSsid = "CD"
+        val newWifiInfo = mock<WifiInfo>().apply {
+            whenever(this.ssid).thenReturn(newSsid)
+            whenever(this.isPrimary).thenReturn(true)
+        }
+
+        getNetworkCallback()
+            .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(newWifiInfo))
+
+        // THEN we've updated to the new SSID
+        assertThat(latest is WifiNetworkModel.Active).isTrue()
+        val latestActive = latest as WifiNetworkModel.Active
+        assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
+        assertThat(latestActive.ssid).isEqualTo(newSsid)
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() = runBlocking(IMMEDIATE) {
+        var latest: WifiNetworkModel? = null
+        val job = underTest
+            .wifiNetwork
+            .onEach { latest = it }
+            .launchIn(this)
+
+        // WHEN we receive #onLost without any #onCapabilitiesChanged beforehand
+        getNetworkCallback().onLost(NETWORK)
+
+        // THEN there's no crash and we still have no network
+        assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiNetwork_currentNetworkLost_flowHasNoNetwork() = runBlocking(IMMEDIATE) {
+        var latest: WifiNetworkModel? = null
+        val job = underTest
+            .wifiNetwork
+            .onEach { latest = it }
+            .launchIn(this)
+
+        getNetworkCallback()
+            .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+        assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
+
+        // WHEN we lose our current network
+        getNetworkCallback().onLost(NETWORK)
+
+        // THEN we update to no network
+        assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() = runBlocking(IMMEDIATE) {
+        var latest: WifiNetworkModel? = null
+        val job = underTest
+            .wifiNetwork
+            .onEach { latest = it }
+            .launchIn(this)
+
+        getNetworkCallback()
+            .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+        assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
+
+        // WHEN we lose an unknown network
+        val unknownNetwork = mock<Network>().apply {
+            whenever(this.getNetId()).thenReturn(543)
+        }
+        getNetworkCallback().onLost(unknownNetwork)
+
+        // THEN we still have our previous network
+        assertThat(latest is WifiNetworkModel.Active).isTrue()
+        val latestActive = latest as WifiNetworkModel.Active
+        assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
+        assertThat(latestActive.ssid).isEqualTo(SSID)
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiNetwork_notCurrentNetworkLost_flowHasCurrentNetwork() = runBlocking(IMMEDIATE) {
+        var latest: WifiNetworkModel? = null
+        val job = underTest
+            .wifiNetwork
+            .onEach { latest = it }
+            .launchIn(this)
+
+        getNetworkCallback()
+            .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+        assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
+
+        // WHEN we update to a new network...
+        val newNetworkId = 89
+        val newNetwork = mock<Network>().apply {
+            whenever(this.getNetId()).thenReturn(newNetworkId)
+        }
+        getNetworkCallback().onCapabilitiesChanged(
+            newNetwork, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
+        )
+        // ...and lose the old network
+        getNetworkCallback().onLost(NETWORK)
+
+        // THEN we still have the new network
+        assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(newNetworkId)
+
+        job.cancel()
     }
 
     @Test
     fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) {
         underTest = WifiRepositoryImpl(
+                connectivityManager,
                 wifiManager = null,
                 executor,
                 logger,
@@ -77,12 +425,6 @@
 
     @Test
     fun wifiActivity_callbackGivesNone_activityFlowHasNone() = runBlocking(IMMEDIATE) {
-        underTest = WifiRepositoryImpl(
-                wifiManager,
-                executor,
-                logger,
-        )
-
         var latest: WifiActivityModel? = null
         val job = underTest
                 .wifiActivity
@@ -100,12 +442,6 @@
 
     @Test
     fun wifiActivity_callbackGivesIn_activityFlowHasIn() = runBlocking(IMMEDIATE) {
-        underTest = WifiRepositoryImpl(
-                wifiManager,
-                executor,
-                logger,
-        )
-
         var latest: WifiActivityModel? = null
         val job = underTest
                 .wifiActivity
@@ -123,12 +459,6 @@
 
     @Test
     fun wifiActivity_callbackGivesOut_activityFlowHasOut() = runBlocking(IMMEDIATE) {
-        underTest = WifiRepositoryImpl(
-                wifiManager,
-                executor,
-                logger,
-        )
-
         var latest: WifiActivityModel? = null
         val job = underTest
                 .wifiActivity
@@ -146,12 +476,6 @@
 
     @Test
     fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = runBlocking(IMMEDIATE) {
-        underTest = WifiRepositoryImpl(
-                wifiManager,
-                executor,
-                logger,
-        )
-
         var latest: WifiActivityModel? = null
         val job = underTest
                 .wifiActivity
@@ -170,6 +494,30 @@
         verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
         return callbackCaptor.value!!
     }
+
+    private fun getNetworkCallback(): ConnectivityManager.NetworkCallback {
+        val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
+        verify(connectivityManager).registerNetworkCallback(any(), callbackCaptor.capture())
+        return callbackCaptor.value!!
+    }
+
+    private fun createWifiNetworkCapabilities(wifiInfo: WifiInfo) =
+        mock<NetworkCapabilities>().apply {
+            whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+            whenever(this.transportInfo).thenReturn(wifiInfo)
+        }
+
+    private companion object {
+        const val NETWORK_ID = 45
+        val NETWORK = mock<Network>().apply {
+            whenever(this.getNetId()).thenReturn(NETWORK_ID)
+        }
+        const val SSID = "AB"
+        val PRIMARY_WIFI_INFO: WifiInfo = mock<WifiInfo>().apply {
+            whenever(this.ssid).thenReturn(SSID)
+            whenever(this.isPrimary).thenReturn(true)
+        }
+    }
 }
 
 private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
index c52f347..5f1b1db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
@@ -19,7 +19,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
@@ -47,7 +47,7 @@
 
     @Test
     fun hasActivityIn_noInOrOut_outputsFalse() = runBlocking(IMMEDIATE) {
-        repository.setWifiModel(WifiModel(ssid = "AB"))
+        repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
         repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = false))
 
         var latest: Boolean? = null
@@ -63,7 +63,7 @@
 
     @Test
     fun hasActivityIn_onlyOut_outputsFalse() = runBlocking(IMMEDIATE) {
-        repository.setWifiModel(WifiModel(ssid = "AB"))
+        repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
         repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = true))
 
         var latest: Boolean? = null
@@ -79,7 +79,7 @@
 
     @Test
     fun hasActivityIn_onlyIn_outputsTrue() = runBlocking(IMMEDIATE) {
-        repository.setWifiModel(WifiModel(ssid = "AB"))
+        repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
         repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
 
         var latest: Boolean? = null
@@ -95,7 +95,7 @@
 
     @Test
     fun hasActivityIn_inAndOut_outputsTrue() = runBlocking(IMMEDIATE) {
-        repository.setWifiModel(WifiModel(ssid = "AB"))
+        repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
         repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
 
         var latest: Boolean? = null
@@ -111,7 +111,7 @@
 
     @Test
     fun hasActivityIn_ssidNull_outputsFalse() = runBlocking(IMMEDIATE) {
-        repository.setWifiModel(WifiModel(ssid = null))
+        repository.setWifiNetwork(WifiNetworkModel.Active(networkId = 1, ssid = null))
         repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
 
         var latest: Boolean? = null
@@ -127,7 +127,7 @@
 
     @Test
     fun hasActivityIn_multipleChanges_multipleOutputChanges() = runBlocking(IMMEDIATE) {
-        repository.setWifiModel(WifiModel(ssid = "AB"))
+        repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
 
         var latest: Boolean? = null
         val job = underTest
@@ -158,6 +158,10 @@
 
         job.cancel()
     }
+
+    companion object {
+        val VALID_WIFI_NETWORK_MODEL = WifiNetworkModel.Active(networkId = 1, ssid = "AB")
+    }
 }
 
 private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
new file mode 100644
index 0000000..3c200a5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.ui.view
+
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.lifecycle.InstantTaskExecutorRule
+import com.android.systemui.util.Assert
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+@RunWithLooper
+class ModernStatusBarWifiViewTest : SysuiTestCase() {
+
+    @JvmField @Rule
+    val instantTaskExecutor = InstantTaskExecutorRule()
+
+    @Before
+    fun setUp() {
+        Assert.setTestThread(Thread.currentThread())
+    }
+
+    @Test
+    fun constructAndBind_hasCorrectSlot() {
+        val view = ModernStatusBarWifiView.constructAndBind(
+            context, "slotName", mock()
+        )
+
+        assertThat(view.slot).isEqualTo("slotName")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index e9259b0..c790734 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -18,9 +18,10 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
@@ -43,6 +44,7 @@
 
     private lateinit var underTest: WifiViewModel
 
+    @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var logger: ConnectivityPipelineLogger
     @Mock private lateinit var constants: WifiConstants
     private lateinit var repository: FakeWifiRepository
@@ -55,13 +57,14 @@
         interactor = WifiInteractor(repository)
 
         underTest = WifiViewModel(
-                constants,
-                logger,
-                interactor
+            statusBarPipelineFlags,
+            constants,
+            logger,
+            interactor
         )
 
         // Set up with a valid SSID
-        repository.setWifiModel(WifiModel(ssid = "AB"))
+        repository.setWifiNetwork(WifiNetworkModel.Active(networkId = 1, ssid = "AB"))
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index e2b9a9e..9764b8c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -63,7 +63,6 @@
     private static final int TEST_A11Y_AUTO_DISMISS_TIME = 600;
     private static final int TEST_A11Y_TIMEOUT_TIME = 5_000;
 
-    private AccessibilityManagerWrapper mAccessibilityMgr;
     private HeadsUpManager mHeadsUpManager;
     private boolean mLivesPastNormalTime;
     private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
@@ -72,10 +71,15 @@
     @Mock private StatusBarNotification mSbn;
     @Mock private Notification mNotification;
     @Mock private HeadsUpManagerLogger mLogger;
+    @Mock private AccessibilityManagerWrapper mAccessibilityMgr;
 
     private final class TestableHeadsUpManager extends HeadsUpManager {
-        TestableHeadsUpManager(Context context, HeadsUpManagerLogger logger, Handler handler) {
-            super(context, logger, handler);
+        TestableHeadsUpManager(Context context,
+                HeadsUpManagerLogger logger,
+                Handler handler,
+                AccessibilityManagerWrapper accessibilityManagerWrapper,
+                UiEventLogger uiEventLogger) {
+            super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger);
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
             mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
         }
@@ -88,13 +92,11 @@
     @Before
     public void setUp() {
         initMocks(this);
-        mAccessibilityMgr = mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
-        mDependency.injectTestDependency(UiEventLogger.class, mUiEventLoggerFake);
         when(mEntry.getSbn()).thenReturn(mSbn);
         when(mSbn.getNotification()).thenReturn(mNotification);
-
         super.setUp();
-        mHeadsUpManager = new TestableHeadsUpManager(mContext, mLogger, mTestHandler);
+        mHeadsUpManager = new TestableHeadsUpManager(mContext, mLogger, mTestHandler,
+                mAccessibilityMgr, mUiEventLoggerFake);
     }
 
     @After
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 b7f38f1..50259b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -310,7 +310,7 @@
     }
 
     @Test
-    public void onWallpaperColorsChanged_ResetThemeWithNewHomeWallpapers() {
+    public void onWallpaperColorsChanged_resetThemeWithNewHomeWallpapers() {
         // Should ask for a new theme when wallpaper colors change
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
                 Color.valueOf(Color.BLUE), null);
@@ -345,6 +345,61 @@
     }
 
     @Test
+    public void onWallpaperColorsChanged_keepsThemeWhenSetFromLockScreen() {
+        // Should ask for a new theme when wallpaper colors change
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+        String jsonString =
+                "{\"android.theme.customization.color_source\":\"lock_wallpaper\","
+                        + "\"android.theme.customization.system_palette\":\"A16B00\","
+                        + "\"android.theme.customization.accent_color\":\"A16B00\","
+                        + "\"android.theme.customization.color_index\":\"2\"}";
+        when(mSecureSettings.getStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+                .thenReturn(jsonString);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+                .thenReturn(20);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+                .thenReturn(21);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
+        verify(mSecureSettings, never()).putStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), any(), anyInt());
+    }
+
+    @Test
+    public void onWallpaperColorsChanged_resetLockScreenThemeWhenBothSet() {
+        // Should ask for a new theme when wallpaper colors change
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+        String jsonString =
+                "{\"android.theme.customization.color_source\":\"lock_wallpaper\","
+                        + "\"android.theme.customization.system_palette\":\"A16B00\","
+                        + "\"android.theme.customization.accent_color\":\"A16B00\","
+                        + "\"android.theme.customization.color_index\":\"2\"}";
+        when(mSecureSettings.getStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+                .thenReturn(jsonString);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+                .thenReturn(20);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+                .thenReturn(21);
+
+        mColorsListener.getValue().onColorsChanged(mainColors,
+                WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK,
+                USER_SYSTEM);
+
+        ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+        verify(mSecureSettings).putStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture(),
+                anyInt());
+        assertThat(updatedSetting.getValue().contains(
+                "android.theme.customization.color_both\":\"1")).isTrue();
+        verify(mThemeOverlayApplier)
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+    }
+
+    @Test
     public void onSettingChanged_honorThemeStyle() {
         when(mDeviceProvisionedController.isUserSetup(anyInt())).thenReturn(true);
         List<Style> validStyles = Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ, Style.TONAL_SPOT,
@@ -381,7 +436,7 @@
     }
 
     @Test
-    public void onWallpaperColorsChanged_ResetThemeWithNewHomeAndLockWallpaper() {
+    public void onWallpaperColorsChanged_resetThemeWithNewHomeAndLockWallpaper() {
         // Should ask for a new theme when wallpaper colors change
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
                 Color.valueOf(Color.BLUE), null);
@@ -450,7 +505,7 @@
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
                 Color.valueOf(Color.BLUE), null);
         String jsonString =
-                "{\"android.theme.customization.color_source\":\"lock_wallpaper\","
+                "{\"android.theme.customization.color_source\":\"home_wallpaper\","
                         + "\"android.theme.customization.system_palette\":\"A16B00\","
                         + "\"android.theme.customization.accent_color\":\"A16B00\","
                         + "\"android.theme.customization.color_index\":\"2\"}";
@@ -476,7 +531,7 @@
     }
 
     @Test
-    public void onWallpaperColorsChanged_ResetThemeWhenFromLatestWallpaper() {
+    public void onWallpaperColorsChanged_resetThemeWhenFromLatestWallpaper() {
         // Should ask for a new theme when the colors of the last applied wallpaper change
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
                 Color.valueOf(Color.BLUE), null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java
deleted file mode 100644
index 4f509ea..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/util/SysuiLifecycleTest.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util;
-
-import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
-import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
-import static androidx.lifecycle.Lifecycle.Event.ON_START;
-import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
-
-import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-import android.testing.ViewUtils;
-import android.view.View;
-
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleEventObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-public class SysuiLifecycleTest extends SysuiTestCase {
-
-    private View mView;
-
-    @Before
-    public void setUp() {
-        mView = new View(mContext);
-    }
-
-    @After
-    public void tearDown() {
-        if (mView.isAttachedToWindow()) {
-            ViewUtils.detachView(mView);
-            TestableLooper.get(this).processAllMessages();
-        }
-    }
-
-    @Test
-    public void testAttach() {
-        LifecycleEventObserver observer = mock(LifecycleEventObserver.class);
-        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
-        lifecycle.getLifecycle().addObserver(observer);
-
-        ViewUtils.attachView(mView);
-        TestableLooper.get(this).processAllMessages();
-
-        verify(observer).onStateChanged(eq(lifecycle), eq(ON_CREATE));
-        verify(observer).onStateChanged(eq(lifecycle), eq(ON_START));
-        verify(observer).onStateChanged(eq(lifecycle), eq(ON_RESUME));
-    }
-
-    @Test
-    public void testDetach() {
-        LifecycleEventObserver observer = mock(LifecycleEventObserver.class);
-        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
-        lifecycle.getLifecycle().addObserver(observer);
-
-        ViewUtils.attachView(mView);
-        TestableLooper.get(this).processAllMessages();
-
-        ViewUtils.detachView(mView);
-        TestableLooper.get(this).processAllMessages();
-
-        verify(observer).onStateChanged(eq(lifecycle), eq(ON_PAUSE));
-        verify(observer).onStateChanged(eq(lifecycle), eq(ON_STOP));
-        verify(observer).onStateChanged(eq(lifecycle), eq(ON_DESTROY));
-    }
-
-    @Test
-    public void testStateBeforeAttach() {
-        // WHEN a lifecycle is obtained from a view
-        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
-        // THEN the lifecycle state should be INITIAZED
-        assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(
-                Lifecycle.State.INITIALIZED);
-    }
-
-    @Test
-    public void testStateAfterAttach() {
-        // WHEN a lifecycle is obtained from a view
-        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
-        // AND the view is attached
-        ViewUtils.attachView(mView);
-        TestableLooper.get(this).processAllMessages();
-        // THEN the lifecycle state should be RESUMED
-        assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.RESUMED);
-    }
-
-    @Test
-    public void testStateAfterDetach() {
-        // WHEN a lifecycle is obtained from a view
-        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
-        // AND the view is detached
-        ViewUtils.attachView(mView);
-        TestableLooper.get(this).processAllMessages();
-        ViewUtils.detachView(mView);
-        TestableLooper.get(this).processAllMessages();
-        // THEN the lifecycle state should be DESTROYED
-        assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.DESTROYED);
-    }
-
-    @Test
-    public void testStateAfterReattach() {
-        // WHEN a lifecycle is obtained from a view
-        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
-        // AND the view is re-attached
-        ViewUtils.attachView(mView);
-        TestableLooper.get(this).processAllMessages();
-        ViewUtils.detachView(mView);
-        TestableLooper.get(this).processAllMessages();
-        ViewUtils.attachView(mView);
-        TestableLooper.get(this).processAllMessages();
-        // THEN the lifecycle state should still be DESTROYED, err RESUMED?
-        assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.RESUMED);
-    }
-
-    @Test
-    public void testStateWhenViewAlreadyAttached() {
-        // GIVEN that a view is already attached
-        ViewUtils.attachView(mView);
-        TestableLooper.get(this).processAllMessages();
-        // WHEN a lifecycle is obtained from a view
-        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
-        // THEN the lifecycle state should be RESUMED
-        assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(Lifecycle.State.RESUMED);
-    }
-
-    @Test
-    public void testStateWhenViewAlreadyDetached() {
-        // GIVEN that a view is already detached
-        ViewUtils.attachView(mView);
-        TestableLooper.get(this).processAllMessages();
-        ViewUtils.detachView(mView);
-        TestableLooper.get(this).processAllMessages();
-        // WHEN a lifecycle is obtained from a view
-        LifecycleOwner lifecycle = viewAttachLifecycle(mView);
-        // THEN the lifecycle state should be INITIALIZED
-        assertThat(lifecycle.getLifecycle().getCurrentState()).isEqualTo(
-                Lifecycle.State.INITIALIZED);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
new file mode 100644
index 0000000..15ba672
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import java.util.concurrent.atomic.AtomicLong
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class IpcSerializerTest : SysuiTestCase() {
+
+    private val serializer = IpcSerializer()
+
+    @Test
+    fun serializeManyIncomingIpcs(): Unit = runBlocking(Dispatchers.Main.immediate) {
+        val processor = launch(start = CoroutineStart.LAZY) { serializer.process() }
+        withContext(Dispatchers.IO) {
+            val lastEvaluatedTime = AtomicLong(System.currentTimeMillis())
+            // First, launch many serialization requests in parallel
+            repeat(100_000) {
+                launch(Dispatchers.Unconfined) {
+                    val enqueuedTime = System.currentTimeMillis()
+                    serializer.runSerialized {
+                        val last = lastEvaluatedTime.getAndSet(enqueuedTime)
+                        assertTrue(
+                            "expected $last less than or equal to $enqueuedTime ",
+                            last <= enqueuedTime,
+                        )
+                    }
+                }
+            }
+            // Then, process them all in the order they came in.
+            processor.start()
+        }
+        // All done, stop processing
+        processor.cancel()
+    }
+
+    @Test(timeout = 5000)
+    fun serializeOnOneThread_doesNotDeadlock() = runBlocking {
+        val job = launch { serializer.process() }
+        repeat(100) {
+            serializer.runSerializedBlocking { }
+        }
+        job.cancel()
+    }
+}
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 18acf3f..5d63632 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -63,6 +63,7 @@
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.Handler;
 import android.os.PowerManager;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.service.dreams.IDreamManager;
@@ -134,6 +135,7 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
@@ -204,6 +206,8 @@
     private ArgumentCaptor<IntentFilter> mFilterArgumentCaptor;
     @Captor
     private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
+    @Captor
+    private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallbackCaptor;
 
     private BubblesManager mBubblesManager;
     private TestableBubbleController mBubbleController;
@@ -222,6 +226,8 @@
     @Mock
     private ShellInit mShellInit;
     @Mock
+    private ShellCommandHandler mShellCommandHandler;
+    @Mock
     private ShellController mShellController;
     @Mock
     private Bubbles.BubbleExpandListener mBubbleExpandListener;
@@ -240,6 +246,8 @@
     @Mock
     private IStatusBarService mStatusBarService;
     @Mock
+    private IDreamManager mIDreamManager;
+    @Mock
     private NotificationVisibilityProvider mVisibilityProvider;
     @Mock
     private LauncherApps mLauncherApps;
@@ -344,6 +352,7 @@
         mBubbleController = new TestableBubbleController(
                 mContext,
                 mShellInit,
+                mShellCommandHandler,
                 mShellController,
                 mBubbleData,
                 mFloatingContentCoordinator,
@@ -371,10 +380,11 @@
                 mContext,
                 mBubbleController.asBubbles(),
                 mNotificationShadeWindowController,
-                mock(KeyguardStateController.class),
+                mKeyguardStateController,
                 mShadeController,
                 mStatusBarService,
                 mock(INotificationManager.class),
+                mIDreamManager,
                 mVisibilityProvider,
                 interruptionStateProvider,
                 mZenModeController,
@@ -383,7 +393,6 @@
                 mCommonNotifCollection,
                 mNotifPipeline,
                 mSysUiState,
-                mDumpManager,
                 syncExecutor);
         mBubblesManager.addNotifCallback(mNotifCallback);
 
@@ -391,6 +400,25 @@
         verify(mNotifPipeline, atLeastOnce())
                 .addCollectionListener(mNotifListenerCaptor.capture());
         mEntryListener = mNotifListenerCaptor.getValue();
+
+        // Get a reference to KeyguardStateController.Callback
+        verify(mKeyguardStateController, atLeastOnce())
+                .addCallback(mKeyguardStateControllerCallbackCaptor.capture());
+    }
+
+    @Test
+    public void dreamingHidesBubbles() throws RemoteException {
+        mBubbleController.updateBubble(mBubbleEntry);
+        assertTrue(mBubbleController.hasBubbles());
+        assertThat(mBubbleController.getStackView().getVisibility()).isEqualTo(View.VISIBLE);
+
+        when(mIDreamManager.isDreamingOrInPreview()).thenReturn(true); // dreaming is happening
+        when(mKeyguardStateController.isShowing()).thenReturn(false); // device is unlocked
+        KeyguardStateController.Callback callback =
+                mKeyguardStateControllerCallbackCaptor.getValue();
+        callback.onKeyguardShowingChanged();
+
+        assertThat(mBubbleController.getStackView().getVisibility()).isEqualTo(View.INVISIBLE);
     }
 
     @Test
@@ -1370,6 +1398,33 @@
         assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE);
     }
 
+    /**
+     * Test to verify behavior for following situation:
+     * <ul>
+     *     <li>status bar shade state is set to <code>false</code></li>
+     *     <li>there is a bubble pending to be expanded</li>
+     * </ul>
+     * Test that duplicate status bar state updates to <code>false</code> do not clear the
+     * pending bubble to be
+     * expanded.
+     */
+    @Test
+    public void testOnStatusBarStateChanged_statusBarChangeDoesNotClearExpandingBubble() {
+        mBubbleController.updateBubble(mBubbleEntry);
+        mBubbleController.onStatusBarStateChanged(false);
+        // Set the bubble to expand once status bar state changes
+        mBubbleController.expandStackAndSelectBubble(mBubbleEntry);
+        // Check that stack is currently collapsed
+        assertStackCollapsed();
+        // Post status bar state change update with the same value
+        mBubbleController.onStatusBarStateChanged(false);
+        // Stack should remain collapsedb
+        assertStackCollapsed();
+        // Post status bar state change which should trigger bubble to expand
+        mBubbleController.onStatusBarStateChanged(true);
+        assertStackExpanded();
+    }
+
     @Test
     public void testSetShouldAutoExpand_notifiesFlagChanged() {
         mBubbleController.updateBubble(mBubbleEntry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index 880ad187..6357a09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -38,6 +38,7 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
@@ -51,6 +52,7 @@
     // Let's assume surfaces can be synchronized immediately.
     TestableBubbleController(Context context,
             ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
             ShellController shellController,
             BubbleData data,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -71,12 +73,12 @@
             Handler shellMainHandler,
             TaskViewTransitions taskViewTransitions,
             SyncTransactionQueue syncQueue) {
-        super(context, shellInit, shellController, data, Runnable::run, floatingContentCoordinator,
-                dataRepository, statusBarService, windowManager, windowManagerShellWrapper,
-                userManager, launcherApps, bubbleLogger, taskStackListener, shellTaskOrganizer,
-                positioner, displayController, oneHandedOptional, dragAndDropController,
-                shellMainExecutor, shellMainHandler, new SyncExecutor(), taskViewTransitions,
-                syncQueue);
+        super(context, shellInit, shellCommandHandler, shellController, data, Runnable::run,
+                floatingContentCoordinator, dataRepository, statusBarService, windowManager,
+                windowManagerShellWrapper, userManager, launcherApps, bubbleLogger,
+                taskStackListener, shellTaskOrganizer, positioner, displayController,
+                oneHandedOptional, dragAndDropController, shellMainExecutor, shellMainHandler,
+                new SyncExecutor(), taskViewTransitions, syncQueue);
         setInflateSynchronously(true);
         onInit();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 9c21366..da33fa6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -28,10 +28,10 @@
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.onehanded.OneHanded;
@@ -72,7 +72,7 @@
     @Mock OneHanded mOneHanded;
     @Mock WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock ProtoTracer mProtoTracer;
-    @Mock UserInfoController mUserInfoController;
+    @Mock UserTracker mUserTracker;
     @Mock ShellExecutor mSysUiMainExecutor;
 
     @Before
@@ -83,7 +83,7 @@
                 Optional.of(mSplitScreen), Optional.of(mOneHanded), mCommandQueue,
                 mConfigurationController, mKeyguardStateController, mKeyguardUpdateMonitor,
                 mScreenLifecycle, mSysUiState, mProtoTracer, mWakefulnessLifecycle,
-                mUserInfoController, mSysUiMainExecutor);
+                mUserTracker, mSysUiMainExecutor);
     }
 
     @Test
diff --git a/services/OWNERS b/services/OWNERS
index 67cee55..495c0737 100644
--- a/services/OWNERS
+++ b/services/OWNERS
@@ -1,4 +1,4 @@
-per-file Android.bp = file:platform/build/soong:/OWNERS
+per-file Android.bp = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION}
 
 # art-team@ manages the system server profile
 per-file art-profile* = calin@google.com, ngeoffray@google.com, vmarko@google.com
diff --git a/services/api/OWNERS b/services/api/OWNERS
index a609390..e10440c 100644
--- a/services/api/OWNERS
+++ b/services/api/OWNERS
@@ -1,4 +1,4 @@
-per-file Android.bp = file:platform/build/soong:/OWNERS
+per-file Android.bp = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION}
 
 # API changes are managed via Prolog rules, not OWNERS
 *
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index fa043f8..265fbc5 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -17,7 +17,6 @@
 
 package com.android.server.companion;
 
-import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
 import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
 import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
@@ -88,7 +87,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArraySet;
-import android.util.Base64;
 import android.util.ExceptionUtils;
 import android.util.Log;
 import android.util.Slog;
@@ -756,6 +754,11 @@
             mDevicePresenceMonitor.onSelfManagedDeviceDisconnected(associationId);
         }
 
+        @Override
+        public boolean isCompanionApplicationBound(String packageName, int userId) {
+            return mCompanionAppController.isCompanionApplicationBound(userId, packageName);
+        }
+
         private void registerDevicePresenceListenerActive(String packageName, String deviceAddress,
                 boolean active) throws RemoteException {
             if (DEBUG) {
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index b881545..b33d84c 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
 import static android.system.OsConstants.O_RDONLY;
 
 import android.content.BroadcastReceiver;
@@ -26,8 +27,10 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.MessageQueue.OnFileDescriptorEventListener;
+import android.os.ParcelFileDescriptor;
 import android.os.RecoverySystem;
 import android.os.SystemProperties;
+import android.os.TombstoneWithHeadersProto;
 import android.provider.Downloads;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -38,6 +41,7 @@
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
@@ -54,6 +58,8 @@
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.attribute.PosixFilePermissions;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.regex.Matcher;
@@ -78,6 +84,11 @@
 
     private static final String TAG_TOMBSTONE = "SYSTEM_TOMBSTONE";
     private static final String TAG_TOMBSTONE_PROTO = "SYSTEM_TOMBSTONE_PROTO";
+    private static final String TAG_TOMBSTONE_PROTO_WITH_HEADERS =
+            "SYSTEM_TOMBSTONE_PROTO_WITH_HEADERS";
+
+    // Directory to store temporary tombstones.
+    private static final File TOMBSTONE_TMP_DIR = new File("/data/tombstones");
 
     // The pre-froyo package and class of the system updater, which
     // ran in the system process.  We need to remove its packages here
@@ -329,7 +340,44 @@
         try {
             if (proto) {
                 if (recordFileTimestamp(tombstone, timestamps)) {
-                    db.addFile(TAG_TOMBSTONE_PROTO, tombstone, 0);
+                    // We need to attach the count indicating the number of dropped dropbox entries
+                    // due to rate limiting. Do this by enclosing the proto tombsstone in a
+                    // container proto that has the dropped entry count and the proto tombstone as
+                    // bytes (to avoid the complexity of reading and writing nested protos).
+
+                    // Read the proto tombstone file as bytes.
+                    final byte[] tombstoneBytes = Files.readAllBytes(tombstone.toPath());
+
+                    final File tombstoneProtoWithHeaders = File.createTempFile(
+                            tombstone.getName(), ".tmp", TOMBSTONE_TMP_DIR);
+                    Files.setPosixFilePermissions(
+                            tombstoneProtoWithHeaders.toPath(),
+                            PosixFilePermissions.fromString("rw-rw----"));
+
+                    // Write the new proto container proto with headers.
+                    ParcelFileDescriptor pfd;
+                    try {
+                        pfd = ParcelFileDescriptor.open(tombstoneProtoWithHeaders, MODE_READ_WRITE);
+
+                        ProtoOutputStream protoStream = new ProtoOutputStream(
+                                pfd.getFileDescriptor());
+                        protoStream.write(TombstoneWithHeadersProto.TOMBSTONE, tombstoneBytes);
+                        protoStream.write(
+                                TombstoneWithHeadersProto.DROPPED_COUNT,
+                                rateLimitResult.droppedCountSinceRateLimitActivated());
+                        protoStream.flush();
+
+                        // Add the proto to dropbox.
+                        db.addFile(TAG_TOMBSTONE_PROTO_WITH_HEADERS, tombstoneProtoWithHeaders, 0);
+                    } catch (FileNotFoundException ex) {
+                        Slog.e(TAG, "failed to open for write: " + tombstoneProtoWithHeaders, ex);
+                        throw ex;
+                    } finally {
+                        // Remove the temporary file.
+                        if (tombstoneProtoWithHeaders != null) {
+                            tombstoneProtoWithHeaders.delete();
+                        }
+                    }
                 }
             } else {
                 // Add the header indicating how many events have been dropped due to rate limiting.
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 0b858cf..f7833b0 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -339,7 +339,7 @@
 
     private int[] mDataConnectionNetworkType;
 
-    private ArrayList<List<CellInfo>> mCellInfo = null;
+    private ArrayList<List<CellInfo>> mCellInfo;
 
     private Map<Integer, List<EmergencyNumber>> mEmergencyNumberList;
 
@@ -725,7 +725,7 @@
                 mMessageWaiting[i] = false;
                 mCallForwarding[i] = false;
                 mCellIdentity[i] = null;
-                mCellInfo.add(i, null);
+                mCellInfo.add(i, Collections.EMPTY_LIST);
                 mImsReasonInfo.add(i, null);
                 mSrvccState[i] = TelephonyManager.SRVCC_STATE_HANDOVER_NONE;
                 mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
@@ -802,7 +802,7 @@
         mCallNetworkType = new int[numPhones];
         mCallAttributes = new CallAttributes[numPhones];
         mPreciseDataConnectionStates = new ArrayList<>();
-        mCellInfo = new ArrayList<>();
+        mCellInfo = new ArrayList<>(numPhones);
         mImsReasonInfo = new ArrayList<>();
         mEmergencyNumberList = new HashMap<>();
         mOutgoingCallEmergencyNumber = new EmergencyNumber[numPhones];
@@ -832,7 +832,7 @@
             mMessageWaiting[i] =  false;
             mCallForwarding[i] =  false;
             mCellIdentity[i] = null;
-            mCellInfo.add(i, null);
+            mCellInfo.add(i, Collections.EMPTY_LIST);
             mImsReasonInfo.add(i, null);
             mSrvccState[i] = TelephonyManager.SRVCC_STATE_HANDOVER_NONE;
             mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
@@ -1803,10 +1803,17 @@
         if (!checkNotifyPermission("notifyCellInfoForSubscriber()")) {
             return;
         }
+
         if (VDBG) {
             log("notifyCellInfoForSubscriber: subId=" + subId
                 + " cellInfo=" + cellInfo);
         }
+
+        if (cellInfo == null) {
+            loge("notifyCellInfoForSubscriber() received a null list");
+            cellInfo = Collections.EMPTY_LIST;
+        }
+
         int phoneId = getPhoneIdFromSubId(subId);
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
@@ -2987,8 +2994,8 @@
                 pw.println("mBarringInfo=" + mBarringInfo.get(i));
                 pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState[i]);
                 pw.println("mTelephonyDisplayInfo=" + mTelephonyDisplayInfos[i]);
-                pw.println("mIsDataEnabled=" + mIsDataEnabled);
-                pw.println("mDataEnabledReason=" + mDataEnabledReason);
+                pw.println("mIsDataEnabled=" + mIsDataEnabled[i]);
+                pw.println("mDataEnabledReason=" + mDataEnabledReason[i]);
                 pw.println("mAllowedNetworkTypeReason=" + mAllowedNetworkTypeReason[i]);
                 pw.println("mAllowedNetworkTypeValue=" + mAllowedNetworkTypeValue[i]);
                 pw.println("mPhysicalChannelConfigs=" + mPhysicalChannelConfigs.get(i));
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c8519c1..a0c2ad5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -454,6 +454,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -2400,13 +2401,13 @@
         mEnableOffloadQueue = SystemProperties.getBoolean(
                 "persist.device_config.activity_manager_native_boot.offload_queue_enabled", true);
 
-        mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
+        mFgBroadcastQueue = new BroadcastQueueImpl(this, mHandler,
                 "foreground", foreConstants, false);
-        mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
+        mBgBroadcastQueue = new BroadcastQueueImpl(this, mHandler,
                 "background", backConstants, true);
-        mBgOffloadBroadcastQueue = new BroadcastQueue(this, mHandler,
+        mBgOffloadBroadcastQueue = new BroadcastQueueImpl(this, mHandler,
                 "offload_bg", offloadConstants, true);
-        mFgOffloadBroadcastQueue = new BroadcastQueue(this, mHandler,
+        mFgOffloadBroadcastQueue = new BroadcastQueueImpl(this, mHandler,
                 "offload_fg", foreConstants, true);
         mBroadcastQueues[0] = mFgBroadcastQueue;
         mBroadcastQueues[1] = mBgBroadcastQueue;
@@ -5501,7 +5502,7 @@
             IIntentSender pendingResult, int matchFlags) {
         enforceCallingPermission(Manifest.permission.GET_INTENT_SENDER_INTENT,
                 "queryIntentComponentsForIntentSender()");
-        Preconditions.checkNotNull(pendingResult);
+        Objects.requireNonNull(pendingResult);
         final PendingIntentRecord res;
         try {
             res = (PendingIntentRecord) pendingResult;
@@ -5513,17 +5514,19 @@
             return null;
         }
         final int userId = res.key.userId;
+        final int uid = res.uid;
+        final String resolvedType = res.key.requestResolvedType;
         switch (res.key.type) {
             case ActivityManager.INTENT_SENDER_ACTIVITY:
-                return new ParceledListSlice<>(mContext.getPackageManager()
-                        .queryIntentActivitiesAsUser(intent, matchFlags, userId));
+                return new ParceledListSlice<>(mPackageManagerInt.queryIntentActivities(
+                        intent, resolvedType, matchFlags, uid, userId));
             case ActivityManager.INTENT_SENDER_SERVICE:
             case ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE:
-                return new ParceledListSlice<>(mContext.getPackageManager()
-                        .queryIntentServicesAsUser(intent, matchFlags, userId));
+                return new ParceledListSlice<>(mPackageManagerInt.queryIntentServices(
+                        intent, matchFlags, uid, userId));
             case ActivityManager.INTENT_SENDER_BROADCAST:
-                return new ParceledListSlice<>(mContext.getPackageManager()
-                        .queryBroadcastReceiversAsUser(intent, matchFlags, userId));
+                return new ParceledListSlice<>(mPackageManagerInt.queryIntentReceivers(
+                        intent, resolvedType, matchFlags, uid, userId, false));
             default: // ActivityManager.INTENT_SENDER_ACTIVITY_RESULT
                 throw new IllegalStateException("Unsupported intent sender type: " + res.key.type);
         }
@@ -10623,8 +10626,7 @@
         if (!onlyHistory && !onlyReceivers && dumpAll) {
             pw.println();
             for (BroadcastQueue queue : mBroadcastQueues) {
-                pw.println("  mBroadcastsScheduled [" + queue.mQueueName + "]="
-                        + queue.mBroadcastsScheduled);
+                pw.println("  Queue " + queue.toString() + ": " + queue.describeState());
             }
             pw.println("  mHandler:");
             mHandler.dump(new PrintWriterPrinter(pw), "    ");
@@ -13079,17 +13081,23 @@
     }
 
     boolean isPendingBroadcastProcessLocked(int pid) {
-        return mFgBroadcastQueue.isPendingBroadcastProcessLocked(pid)
-                || mBgBroadcastQueue.isPendingBroadcastProcessLocked(pid)
-                || mBgOffloadBroadcastQueue.isPendingBroadcastProcessLocked(pid)
-                || mFgOffloadBroadcastQueue.isPendingBroadcastProcessLocked(pid);
+        for (BroadcastQueue queue : mBroadcastQueues) {
+            BroadcastRecord r = queue.getPendingBroadcastLocked();
+            if (r != null && r.curApp.getPid() == pid) {
+                return true;
+            }
+        }
+        return false;
     }
 
     boolean isPendingBroadcastProcessLocked(ProcessRecord app) {
-        return mFgBroadcastQueue.isPendingBroadcastProcessLocked(app)
-                || mBgBroadcastQueue.isPendingBroadcastProcessLocked(app)
-                || mBgOffloadBroadcastQueue.isPendingBroadcastProcessLocked(app)
-                || mFgOffloadBroadcastQueue.isPendingBroadcastProcessLocked(app);
+        for (BroadcastQueue queue : mBroadcastQueues) {
+            BroadcastRecord r = queue.getPendingBroadcastLocked();
+            if (r != null && r.curApp == app) {
+                return true;
+            }
+        }
+        return false;
     }
 
     void skipPendingBroadcastLocked(int pid) {
@@ -13111,7 +13119,6 @@
     void updateUidReadyForBootCompletedBroadcastLocked(int uid) {
         for (BroadcastQueue queue : mBroadcastQueues) {
             queue.updateUidReadyForBootCompletedBroadcastLocked(uid);
-            queue.scheduleBroadcastsLocked();
         }
     }
 
@@ -13361,8 +13368,7 @@
                             receivers, null, 0, null, null, false, true, true, -1, false, null,
                             false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
                             null /* filterExtrasForReceiver */);
-                    queue.enqueueParallelBroadcastLocked(r);
-                    queue.scheduleBroadcastsLocked();
+                    queue.enqueueBroadcastLocked(r);
                 }
             }
 
@@ -14240,13 +14246,7 @@
                     sticky, false, userId, allowBackgroundActivityStarts,
                     backgroundActivityStartsToken, timeoutExempt, filterExtrasForReceiver);
             if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);
-            final boolean replaced = replacePending
-                    && (queue.replaceParallelBroadcastLocked(r) != null);
-            // Note: We assume resultTo is null for non-ordered broadcasts.
-            if (!replaced) {
-                queue.enqueueParallelBroadcastLocked(r);
-                queue.scheduleBroadcastsLocked();
-            }
+            queue.enqueueBroadcastLocked(r);
             registeredReceivers = null;
             NR = 0;
         }
@@ -14339,31 +14339,7 @@
                     backgroundActivityStartsToken, timeoutExempt, filterExtrasForReceiver);
 
             if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
-
-            final BroadcastRecord oldRecord =
-                    replacePending ? queue.replaceOrderedBroadcastLocked(r) : null;
-            if (oldRecord != null) {
-                // Replaced, fire the result-to receiver.
-                if (oldRecord.resultTo != null) {
-                    final BroadcastQueue oldQueue = broadcastQueueForIntent(oldRecord.intent);
-                    try {
-                        oldRecord.mIsReceiverAppRunning = true;
-                        oldQueue.performReceiveLocked(oldRecord.callerApp, oldRecord.resultTo,
-                                oldRecord.intent,
-                                Activity.RESULT_CANCELED, null, null,
-                                false, false, oldRecord.userId, oldRecord.callingUid, callingUid,
-                                SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0);
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "Failure ["
-                                + queue.mQueueName + "] sending broadcast result of "
-                                + intent, e);
-
-                    }
-                }
-            } else {
-                queue.enqueueOrderedBroadcastLocked(r);
-                queue.scheduleBroadcastsLocked();
-            }
+            queue.enqueueBroadcastLocked(r);
         } else {
             // There was nobody interested in the broadcast, but we still want to record
             // that it happened.
@@ -15183,7 +15159,7 @@
 
         // It's not the current receiver, but it might be starting up to become one
         for (BroadcastQueue queue : mBroadcastQueues) {
-            final BroadcastRecord r = queue.mPendingBroadcast;
+            final BroadcastRecord r = queue.getPendingBroadcastLocked();
             if (r != null && r.curApp == app) {
                 // found it; report which queue it's in
                 receivingQueues.add(queue);
@@ -15298,7 +15274,7 @@
     @GuardedBy("this")
     final boolean canGcNowLocked() {
         for (BroadcastQueue q : mBroadcastQueues) {
-            if (!q.mParallelBroadcasts.isEmpty() || !q.mDispatcher.isIdle()) {
+            if (!q.isIdle()) {
                 return false;
             }
         }
@@ -17887,7 +17863,7 @@
                             pw.flush();
                         }
                         Slog.v(TAG, msg);
-                        queue.cancelDeferrals();
+                        queue.flush();
                         idle = false;
                     }
                 }
@@ -18414,11 +18390,11 @@
                         attributionSource.getUid()), Process.SHELL_UID);
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    return superImpl.apply(code, new AttributionSource(shellUid, /*pid*/ -1,
+                    return superImpl.apply(code, new AttributionSource(shellUid,
                             "com.android.shell", attributionSource.getAttributionTag(),
-                            attributionSource.getToken(), /*renouncedPermissions*/ null,
-                            attributionSource.getNext()), shouldCollectAsyncNotedOp, message,
-                            shouldCollectMessage, skiProxyOperation);
+                            attributionSource.getToken(), attributionSource.getNext()),
+                            shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+                            skiProxyOperation);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
@@ -18465,13 +18441,12 @@
                         attributionSource.getUid()), Process.SHELL_UID);
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    return superImpl.apply(code, new AttributionSource(shellUid, /*pid*/ -1,
+                    return superImpl.apply(code, new AttributionSource(shellUid,
                             "com.android.shell", attributionSource.getAttributionTag(),
-                            attributionSource.getToken(), /*renouncedPermissions*/ null,
-                            attributionSource.getNext()), startIfModeDefault,
-                            shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                            skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags,
-                            attributionChainId);
+                            attributionSource.getToken(), attributionSource.getNext()),
+                            startIfModeDefault, shouldCollectAsyncNotedOp, message,
+                            shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+                            proxiedAttributionFlags, attributionChainId);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
@@ -18490,10 +18465,10 @@
                         attributionSource.getUid()), Process.SHELL_UID);
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    superImpl.apply(code, new AttributionSource(shellUid, /*pid*/ -1,
+                    superImpl.apply(code, new AttributionSource(shellUid,
                             "com.android.shell", attributionSource.getAttributionTag(),
-                            attributionSource.getToken(), /*renouncedPermissions*/ null,
-                            attributionSource.getNext()), skipProxyOperation);
+                            attributionSource.getToken(), attributionSource.getNext()),
+                            skipProxyOperation);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java
index 49477ad..e9a36e0 100644
--- a/services/core/java/com/android/server/am/BroadcastDispatcher.java
+++ b/services/core/java/com/android/server/am/BroadcastDispatcher.java
@@ -162,7 +162,7 @@
     }
 
     private final Object mLock;
-    private final BroadcastQueue mQueue;
+    private final BroadcastQueueImpl mQueue;
     private final BroadcastConstants mConstants;
     private final Handler mHandler;
     private AlarmManagerInternal mAlarm;
@@ -489,7 +489,7 @@
     /**
      * Constructed & sharing a lock with its associated BroadcastQueue instance
      */
-    public BroadcastDispatcher(BroadcastQueue queue, BroadcastConstants constants,
+    public BroadcastDispatcher(BroadcastQueueImpl queue, BroadcastConstants constants,
             Handler handler, Object lock) {
         mQueue = queue;
         mConstants = constants;
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 31d9f96..d0946be 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,236 +16,40 @@
 
 package com.android.server.am;
 
-import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
-import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
-import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
-import static android.text.TextUtils.formatSimple;
-
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED;
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
-
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.AppGlobals;
-import android.app.AppOpsManager;
-import android.app.BroadcastOptions;
-import android.app.IApplicationThread;
-import android.app.PendingIntent;
-import android.app.RemoteServiceException.CannotDeliverBroadcastException;
-import android.app.usage.UsageEvents.Event;
-import android.app.usage.UsageStatsManagerInternal;
-import android.content.ComponentName;
+import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
 import android.content.Intent;
-import android.content.IntentSender;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PermissionInfo;
-import android.content.pm.ResolveInfo;
-import android.content.pm.UserInfo;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerExemptionManager.ReasonCode;
-import android.os.PowerExemptionManager.TempAllowListType;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.permission.IPermissionManager;
-import android.text.TextUtils;
-import android.util.EventLog;
-import android.util.Slog;
-import android.util.SparseIntArray;
-import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.internal.os.TimeoutRecord;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
 import java.util.Set;
 
 /**
- * BROADCASTS
- *
- * We keep three broadcast queues and associated bookkeeping, one for those at
- * foreground priority, and one for normal (background-priority) broadcasts, and one to
- * offload special broadcasts that we know take a long time, such as BOOT_COMPLETED.
+ * Queue of broadcast intents and associated bookkeeping.
  */
-public final class BroadcastQueue {
-    private static final String TAG = "BroadcastQueue";
-    private static final String TAG_MU = TAG + POSTFIX_MU;
-    private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
-
-    static final int MAX_BROADCAST_HISTORY = ActivityManager.isLowRamDeviceStatic() ? 10 : 50;
-    static final int MAX_BROADCAST_SUMMARY_HISTORY
-            = ActivityManager.isLowRamDeviceStatic() ? 25 : 300;
+public abstract class BroadcastQueue {
+    public static final String TAG = "BroadcastQueue";
 
     final ActivityManagerService mService;
-
-    /**
-     * Behavioral parameters such as timeouts and deferral policy, tracking Settings
-     * for runtime configurability
-     */
+    final Handler mHandler;
     final BroadcastConstants mConstants;
-
-    /**
-     * Recognizable moniker for this queue
-     */
+    final BroadcastSkipPolicy mSkipPolicy;
     final String mQueueName;
 
-    /**
-     * If true, we can delay broadcasts while waiting services to finish in the previous
-     * receiver's process.
-     */
-    final boolean mDelayBehindServices;
-
-    /**
-     * Lists of all active broadcasts that are to be executed immediately
-     * (without waiting for another broadcast to finish).  Currently this only
-     * contains broadcasts to registered receivers, to avoid spinning up
-     * a bunch of processes to execute IntentReceiver components.  Background-
-     * and foreground-priority broadcasts are queued separately.
-     */
-    final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>();
-
-    /**
-     * Tracking of the ordered broadcast queue, including deferral policy and alarm
-     * prioritization.
-     */
-    final BroadcastDispatcher mDispatcher;
-
-    /**
-     * Refcounting for completion callbacks of split/deferred broadcasts.  The key
-     * is an opaque integer token assigned lazily when a broadcast is first split
-     * into multiple BroadcastRecord objects.
-     */
-    final SparseIntArray mSplitRefcounts = new SparseIntArray();
-    private int mNextToken = 0;
-
-    /**
-     * Historical data of past broadcasts, for debugging.  This is a ring buffer
-     * whose last element is at mHistoryNext.
-     */
-    final BroadcastRecord[] mBroadcastHistory = new BroadcastRecord[MAX_BROADCAST_HISTORY];
-    int mHistoryNext = 0;
-
-    /**
-     * Summary of historical data of past broadcasts, for debugging.  This is a
-     * ring buffer whose last element is at mSummaryHistoryNext.
-     */
-    final Intent[] mBroadcastSummaryHistory = new Intent[MAX_BROADCAST_SUMMARY_HISTORY];
-    int mSummaryHistoryNext = 0;
-
-    /**
-     * Various milestone timestamps of entries in the mBroadcastSummaryHistory ring
-     * buffer, also tracked via the mSummaryHistoryNext index.  These are all in wall
-     * clock time, not elapsed.
-     */
-    final long[] mSummaryHistoryEnqueueTime = new  long[MAX_BROADCAST_SUMMARY_HISTORY];
-    final long[] mSummaryHistoryDispatchTime = new  long[MAX_BROADCAST_SUMMARY_HISTORY];
-    final long[] mSummaryHistoryFinishTime = new  long[MAX_BROADCAST_SUMMARY_HISTORY];
-
-    /**
-     * Set when we current have a BROADCAST_INTENT_MSG in flight.
-     */
-    boolean mBroadcastsScheduled = false;
-
-    /**
-     * True if we have a pending unexpired BROADCAST_TIMEOUT_MSG posted to our handler.
-     */
-    boolean mPendingBroadcastTimeoutMessage;
-
-    /**
-     * Intent broadcasts that we have tried to start, but are
-     * waiting for the application's process to be created.  We only
-     * need one per scheduling class (instead of a list) because we always
-     * process broadcasts one at a time, so no others can be started while
-     * waiting for this one.
-     */
-    BroadcastRecord mPendingBroadcast = null;
-
-    /**
-     * The receiver index that is pending, to restart the broadcast if needed.
-     */
-    int mPendingBroadcastRecvIndex;
-
-    static final int BROADCAST_INTENT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG;
-    static final int BROADCAST_TIMEOUT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG + 1;
-
-    // log latency metrics for ordered broadcasts during BOOT_COMPLETED processing
-    boolean mLogLatencyMetrics = true;
-
-    final BroadcastHandler mHandler;
-
-    private final class BroadcastHandler extends Handler {
-        public BroadcastHandler(Looper looper) {
-            super(looper, null, true);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case BROADCAST_INTENT_MSG: {
-                    if (DEBUG_BROADCAST) Slog.v(
-                            TAG_BROADCAST, "Received BROADCAST_INTENT_MSG ["
-                            + mQueueName + "]");
-                    processNextBroadcast(true);
-                } break;
-                case BROADCAST_TIMEOUT_MSG: {
-                    synchronized (mService) {
-                        broadcastTimeoutLocked(true);
-                    }
-                } break;
-            }
-        }
-    }
-
     BroadcastQueue(ActivityManagerService service, Handler handler,
-            String name, BroadcastConstants constants, boolean allowDelayBehindServices) {
+            String name, BroadcastConstants constants) {
         mService = service;
-        mHandler = new BroadcastHandler(handler.getLooper());
+        mHandler = handler;
         mQueueName = name;
-        mDelayBehindServices = allowDelayBehindServices;
-
         mConstants = constants;
-        mDispatcher = new BroadcastDispatcher(this, mConstants, mHandler, mService);
+        mSkipPolicy = new BroadcastSkipPolicy(service);
     }
 
     void start(ContentResolver resolver) {
-        mDispatcher.start();
         mConstants.startObserving(mHandler, resolver);
     }
 
@@ -254,2322 +58,78 @@
         return mQueueName;
     }
 
-    public boolean isPendingBroadcastProcessLocked(int pid) {
-        return mPendingBroadcast != null && mPendingBroadcast.curApp.getPid() == pid;
-    }
+    public abstract boolean isDelayBehindServices();
 
-    boolean isPendingBroadcastProcessLocked(ProcessRecord app) {
-        return mPendingBroadcast != null && mPendingBroadcast.curApp == app;
-    }
+    public abstract BroadcastRecord getPendingBroadcastLocked();
 
-    public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
-        r.enqueueClockTime = System.currentTimeMillis();
-        r.enqueueTime = SystemClock.uptimeMillis();
-        r.enqueueRealTime = SystemClock.elapsedRealtime();
-        mParallelBroadcasts.add(r);
-        enqueueBroadcastHelper(r);
-    }
-
-    public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
-        r.enqueueClockTime = System.currentTimeMillis();
-        r.enqueueTime = SystemClock.uptimeMillis();
-        r.enqueueRealTime = SystemClock.elapsedRealtime();
-        mDispatcher.enqueueOrderedBroadcastLocked(r);
-        enqueueBroadcastHelper(r);
-    }
+    public abstract BroadcastRecord getActiveBroadcastLocked();
 
     /**
-     * Don't call this method directly; call enqueueParallelBroadcastLocked or
-     * enqueueOrderedBroadcastLocked.
+     * Enqueue the given broadcast to be eventually dispatched.
+     * <p>
+     * Callers must populate {@link BroadcastRecord#receivers} with the relevant
+     * targets before invoking this method.
+     * <p>
+     * When {@link Intent#FLAG_RECEIVER_REPLACE_PENDING} is set, this method
+     * internally handles replacement of any matching broadcasts.
      */
-    private void enqueueBroadcastHelper(BroadcastRecord r) {
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
-                System.identityHashCode(r));
-        }
-    }
+    public abstract void enqueueBroadcastLocked(BroadcastRecord r);
+
+    public abstract void updateUidReadyForBootCompletedBroadcastLocked(int uid);
+
+    public abstract boolean sendPendingBroadcastsLocked(ProcessRecord app);
+
+    public abstract void skipPendingBroadcastLocked(int pid);
+
+    public abstract void skipCurrentReceiverLocked(ProcessRecord app);
+
+    public abstract BroadcastRecord getMatchingOrderedReceiver(IBinder receiver);
 
     /**
-     * Find the same intent from queued parallel broadcast, replace with a new one and return
-     * the old one.
+     * Signal delivered back from a {@link BroadcastReceiver} to indicate that
+     * it's finished processing the current broadcast being dispatched to it.
+     * <p>
+     * If this signal isn't delivered back in a timely fashion, we assume the
+     * receiver has somehow wedged and we trigger an ANR.
      */
-    public final BroadcastRecord replaceParallelBroadcastLocked(BroadcastRecord r) {
-        return replaceBroadcastLocked(mParallelBroadcasts, r, "PARALLEL");
-    }
+    public abstract boolean finishReceiverLocked(BroadcastRecord r, int resultCode,
+            String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices);
+
+    public abstract void backgroundServicesFinishedLocked(int userId);
+
+    public abstract void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj);
 
     /**
-     * Find the same intent from queued ordered broadcast, replace with a new one and return
-     * the old one.
+     * Signal from OS internals that the given package (or some subset of that
+     * package) has been disabled or uninstalled, and that any pending
+     * broadcasts should be cleaned up.
      */
-    public final BroadcastRecord replaceOrderedBroadcastLocked(BroadcastRecord r) {
-        return mDispatcher.replaceBroadcastLocked(r, "ORDERED");
-    }
-
-    private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> queue,
-            BroadcastRecord r, String typeForLogging) {
-        final Intent intent = r.intent;
-        for (int i = queue.size() - 1; i > 0; i--) {
-            final BroadcastRecord old = queue.get(i);
-            if (old.userId == r.userId && intent.filterEquals(old.intent)) {
-                if (DEBUG_BROADCAST) {
-                    Slog.v(TAG_BROADCAST, "***** DROPPING "
-                            + typeForLogging + " [" + mQueueName + "]: " + intent);
-                }
-                queue.set(i, r);
-                return old;
-            }
-        }
-        return null;
-    }
-
-    private final void processCurBroadcastLocked(BroadcastRecord r,
-            ProcessRecord app) throws RemoteException {
-        if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                "Process cur broadcast " + r + " for app " + app);
-        final IApplicationThread thread = app.getThread();
-        if (thread == null) {
-            throw new RemoteException();
-        }
-        if (app.isInFullBackup()) {
-            skipReceiverLocked(r);
-            return;
-        }
-
-        r.receiver = thread.asBinder();
-        r.curApp = app;
-        final ProcessReceiverRecord prr = app.mReceivers;
-        prr.addCurReceiver(r);
-        app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
-        // Don't bump its LRU position if it's in the background restricted.
-        if (mService.mInternal.getRestrictionLevel(app.info.packageName, app.userId)
-                < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
-            mService.updateLruProcessLocked(app, false, null);
-        }
-        // Make sure the oom adj score is updated before delivering the broadcast.
-        // Force an update, even if there are other pending requests, overall it still saves time,
-        // because time(updateOomAdj(N apps)) <= N * time(updateOomAdj(1 app)).
-        mService.enqueueOomAdjTargetLocked(app);
-        mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
-
-        // Tell the application to launch this receiver.
-        maybeReportBroadcastDispatchedEventLocked(r, r.curReceiver.applicationInfo.uid);
-        r.intent.setComponent(r.curComponent);
-
-        boolean started = false;
-        try {
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
-                    "Delivering to component " + r.curComponent
-                    + ": " + r);
-            mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
-                                      PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
-            thread.scheduleReceiver(prepareReceiverIntent(r.intent, r.curFilteredExtras),
-                    r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
-                    r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
-                    app.mState.getReportedProcState());
-            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                    "Process cur broadcast " + r + " DELIVERED for app " + app);
-            started = true;
-        } finally {
-            if (!started) {
-                if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                        "Process cur broadcast " + r + ": NOT STARTED!");
-                r.receiver = null;
-                r.curApp = null;
-                prr.removeCurReceiver(r);
-            }
-        }
-
-        // if something bad happens here, launch the app and try again
-        if (app.isKilled()) {
-            throw new RemoteException("app gets killed during broadcasting");
-        }
-    }
+    public abstract boolean cleanupDisabledPackageReceiversLocked(
+            String packageName, Set<String> filterByClasses, int userId, boolean doit);
 
     /**
-     * Called by ActivityManagerService to notify that the uid has process started, if there is any
-     * deferred BOOT_COMPLETED broadcast, the BroadcastDispatcher can dispatch the broadcast now.
-     * @param uid
+     * Quickly determine if this queue has broadcasts that are still waiting to
+     * be delivered at some point in the future.
+     *
+     * @see #flush()
      */
-    public void updateUidReadyForBootCompletedBroadcastLocked(int uid) {
-        mDispatcher.updateUidReadyForBootCompletedBroadcastLocked(uid);
-    }
-
-    public boolean sendPendingBroadcastsLocked(ProcessRecord app) {
-        boolean didSomething = false;
-        final BroadcastRecord br = mPendingBroadcast;
-        if (br != null && br.curApp.getPid() > 0 && br.curApp.getPid() == app.getPid()) {
-            if (br.curApp != app) {
-                Slog.e(TAG, "App mismatch when sending pending broadcast to "
-                        + app.processName + ", intended target is " + br.curApp.processName);
-                return false;
-            }
-            try {
-                mPendingBroadcast = null;
-                br.mIsReceiverAppRunning = false;
-                processCurBroadcastLocked(br, app);
-                didSomething = true;
-            } catch (Exception e) {
-                Slog.w(TAG, "Exception in new application when starting receiver "
-                        + br.curComponent.flattenToShortString(), e);
-                logBroadcastReceiverDiscardLocked(br);
-                finishReceiverLocked(br, br.resultCode, br.resultData,
-                        br.resultExtras, br.resultAbort, false);
-                scheduleBroadcastsLocked();
-                // We need to reset the state if we failed to start the receiver.
-                br.state = BroadcastRecord.IDLE;
-                throw new RuntimeException(e.getMessage());
-            }
-        }
-        return didSomething;
-    }
-
-    public void skipPendingBroadcastLocked(int pid) {
-        final BroadcastRecord br = mPendingBroadcast;
-        if (br != null && br.curApp.getPid() == pid) {
-            br.state = BroadcastRecord.IDLE;
-            br.nextReceiver = mPendingBroadcastRecvIndex;
-            mPendingBroadcast = null;
-            scheduleBroadcastsLocked();
-        }
-    }
-
-    // Skip the current receiver, if any, that is in flight to the given process
-    public void skipCurrentReceiverLocked(ProcessRecord app) {
-        BroadcastRecord r = null;
-        final BroadcastRecord curActive = mDispatcher.getActiveBroadcastLocked();
-        if (curActive != null && curActive.curApp == app) {
-            // confirmed: the current active broadcast is to the given app
-            r = curActive;
-        }
-
-        // If the current active broadcast isn't this BUT we're waiting for
-        // mPendingBroadcast to spin up the target app, that's what we use.
-        if (r == null && mPendingBroadcast != null && mPendingBroadcast.curApp == app) {
-            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
-                    "[" + mQueueName + "] skip & discard pending app " + r);
-            r = mPendingBroadcast;
-        }
-
-        if (r != null) {
-            skipReceiverLocked(r);
-        }
-    }
-
-    private void skipReceiverLocked(BroadcastRecord r) {
-        logBroadcastReceiverDiscardLocked(r);
-        finishReceiverLocked(r, r.resultCode, r.resultData,
-                r.resultExtras, r.resultAbort, false);
-        scheduleBroadcastsLocked();
-    }
-
-    public void scheduleBroadcastsLocked() {
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["
-                + mQueueName + "]: current="
-                + mBroadcastsScheduled);
-
-        if (mBroadcastsScheduled) {
-            return;
-        }
-        mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
-        mBroadcastsScheduled = true;
-    }
-
-    public BroadcastRecord getMatchingOrderedReceiver(IBinder receiver) {
-        BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
-        if (br != null && br.receiver == receiver) {
-            return br;
-        }
-        return null;
-    }
-
-    // > 0 only, no worry about "eventual" recycling
-    private int nextSplitTokenLocked() {
-        int next = mNextToken + 1;
-        if (next <= 0) {
-            next = 1;
-        }
-        mNextToken = next;
-        return next;
-    }
-
-    private void postActivityStartTokenRemoval(ProcessRecord app, BroadcastRecord r) {
-        // the receiver had run for less than allowed bg activity start timeout,
-        // so allow the process to still start activities from bg for some more time
-        String msgToken = (app.toShortString() + r.toString()).intern();
-        // first, if there exists a past scheduled request to remove this token, drop
-        // that request - we don't want the token to be swept from under our feet...
-        mHandler.removeCallbacksAndMessages(msgToken);
-        // ...then schedule the removal of the token after the extended timeout
-        mHandler.postAtTime(() -> {
-            synchronized (mService) {
-                app.removeAllowBackgroundActivityStartsToken(r);
-            }
-        }, msgToken, (r.receiverTime + mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT));
-    }
-
-    public boolean finishReceiverLocked(BroadcastRecord r, int resultCode,
-            String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
-        final int state = r.state;
-        final ActivityInfo receiver = r.curReceiver;
-        final long finishTime = SystemClock.uptimeMillis();
-        final long elapsed = finishTime - r.receiverTime;
-        r.state = BroadcastRecord.IDLE;
-        final int curIndex = r.nextReceiver - 1;
-        if (curIndex >= 0 && curIndex < r.receivers.size() && r.curApp != null) {
-            final Object curReceiver = r.receivers.get(curIndex);
-            FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, r.curApp.uid,
-                    r.callingUid == -1 ? Process.SYSTEM_UID : r.callingUid,
-                    ActivityManagerService.getShortAction(r.intent.getAction()),
-                    curReceiver instanceof BroadcastFilter
-                    ? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME
-                    : BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST,
-                    r.mIsReceiverAppRunning
-                    ? BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM
-                    : BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD,
-                    r.dispatchTime - r.enqueueTime,
-                    r.receiverTime - r.dispatchTime,
-                    finishTime - r.receiverTime);
-        }
-        if (state == BroadcastRecord.IDLE) {
-            Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
-        }
-        if (r.allowBackgroundActivityStarts && r.curApp != null) {
-            if (elapsed > mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT) {
-                // if the receiver has run for more than allowed bg activity start timeout,
-                // just remove the token for this process now and we're done
-                r.curApp.removeAllowBackgroundActivityStartsToken(r);
-            } else {
-                // It gets more time; post the removal to happen at the appropriate moment
-                postActivityStartTokenRemoval(r.curApp, r);
-            }
-        }
-        // If we're abandoning this broadcast before any receivers were actually spun up,
-        // nextReceiver is zero; in which case time-to-process bookkeeping doesn't apply.
-        if (r.nextReceiver > 0) {
-            r.duration[r.nextReceiver - 1] = elapsed;
-        }
-
-        // if this receiver was slow, impose deferral policy on the app.  This will kick in
-        // when processNextBroadcastLocked() next finds this uid as a receiver identity.
-        if (!r.timeoutExempt) {
-            // r.curApp can be null if finish has raced with process death - benign
-            // edge case, and we just ignore it because we're already cleaning up
-            // as expected.
-            if (r.curApp != null
-                    && mConstants.SLOW_TIME > 0 && elapsed > mConstants.SLOW_TIME) {
-                // Core system packages are exempt from deferral policy
-                if (!UserHandle.isCore(r.curApp.uid)) {
-                    if (DEBUG_BROADCAST_DEFERRAL) {
-                        Slog.i(TAG_BROADCAST, "Broadcast receiver " + (r.nextReceiver - 1)
-                                + " was slow: " + receiver + " br=" + r);
-                    }
-                    mDispatcher.startDeferring(r.curApp.uid);
-                } else {
-                    if (DEBUG_BROADCAST_DEFERRAL) {
-                        Slog.i(TAG_BROADCAST, "Core uid " + r.curApp.uid
-                                + " receiver was slow but not deferring: "
-                                + receiver + " br=" + r);
-                    }
-                }
-            }
-        } else {
-            if (DEBUG_BROADCAST_DEFERRAL) {
-                Slog.i(TAG_BROADCAST, "Finished broadcast " + r.intent.getAction()
-                        + " is exempt from deferral policy");
-            }
-        }
-
-        r.receiver = null;
-        r.intent.setComponent(null);
-        if (r.curApp != null && r.curApp.mReceivers.hasCurReceiver(r)) {
-            r.curApp.mReceivers.removeCurReceiver(r);
-            mService.enqueueOomAdjTargetLocked(r.curApp);
-        }
-        if (r.curFilter != null) {
-            r.curFilter.receiverList.curBroadcast = null;
-        }
-        r.curFilter = null;
-        r.curReceiver = null;
-        r.curApp = null;
-        r.curFilteredExtras = null;
-        mPendingBroadcast = null;
-
-        r.resultCode = resultCode;
-        r.resultData = resultData;
-        r.resultExtras = resultExtras;
-        if (resultAbort && (r.intent.getFlags()&Intent.FLAG_RECEIVER_NO_ABORT) == 0) {
-            r.resultAbort = resultAbort;
-        } else {
-            r.resultAbort = false;
-        }
-
-        // If we want to wait behind services *AND* we're finishing the head/
-        // active broadcast on its queue
-        if (waitForServices && r.curComponent != null && r.queue.mDelayBehindServices
-                && r.queue.mDispatcher.getActiveBroadcastLocked() == r) {
-            ActivityInfo nextReceiver;
-            if (r.nextReceiver < r.receivers.size()) {
-                Object obj = r.receivers.get(r.nextReceiver);
-                nextReceiver = (obj instanceof ActivityInfo) ? (ActivityInfo)obj : null;
-            } else {
-                nextReceiver = null;
-            }
-            // Don't do this if the next receive is in the same process as the current one.
-            if (receiver == null || nextReceiver == null
-                    || receiver.applicationInfo.uid != nextReceiver.applicationInfo.uid
-                    || !receiver.processName.equals(nextReceiver.processName)) {
-                // In this case, we are ready to process the next receiver for the current broadcast,
-                // but are on a queue that would like to wait for services to finish before moving
-                // on.  If there are background services currently starting, then we will go into a
-                // special state where we hold off on continuing this broadcast until they are done.
-                if (mService.mServices.hasBackgroundServicesLocked(r.userId)) {
-                    Slog.i(TAG, "Delay finish: " + r.curComponent.flattenToShortString());
-                    r.state = BroadcastRecord.WAITING_SERVICES;
-                    return false;
-                }
-            }
-        }
-
-        r.curComponent = null;
-
-        // We will process the next receiver right now if this is finishing
-        // an app receiver (which is always asynchronous) or after we have
-        // come back from calling a receiver.
-        return state == BroadcastRecord.APP_RECEIVE
-                || state == BroadcastRecord.CALL_DONE_RECEIVE;
-    }
-
-    public void backgroundServicesFinishedLocked(int userId) {
-        BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
-        if (br != null) {
-            if (br.userId == userId && br.state == BroadcastRecord.WAITING_SERVICES) {
-                Slog.i(TAG, "Resuming delayed broadcast");
-                br.curComponent = null;
-                br.state = BroadcastRecord.IDLE;
-                processNextBroadcastLocked(false, false);
-            }
-        }
-    }
-
-    void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
-            Intent intent, int resultCode, String data, Bundle extras,
-            boolean ordered, boolean sticky, int sendingUser,
-            int receiverUid, int callingUid, long dispatchDelay,
-            long receiveDelay) throws RemoteException {
-        // Send the intent to the receiver asynchronously using one-way binder calls.
-        if (app != null) {
-            final IApplicationThread thread = app.getThread();
-            if (thread != null) {
-                // If we have an app thread, do the call through that so it is
-                // correctly ordered with other one-way calls.
-                try {
-                    thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
-                            data, extras, ordered, sticky, sendingUser,
-                            app.mState.getReportedProcState());
-                // TODO: Uncomment this when (b/28322359) is fixed and we aren't getting
-                // DeadObjectException when the process isn't actually dead.
-                //} catch (DeadObjectException ex) {
-                // Failed to call into the process.  It's dying so just let it die and move on.
-                //    throw ex;
-                } catch (RemoteException ex) {
-                    // Failed to call into the process. It's either dying or wedged. Kill it gently.
-                    synchronized (mService) {
-                        Slog.w(TAG, "Can't deliver broadcast to " + app.processName
-                                + " (pid " + app.getPid() + "). Crashing it.");
-                        app.scheduleCrashLocked("can't deliver broadcast",
-                                CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null);
-                    }
-                    throw ex;
-                }
-            } else {
-                // Application has died. Receiver doesn't exist.
-                throw new RemoteException("app.thread must not be null");
-            }
-        } else {
-            receiver.performReceive(intent, resultCode, data, extras, ordered,
-                    sticky, sendingUser);
-        }
-        if (!ordered) {
-            FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED,
-                    receiverUid == -1 ? Process.SYSTEM_UID : receiverUid,
-                    callingUid == -1 ? Process.SYSTEM_UID : callingUid,
-                    ActivityManagerService.getShortAction(intent.getAction()),
-                    BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME,
-                    BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
-                    dispatchDelay, receiveDelay, 0 /* finish_delay */);
-        }
-    }
-
-    private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
-            BroadcastFilter filter, boolean ordered, int index) {
-        boolean skip = false;
-        if (r.options != null && !r.options.testRequireCompatChange(filter.owningUid)) {
-            Slog.w(TAG, "Compat change filtered: broadcasting " + r.intent.toString()
-                    + " to uid " + filter.owningUid + " due to compat change "
-                    + r.options.getRequireCompatChangeId());
-            skip = true;
-        }
-        if (!mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
-                filter.packageName, filter.owningUid)) {
-            Slog.w(TAG, "Association not allowed: broadcasting "
-                    + r.intent.toString()
-                    + " from " + r.callerPackage + " (pid=" + r.callingPid
-                    + ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
-                    + filter);
-            skip = true;
-        }
-        if (!skip && !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
-                r.callingPid, r.resolvedType, filter.receiverList.uid)) {
-            Slog.w(TAG, "Firewall blocked: broadcasting "
-                    + r.intent.toString()
-                    + " from " + r.callerPackage + " (pid=" + r.callingPid
-                    + ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
-                    + filter);
-            skip = true;
-        }
-        // Check that the sender has permission to send to this receiver
-        if (filter.requiredPermission != null) {
-            int perm = mService.checkComponentPermission(filter.requiredPermission,
-                    r.callingPid, r.callingUid, -1, true);
-            if (perm != PackageManager.PERMISSION_GRANTED) {
-                Slog.w(TAG, "Permission Denial: broadcasting "
-                        + r.intent.toString()
-                        + " from " + r.callerPackage + " (pid="
-                        + r.callingPid + ", uid=" + r.callingUid + ")"
-                        + " requires " + filter.requiredPermission
-                        + " due to registered receiver " + filter);
-                skip = true;
-            } else {
-                final int opCode = AppOpsManager.permissionToOpCode(filter.requiredPermission);
-                if (opCode != AppOpsManager.OP_NONE
-                        && mService.getAppOpsManager().noteOpNoThrow(opCode, r.callingUid,
-                        r.callerPackage, r.callerFeatureId, "Broadcast sent to protected receiver")
-                        != AppOpsManager.MODE_ALLOWED) {
-                    Slog.w(TAG, "Appop Denial: broadcasting "
-                            + r.intent.toString()
-                            + " from " + r.callerPackage + " (pid="
-                            + r.callingPid + ", uid=" + r.callingUid + ")"
-                            + " requires appop " + AppOpsManager.permissionToOp(
-                                    filter.requiredPermission)
-                            + " due to registered receiver " + filter);
-                    skip = true;
-                }
-            }
-        }
-
-        if (!skip && (filter.receiverList.app == null || filter.receiverList.app.isKilled()
-                || filter.receiverList.app.mErrorState.isCrashing())) {
-            Slog.w(TAG, "Skipping deliver [" + mQueueName + "] " + r
-                    + " to " + filter.receiverList + ": process gone or crashing");
-            skip = true;
-        }
-
-        // Ensure that broadcasts are only sent to other Instant Apps if they are marked as
-        // visible to Instant Apps.
-        final boolean visibleToInstantApps =
-                (r.intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
-
-        if (!skip && !visibleToInstantApps && filter.instantApp
-                && filter.receiverList.uid != r.callingUid) {
-            Slog.w(TAG, "Instant App Denial: receiving "
-                    + r.intent.toString()
-                    + " to " + filter.receiverList.app
-                    + " (pid=" + filter.receiverList.pid
-                    + ", uid=" + filter.receiverList.uid + ")"
-                    + " due to sender " + r.callerPackage
-                    + " (uid " + r.callingUid + ")"
-                    + " not specifying FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS");
-            skip = true;
-        }
-
-        if (!skip && !filter.visibleToInstantApp && r.callerInstantApp
-                && filter.receiverList.uid != r.callingUid) {
-            Slog.w(TAG, "Instant App Denial: receiving "
-                    + r.intent.toString()
-                    + " to " + filter.receiverList.app
-                    + " (pid=" + filter.receiverList.pid
-                    + ", uid=" + filter.receiverList.uid + ")"
-                    + " requires receiver be visible to instant apps"
-                    + " due to sender " + r.callerPackage
-                    + " (uid " + r.callingUid + ")");
-            skip = true;
-        }
-
-        // Check that the receiver has the required permission(s) to receive this broadcast.
-        if (!skip && r.requiredPermissions != null && r.requiredPermissions.length > 0) {
-            for (int i = 0; i < r.requiredPermissions.length; i++) {
-                String requiredPermission = r.requiredPermissions[i];
-                int perm = mService.checkComponentPermission(requiredPermission,
-                        filter.receiverList.pid, filter.receiverList.uid, -1, true);
-                if (perm != PackageManager.PERMISSION_GRANTED) {
-                    Slog.w(TAG, "Permission Denial: receiving "
-                            + r.intent.toString()
-                            + " to " + filter.receiverList.app
-                            + " (pid=" + filter.receiverList.pid
-                            + ", uid=" + filter.receiverList.uid + ")"
-                            + " requires " + requiredPermission
-                            + " due to sender " + r.callerPackage
-                            + " (uid " + r.callingUid + ")");
-                    skip = true;
-                    break;
-                }
-                int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
-                if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp
-                        && mService.getAppOpsManager().noteOpNoThrow(appOp,
-                        filter.receiverList.uid, filter.packageName, filter.featureId,
-                        "Broadcast delivered to registered receiver " + filter.receiverId)
-                        != AppOpsManager.MODE_ALLOWED) {
-                    Slog.w(TAG, "Appop Denial: receiving "
-                            + r.intent.toString()
-                            + " to " + filter.receiverList.app
-                            + " (pid=" + filter.receiverList.pid
-                            + ", uid=" + filter.receiverList.uid + ")"
-                            + " requires appop " + AppOpsManager.permissionToOp(
-                            requiredPermission)
-                            + " due to sender " + r.callerPackage
-                            + " (uid " + r.callingUid + ")");
-                    skip = true;
-                    break;
-                }
-            }
-        }
-        if (!skip && (r.requiredPermissions == null || r.requiredPermissions.length == 0)) {
-            int perm = mService.checkComponentPermission(null,
-                    filter.receiverList.pid, filter.receiverList.uid, -1, true);
-            if (perm != PackageManager.PERMISSION_GRANTED) {
-                Slog.w(TAG, "Permission Denial: security check failed when receiving "
-                        + r.intent.toString()
-                        + " to " + filter.receiverList.app
-                        + " (pid=" + filter.receiverList.pid
-                        + ", uid=" + filter.receiverList.uid + ")"
-                        + " due to sender " + r.callerPackage
-                        + " (uid " + r.callingUid + ")");
-                skip = true;
-            }
-        }
-        // Check that the receiver does *not* have any excluded permissions
-        if (!skip && r.excludedPermissions != null && r.excludedPermissions.length > 0) {
-            for (int i = 0; i < r.excludedPermissions.length; i++) {
-                String excludedPermission = r.excludedPermissions[i];
-                final int perm = mService.checkComponentPermission(excludedPermission,
-                        filter.receiverList.pid, filter.receiverList.uid, -1, true);
-
-                int appOp = AppOpsManager.permissionToOpCode(excludedPermission);
-                if (appOp != AppOpsManager.OP_NONE) {
-                    // When there is an app op associated with the permission,
-                    // skip when both the permission and the app op are
-                    // granted.
-                    if ((perm == PackageManager.PERMISSION_GRANTED) && (
-                            mService.getAppOpsManager().checkOpNoThrow(appOp,
-                                    filter.receiverList.uid,
-                                    filter.packageName)
-                                    == AppOpsManager.MODE_ALLOWED)) {
-                        Slog.w(TAG, "Appop Denial: receiving "
-                                + r.intent.toString()
-                                + " to " + filter.receiverList.app
-                                + " (pid=" + filter.receiverList.pid
-                                + ", uid=" + filter.receiverList.uid + ")"
-                                + " excludes appop " + AppOpsManager.permissionToOp(
-                                excludedPermission)
-                                + " due to sender " + r.callerPackage
-                                + " (uid " + r.callingUid + ")");
-                        skip = true;
-                        break;
-                    }
-                } else {
-                    // When there is no app op associated with the permission,
-                    // skip when permission is granted.
-                    if (perm == PackageManager.PERMISSION_GRANTED) {
-                        Slog.w(TAG, "Permission Denial: receiving "
-                                + r.intent.toString()
-                                + " to " + filter.receiverList.app
-                                + " (pid=" + filter.receiverList.pid
-                                + ", uid=" + filter.receiverList.uid + ")"
-                                + " excludes " + excludedPermission
-                                + " due to sender " + r.callerPackage
-                                + " (uid " + r.callingUid + ")");
-                        skip = true;
-                        break;
-                    }
-                }
-            }
-        }
-
-        // Check that the receiver does *not* belong to any of the excluded packages
-        if (!skip && r.excludedPackages != null && r.excludedPackages.length > 0) {
-            if (ArrayUtils.contains(r.excludedPackages, filter.packageName)) {
-                Slog.w(TAG, "Skipping delivery of excluded package "
-                        + r.intent.toString()
-                        + " to " + filter.receiverList.app
-                        + " (pid=" + filter.receiverList.pid
-                        + ", uid=" + filter.receiverList.uid + ")"
-                        + " excludes package " + filter.packageName
-                        + " due to sender " + r.callerPackage
-                        + " (uid " + r.callingUid + ")");
-                skip = true;
-            }
-        }
-
-        // If the broadcast also requires an app op check that as well.
-        if (!skip && r.appOp != AppOpsManager.OP_NONE
-                && mService.getAppOpsManager().noteOpNoThrow(r.appOp,
-                filter.receiverList.uid, filter.packageName, filter.featureId,
-                "Broadcast delivered to registered receiver " + filter.receiverId)
-                != AppOpsManager.MODE_ALLOWED) {
-            Slog.w(TAG, "Appop Denial: receiving "
-                    + r.intent.toString()
-                    + " to " + filter.receiverList.app
-                    + " (pid=" + filter.receiverList.pid
-                    + ", uid=" + filter.receiverList.uid + ")"
-                    + " requires appop " + AppOpsManager.opToName(r.appOp)
-                    + " due to sender " + r.callerPackage
-                    + " (uid " + r.callingUid + ")");
-            skip = true;
-        }
-
-        // Ensure that broadcasts are only sent to other apps if they are explicitly marked as
-        // exported, or are System level broadcasts
-        if (!skip && !filter.exported && mService.checkComponentPermission(null, r.callingPid,
-                r.callingUid, filter.receiverList.uid, filter.exported)
-                != PackageManager.PERMISSION_GRANTED) {
-            Slog.w(TAG, "Exported Denial: sending "
-                    + r.intent.toString()
-                    + ", action: " + r.intent.getAction()
-                    + " from " + r.callerPackage
-                    + " (uid=" + r.callingUid + ")"
-                    + " due to receiver " + filter.receiverList.app
-                    + " (uid " + filter.receiverList.uid + ")"
-                    + " not specifying RECEIVER_EXPORTED");
-            skip = true;
-        }
-
-        // Filter packages in the intent extras, skipping delivery if none of the packages is
-        // visible to the receiver.
-        Bundle filteredExtras = null;
-        if (!skip && r.filterExtrasForReceiver != null) {
-            final Bundle extras = r.intent.getExtras();
-            if (extras != null) {
-                filteredExtras = r.filterExtrasForReceiver.apply(filter.receiverList.uid, extras);
-                if (filteredExtras == null) {
-                    if (DEBUG_BROADCAST) {
-                        Slog.v(TAG, "Skipping delivery to "
-                                + filter.receiverList.app
-                                + " : receiver is filtered by the package visibility");
-                    }
-                    skip = true;
-                }
-            }
-        }
-
-        if (skip) {
-            r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
-            return;
-        }
-
-        // If permissions need a review before any of the app components can run, we drop
-        // the broadcast and if the calling app is in the foreground and the broadcast is
-        // explicit we launch the review UI passing it a pending intent to send the skipped
-        // broadcast.
-        if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName,
-                filter.owningUserId)) {
-            r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
-            return;
-        }
-
-        r.delivery[index] = BroadcastRecord.DELIVERY_DELIVERED;
-
-        // If this is not being sent as an ordered broadcast, then we
-        // don't want to touch the fields that keep track of the current
-        // state of ordered broadcasts.
-        if (ordered) {
-            r.receiver = filter.receiverList.receiver.asBinder();
-            r.curFilter = filter;
-            filter.receiverList.curBroadcast = r;
-            r.state = BroadcastRecord.CALL_IN_RECEIVE;
-            if (filter.receiverList.app != null) {
-                // Bump hosting application to no longer be in background
-                // scheduling class.  Note that we can't do that if there
-                // isn't an app...  but we can only be in that case for
-                // things that directly call the IActivityManager API, which
-                // are already core system stuff so don't matter for this.
-                r.curApp = filter.receiverList.app;
-                filter.receiverList.app.mReceivers.addCurReceiver(r);
-                mService.enqueueOomAdjTargetLocked(r.curApp);
-                mService.updateOomAdjPendingTargetsLocked(
-                        OOM_ADJ_REASON_START_RECEIVER);
-            }
-        } else if (filter.receiverList.app != null) {
-            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(filter.receiverList.app,
-                    OOM_ADJ_REASON_START_RECEIVER);
-        }
-
-        try {
-            if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
-                    "Delivering to " + filter + " : " + r);
-            if (filter.receiverList.app != null && filter.receiverList.app.isInFullBackup()) {
-                // Skip delivery if full backup in progress
-                // If it's an ordered broadcast, we need to continue to the next receiver.
-                if (ordered) {
-                    skipReceiverLocked(r);
-                }
-            } else {
-                r.receiverTime = SystemClock.uptimeMillis();
-                maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
-                maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
-                maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
-                performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
-                        prepareReceiverIntent(r.intent, filteredExtras), r.resultCode, r.resultData,
-                        r.resultExtras, r.ordered, r.initialSticky, r.userId,
-                        filter.receiverList.uid, r.callingUid,
-                        r.dispatchTime - r.enqueueTime,
-                        r.receiverTime - r.dispatchTime);
-                // parallel broadcasts are fire-and-forget, not bookended by a call to
-                // finishReceiverLocked(), so we manage their activity-start token here
-                if (filter.receiverList.app != null
-                        && r.allowBackgroundActivityStarts && !r.ordered) {
-                    postActivityStartTokenRemoval(filter.receiverList.app, r);
-                }
-            }
-            if (ordered) {
-                r.state = BroadcastRecord.CALL_DONE_RECEIVE;
-            }
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Failure sending broadcast " + r.intent, e);
-            // Clean up ProcessRecord state related to this broadcast attempt
-            if (filter.receiverList.app != null) {
-                filter.receiverList.app.removeAllowBackgroundActivityStartsToken(r);
-                if (ordered) {
-                    filter.receiverList.app.mReceivers.removeCurReceiver(r);
-                    // Something wrong, its oom adj could be downgraded, but not in a hurry.
-                    mService.enqueueOomAdjTargetLocked(r.curApp);
-                }
-            }
-            // And BroadcastRecord state related to ordered delivery, if appropriate
-            if (ordered) {
-                r.receiver = null;
-                r.curFilter = null;
-                filter.receiverList.curBroadcast = null;
-            }
-        }
-    }
-
-    private boolean requestStartTargetPermissionsReviewIfNeededLocked(
-            BroadcastRecord receiverRecord, String receivingPackageName,
-            final int receivingUserId) {
-        if (!mService.getPackageManagerInternal().isPermissionsReviewRequired(
-                receivingPackageName, receivingUserId)) {
-            return true;
-        }
-
-        final boolean callerForeground = receiverRecord.callerApp != null
-                ? receiverRecord.callerApp.mState.getSetSchedGroup()
-                != ProcessList.SCHED_GROUP_BACKGROUND : true;
-
-        // Show a permission review UI only for explicit broadcast from a foreground app
-        if (callerForeground && receiverRecord.intent.getComponent() != null) {
-            IIntentSender target = mService.mPendingIntentController.getIntentSender(
-                    ActivityManager.INTENT_SENDER_BROADCAST, receiverRecord.callerPackage,
-                    receiverRecord.callerFeatureId, receiverRecord.callingUid,
-                    receiverRecord.userId, null, null, 0,
-                    new Intent[]{receiverRecord.intent},
-                    new String[]{receiverRecord.intent.resolveType(mService.mContext
-                            .getContentResolver())},
-                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
-                            | PendingIntent.FLAG_IMMUTABLE, null);
-
-            final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                    | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
-                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            intent.putExtra(Intent.EXTRA_PACKAGE_NAME, receivingPackageName);
-            intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
-
-            if (DEBUG_PERMISSIONS_REVIEW) {
-                Slog.i(TAG, "u" + receivingUserId + " Launching permission review for package "
-                        + receivingPackageName);
-            }
-
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mService.mContext.startActivityAsUser(intent, new UserHandle(receivingUserId));
-                }
-            });
-        } else {
-            Slog.w(TAG, "u" + receivingUserId + " Receiving a broadcast in package"
-                    + receivingPackageName + " requires a permissions review");
-        }
-
-        return false;
-    }
-
-    void maybeScheduleTempAllowlistLocked(int uid, BroadcastRecord r,
-            @Nullable BroadcastOptions brOptions) {
-        if (brOptions == null || brOptions.getTemporaryAppAllowlistDuration() <= 0) {
-            return;
-        }
-        long duration = brOptions.getTemporaryAppAllowlistDuration();
-        final @TempAllowListType int type = brOptions.getTemporaryAppAllowlistType();
-        final @ReasonCode int reasonCode = brOptions.getTemporaryAppAllowlistReasonCode();
-        final String reason = brOptions.getTemporaryAppAllowlistReason();
-
-        if (duration > Integer.MAX_VALUE) {
-            duration = Integer.MAX_VALUE;
-        }
-        // XXX ideally we should pause the broadcast until everything behind this is done,
-        // or else we will likely start dispatching the broadcast before we have opened
-        // access to the app (there is a lot of asynchronicity behind this).  It is probably
-        // not that big a deal, however, because the main purpose here is to allow apps
-        // to hold wake locks, and they will be able to acquire their wake lock immediately
-        // it just won't be enabled until we get through this work.
-        StringBuilder b = new StringBuilder();
-        b.append("broadcast:");
-        UserHandle.formatUid(b, r.callingUid);
-        b.append(":");
-        if (r.intent.getAction() != null) {
-            b.append(r.intent.getAction());
-        } else if (r.intent.getComponent() != null) {
-            r.intent.getComponent().appendShortString(b);
-        } else if (r.intent.getData() != null) {
-            b.append(r.intent.getData());
-        }
-        b.append(",reason:");
-        b.append(reason);
-        if (DEBUG_BROADCAST) {
-            Slog.v(TAG, "Broadcast temp allowlist uid=" + uid + " duration=" + duration
-                    + " type=" + type + " : " + b.toString());
-        }
-        mService.tempAllowlistUidLocked(uid, duration, reasonCode, b.toString(), type,
-                r.callingUid);
-    }
+    public abstract boolean isIdle();
 
     /**
-     * Return true if all given permissions are signature-only perms.
+     * Brief summary of internal state, useful for debugging purposes.
      */
-    final boolean isSignaturePerm(String[] perms) {
-        if (perms == null) {
-            return false;
-        }
-        IPermissionManager pm = AppGlobals.getPermissionManager();
-        for (int i = perms.length-1; i >= 0; i--) {
-            try {
-                PermissionInfo pi = pm.getPermissionInfo(perms[i], "android", 0);
-                if (pi == null) {
-                    // a required permission that no package has actually
-                    // defined cannot be signature-required.
-                    return false;
-                }
-                if ((pi.protectionLevel & (PermissionInfo.PROTECTION_MASK_BASE
-                        | PermissionInfo.PROTECTION_FLAG_PRIVILEGED))
-                        != PermissionInfo.PROTECTION_SIGNATURE) {
-                    // If this a signature permission and NOT allowed for privileged apps, it
-                    // is okay...  otherwise, nope!
-                    return false;
-                }
-            } catch (RemoteException e) {
-                return false;
-            }
-        }
-        return true;
-    }
+    public abstract String describeState();
 
-    private void processNextBroadcast(boolean fromMsg) {
-        synchronized (mService) {
-            processNextBroadcastLocked(fromMsg, false);
-        }
-    }
+    /**
+     * Flush any broadcasts still waiting to be delivered, causing them to be
+     * delivered as soon as possible.
+     *
+     * @see #isIdle()
+     */
+    public abstract void flush();
 
-    static String broadcastDescription(BroadcastRecord r, ComponentName component) {
-        return r.intent.toString()
-                + " from " + r.callerPackage + " (pid=" + r.callingPid
-                + ", uid=" + r.callingUid + ") to " + component.flattenToShortString();
-    }
+    public abstract void dumpDebug(ProtoOutputStream proto, long fieldId);
 
-    private static Intent prepareReceiverIntent(@NonNull Intent originalIntent,
-            @Nullable Bundle filteredExtras) {
-        final Intent intent = new Intent(originalIntent);
-        if (filteredExtras != null) {
-            intent.replaceExtras(filteredExtras);
-        }
-        return intent;
-    }
-
-    final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
-        BroadcastRecord r;
-
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "processNextBroadcast ["
-                + mQueueName + "]: "
-                + mParallelBroadcasts.size() + " parallel broadcasts; "
-                + mDispatcher.describeStateLocked());
-
-        mService.updateCpuStats();
-
-        if (fromMsg) {
-            mBroadcastsScheduled = false;
-        }
-
-        // First, deliver any non-serialized broadcasts right away.
-        while (mParallelBroadcasts.size() > 0) {
-            r = mParallelBroadcasts.remove(0);
-            r.dispatchTime = SystemClock.uptimeMillis();
-            r.dispatchRealTime = SystemClock.elapsedRealtime();
-            r.dispatchClockTime = System.currentTimeMillis();
-            r.mIsReceiverAppRunning = true;
-
-            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-                Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
-                    System.identityHashCode(r));
-                Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
-                    System.identityHashCode(r));
-            }
-
-            final int N = r.receivers.size();
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing parallel broadcast ["
-                    + mQueueName + "] " + r);
-            for (int i=0; i<N; i++) {
-                Object target = r.receivers.get(i);
-                if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                        "Delivering non-ordered on [" + mQueueName + "] to registered "
-                        + target + ": " + r);
-                deliverToRegisteredReceiverLocked(r,
-                        (BroadcastFilter) target, false, i);
-            }
-            addBroadcastToHistoryLocked(r);
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Done with parallel broadcast ["
-                    + mQueueName + "] " + r);
-        }
-
-        // Now take care of the next serialized one...
-
-        // If we are waiting for a process to come up to handle the next
-        // broadcast, then do nothing at this point.  Just in case, we
-        // check that the process we're waiting for still exists.
-        if (mPendingBroadcast != null) {
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
-                    "processNextBroadcast [" + mQueueName + "]: waiting for "
-                    + mPendingBroadcast.curApp);
-
-            boolean isDead;
-            if (mPendingBroadcast.curApp.getPid() > 0) {
-                synchronized (mService.mPidsSelfLocked) {
-                    ProcessRecord proc = mService.mPidsSelfLocked.get(
-                            mPendingBroadcast.curApp.getPid());
-                    isDead = proc == null || proc.mErrorState.isCrashing();
-                }
-            } else {
-                final ProcessRecord proc = mService.mProcessList.getProcessNamesLOSP().get(
-                        mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
-                isDead = proc == null || !proc.isPendingStart();
-            }
-            if (!isDead) {
-                // It's still alive, so keep waiting
-                return;
-            } else {
-                Slog.w(TAG, "pending app  ["
-                        + mQueueName + "]" + mPendingBroadcast.curApp
-                        + " died before responding to broadcast");
-                mPendingBroadcast.state = BroadcastRecord.IDLE;
-                mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
-                mPendingBroadcast = null;
-            }
-        }
-
-        boolean looped = false;
-
-        do {
-            final long now = SystemClock.uptimeMillis();
-            r = mDispatcher.getNextBroadcastLocked(now);
-
-            if (r == null) {
-                // No more broadcasts are deliverable right now, so all done!
-                mDispatcher.scheduleDeferralCheckLocked(false);
-                synchronized (mService.mAppProfiler.mProfilerLock) {
-                    mService.mAppProfiler.scheduleAppGcsLPf();
-                }
-                if (looped && !skipOomAdj) {
-                    // If we had finished the last ordered broadcast, then
-                    // make sure all processes have correct oom and sched
-                    // adjustments.
-                    mService.updateOomAdjPendingTargetsLocked(
-                            OOM_ADJ_REASON_START_RECEIVER);
-                }
-
-                // when we have no more ordered broadcast on this queue, stop logging
-                if (mService.mUserController.mBootCompleted && mLogLatencyMetrics) {
-                    mLogLatencyMetrics = false;
-                }
-
-                return;
-            }
-
-            boolean forceReceive = false;
-
-            // Ensure that even if something goes awry with the timeout
-            // detection, we catch "hung" broadcasts here, discard them,
-            // and continue to make progress.
-            //
-            // This is only done if the system is ready so that early-stage receivers
-            // don't get executed with timeouts; and of course other timeout-
-            // exempt broadcasts are ignored.
-            int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
-            if (mService.mProcessesReady && !r.timeoutExempt && r.dispatchTime > 0) {
-                if ((numReceivers > 0) &&
-                        (now > r.dispatchTime + (2 * mConstants.TIMEOUT * numReceivers))) {
-                    Slog.w(TAG, "Hung broadcast ["
-                            + mQueueName + "] discarded after timeout failure:"
-                            + " now=" + now
-                            + " dispatchTime=" + r.dispatchTime
-                            + " startTime=" + r.receiverTime
-                            + " intent=" + r.intent
-                            + " numReceivers=" + numReceivers
-                            + " nextReceiver=" + r.nextReceiver
-                            + " state=" + r.state);
-                    broadcastTimeoutLocked(false); // forcibly finish this broadcast
-                    forceReceive = true;
-                    r.state = BroadcastRecord.IDLE;
-                }
-            }
-
-            if (r.state != BroadcastRecord.IDLE) {
-                if (DEBUG_BROADCAST) Slog.d(TAG_BROADCAST,
-                        "processNextBroadcast("
-                        + mQueueName + ") called when not idle (state="
-                        + r.state + ")");
-                return;
-            }
-
-            // Is the current broadcast is done for any reason?
-            if (r.receivers == null || r.nextReceiver >= numReceivers
-                    || r.resultAbort || forceReceive) {
-                // Send the final result if requested
-                if (r.resultTo != null) {
-                    boolean sendResult = true;
-
-                    // if this was part of a split/deferral complex, update the refcount and only
-                    // send the completion when we clear all of them
-                    if (r.splitToken != 0) {
-                        int newCount = mSplitRefcounts.get(r.splitToken) - 1;
-                        if (newCount == 0) {
-                            // done!  clear out this record's bookkeeping and deliver
-                            if (DEBUG_BROADCAST_DEFERRAL) {
-                                Slog.i(TAG_BROADCAST,
-                                        "Sending broadcast completion for split token "
-                                        + r.splitToken + " : " + r.intent.getAction());
-                            }
-                            mSplitRefcounts.delete(r.splitToken);
-                        } else {
-                            // still have some split broadcast records in flight; update refcount
-                            // and hold off on the callback
-                            if (DEBUG_BROADCAST_DEFERRAL) {
-                                Slog.i(TAG_BROADCAST,
-                                        "Result refcount now " + newCount + " for split token "
-                                        + r.splitToken + " : " + r.intent.getAction()
-                                        + " - not sending completion yet");
-                            }
-                            sendResult = false;
-                            mSplitRefcounts.put(r.splitToken, newCount);
-                        }
-                    }
-                    if (sendResult) {
-                        if (r.callerApp != null) {
-                            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
-                                    r.callerApp, OOM_ADJ_REASON_FINISH_RECEIVER);
-                        }
-                        try {
-                            if (DEBUG_BROADCAST) {
-                                Slog.i(TAG_BROADCAST, "Finishing broadcast [" + mQueueName + "] "
-                                        + r.intent.getAction() + " app=" + r.callerApp);
-                            }
-                            if (r.dispatchTime == 0) {
-                                // The dispatch time here could be 0, in case it's a parallel
-                                // broadcast but it has a result receiver. Set it to now.
-                                r.dispatchTime = now;
-                            }
-                            r.mIsReceiverAppRunning = true;
-                            performReceiveLocked(r.callerApp, r.resultTo,
-                                    new Intent(r.intent), r.resultCode,
-                                    r.resultData, r.resultExtras, false, false, r.userId,
-                                    r.callingUid, r.callingUid,
-                                    r.dispatchTime - r.enqueueTime,
-                                    now - r.dispatchTime);
-                            logBootCompletedBroadcastCompletionLatencyIfPossible(r);
-                            // Set this to null so that the reference
-                            // (local and remote) isn't kept in the mBroadcastHistory.
-                            r.resultTo = null;
-                        } catch (RemoteException e) {
-                            r.resultTo = null;
-                            Slog.w(TAG, "Failure ["
-                                    + mQueueName + "] sending broadcast result of "
-                                    + r.intent, e);
-                        }
-                    }
-                }
-
-                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Cancelling BROADCAST_TIMEOUT_MSG");
-                cancelBroadcastTimeoutLocked();
-
-                if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
-                        "Finished with ordered broadcast " + r);
-
-                // ... and on to the next...
-                addBroadcastToHistoryLocked(r);
-                if (r.intent.getComponent() == null && r.intent.getPackage() == null
-                        && (r.intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
-                    // This was an implicit broadcast... let's record it for posterity.
-                    mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
-                            r.manifestCount, r.manifestSkipCount, r.finishTime-r.dispatchTime);
-                }
-                mDispatcher.retireBroadcastLocked(r);
-                r = null;
-                looped = true;
-                continue;
-            }
-
-            // Check whether the next receiver is under deferral policy, and handle that
-            // accordingly.  If the current broadcast was already part of deferred-delivery
-            // tracking, we know that it must now be deliverable as-is without re-deferral.
-            if (!r.deferred) {
-                final int receiverUid = r.getReceiverUid(r.receivers.get(r.nextReceiver));
-                if (mDispatcher.isDeferringLocked(receiverUid)) {
-                    if (DEBUG_BROADCAST_DEFERRAL) {
-                        Slog.i(TAG_BROADCAST, "Next receiver in " + r + " uid " + receiverUid
-                                + " at " + r.nextReceiver + " is under deferral");
-                    }
-                    // If this is the only (remaining) receiver in the broadcast, "splitting"
-                    // doesn't make sense -- just defer it as-is and retire it as the
-                    // currently active outgoing broadcast.
-                    BroadcastRecord defer;
-                    if (r.nextReceiver + 1 == numReceivers) {
-                        if (DEBUG_BROADCAST_DEFERRAL) {
-                            Slog.i(TAG_BROADCAST, "Sole receiver of " + r
-                                    + " is under deferral; setting aside and proceeding");
-                        }
-                        defer = r;
-                        mDispatcher.retireBroadcastLocked(r);
-                    } else {
-                        // Nontrivial case; split out 'uid's receivers to a new broadcast record
-                        // and defer that, then loop and pick up continuing delivery of the current
-                        // record (now absent those receivers).
-
-                        // The split operation is guaranteed to match at least at 'nextReceiver'
-                        defer = r.splitRecipientsLocked(receiverUid, r.nextReceiver);
-                        if (DEBUG_BROADCAST_DEFERRAL) {
-                            Slog.i(TAG_BROADCAST, "Post split:");
-                            Slog.i(TAG_BROADCAST, "Original broadcast receivers:");
-                            for (int i = 0; i < r.receivers.size(); i++) {
-                                Slog.i(TAG_BROADCAST, "  " + r.receivers.get(i));
-                            }
-                            Slog.i(TAG_BROADCAST, "Split receivers:");
-                            for (int i = 0; i < defer.receivers.size(); i++) {
-                                Slog.i(TAG_BROADCAST, "  " + defer.receivers.get(i));
-                            }
-                        }
-                        // Track completion refcount as well if relevant
-                        if (r.resultTo != null) {
-                            int token = r.splitToken;
-                            if (token == 0) {
-                                // first split of this record; refcount for 'r' and 'deferred'
-                                r.splitToken = defer.splitToken = nextSplitTokenLocked();
-                                mSplitRefcounts.put(r.splitToken, 2);
-                                if (DEBUG_BROADCAST_DEFERRAL) {
-                                    Slog.i(TAG_BROADCAST,
-                                            "Broadcast needs split refcount; using new token "
-                                            + r.splitToken);
-                                }
-                            } else {
-                                // new split from an already-refcounted situation; increment count
-                                final int curCount = mSplitRefcounts.get(token);
-                                if (DEBUG_BROADCAST_DEFERRAL) {
-                                    if (curCount == 0) {
-                                        Slog.wtf(TAG_BROADCAST,
-                                                "Split refcount is zero with token for " + r);
-                                    }
-                                }
-                                mSplitRefcounts.put(token, curCount + 1);
-                                if (DEBUG_BROADCAST_DEFERRAL) {
-                                    Slog.i(TAG_BROADCAST, "New split count for token " + token
-                                            + " is " + (curCount + 1));
-                                }
-                            }
-                        }
-                    }
-                    mDispatcher.addDeferredBroadcast(receiverUid, defer);
-                    r = null;
-                    looped = true;
-                    continue;
-                }
-            }
-        } while (r == null);
-
-        // Get the next receiver...
-        int recIdx = r.nextReceiver++;
-
-        // Keep track of when this receiver started, and make sure there
-        // is a timeout message pending to kill it if need be.
-        r.receiverTime = SystemClock.uptimeMillis();
-        if (recIdx == 0) {
-            r.dispatchTime = r.receiverTime;
-            r.dispatchRealTime = SystemClock.elapsedRealtime();
-            r.dispatchClockTime = System.currentTimeMillis();
-
-            if (mLogLatencyMetrics) {
-                FrameworkStatsLog.write(
-                        FrameworkStatsLog.BROADCAST_DISPATCH_LATENCY_REPORTED,
-                        r.dispatchClockTime - r.enqueueClockTime);
-            }
-
-            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-                Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
-                    System.identityHashCode(r));
-                Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
-                    System.identityHashCode(r));
-            }
-            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing ordered broadcast ["
-                    + mQueueName + "] " + r);
-        }
-        if (! mPendingBroadcastTimeoutMessage) {
-            long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
-            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
-                    "Submitting BROADCAST_TIMEOUT_MSG ["
-                    + mQueueName + "] for " + r + " at " + timeoutTime);
-            setBroadcastTimeoutLocked(timeoutTime);
-        }
-
-        final BroadcastOptions brOptions = r.options;
-        final Object nextReceiver = r.receivers.get(recIdx);
-
-        if (nextReceiver instanceof BroadcastFilter) {
-            // Simple case: this is a registered receiver who gets
-            // a direct call.
-            BroadcastFilter filter = (BroadcastFilter)nextReceiver;
-            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                    "Delivering ordered ["
-                    + mQueueName + "] to registered "
-                    + filter + ": " + r);
-            r.mIsReceiverAppRunning = true;
-            deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
-            if (r.receiver == null || !r.ordered) {
-                // The receiver has already finished, so schedule to
-                // process the next one.
-                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Quick finishing ["
-                        + mQueueName + "]: ordered="
-                        + r.ordered + " receiver=" + r.receiver);
-                r.state = BroadcastRecord.IDLE;
-                scheduleBroadcastsLocked();
-            } else {
-                if (filter.receiverList != null) {
-                    maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
-                    // r is guaranteed ordered at this point, so we know finishReceiverLocked()
-                    // will get a callback and handle the activity start token lifecycle.
-                }
-            }
-            return;
-        }
-
-        // Hard case: need to instantiate the receiver, possibly
-        // starting its application process to host it.
-
-        ResolveInfo info =
-            (ResolveInfo)nextReceiver;
-        ComponentName component = new ComponentName(
-                info.activityInfo.applicationInfo.packageName,
-                info.activityInfo.name);
-
-        boolean skip = false;
-        if (brOptions != null &&
-                (info.activityInfo.applicationInfo.targetSdkVersion
-                        < brOptions.getMinManifestReceiverApiLevel() ||
-                info.activityInfo.applicationInfo.targetSdkVersion
-                        > brOptions.getMaxManifestReceiverApiLevel())) {
-            Slog.w(TAG, "Target SDK mismatch: receiver " + info.activityInfo
-                    + " targets " + info.activityInfo.applicationInfo.targetSdkVersion
-                    + " but delivery restricted to ["
-                    + brOptions.getMinManifestReceiverApiLevel() + ", "
-                    + brOptions.getMaxManifestReceiverApiLevel()
-                    + "] broadcasting " + broadcastDescription(r, component));
-            skip = true;
-        }
-        if (brOptions != null &&
-                !brOptions.testRequireCompatChange(info.activityInfo.applicationInfo.uid)) {
-            Slog.w(TAG, "Compat change filtered: broadcasting " + broadcastDescription(r, component)
-                    + " to uid " + info.activityInfo.applicationInfo.uid + " due to compat change "
-                    + r.options.getRequireCompatChangeId());
-            skip = true;
-        }
-        if (!skip && !mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
-                component.getPackageName(), info.activityInfo.applicationInfo.uid)) {
-            Slog.w(TAG, "Association not allowed: broadcasting "
-                    + broadcastDescription(r, component));
-            skip = true;
-        }
-        if (!skip) {
-            skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
-                    r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid);
-            if (skip) {
-                Slog.w(TAG, "Firewall blocked: broadcasting "
-                        + broadcastDescription(r, component));
-            }
-        }
-        int perm = mService.checkComponentPermission(info.activityInfo.permission,
-                r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
-                info.activityInfo.exported);
-        if (!skip && perm != PackageManager.PERMISSION_GRANTED) {
-            if (!info.activityInfo.exported) {
-                Slog.w(TAG, "Permission Denial: broadcasting "
-                        + broadcastDescription(r, component)
-                        + " is not exported from uid " + info.activityInfo.applicationInfo.uid);
-            } else {
-                Slog.w(TAG, "Permission Denial: broadcasting "
-                        + broadcastDescription(r, component)
-                        + " requires " + info.activityInfo.permission);
-            }
-            skip = true;
-        } else if (!skip && info.activityInfo.permission != null) {
-            final int opCode = AppOpsManager.permissionToOpCode(info.activityInfo.permission);
-            if (opCode != AppOpsManager.OP_NONE && mService.getAppOpsManager().noteOpNoThrow(opCode,
-                    r.callingUid, r.callerPackage, r.callerFeatureId,
-                    "Broadcast delivered to " + info.activityInfo.name)
-                    != AppOpsManager.MODE_ALLOWED) {
-                Slog.w(TAG, "Appop Denial: broadcasting "
-                        + broadcastDescription(r, component)
-                        + " requires appop " + AppOpsManager.permissionToOp(
-                                info.activityInfo.permission));
-                skip = true;
-            }
-        }
-
-        boolean isSingleton = false;
-        try {
-            isSingleton = mService.isSingleton(info.activityInfo.processName,
-                    info.activityInfo.applicationInfo,
-                    info.activityInfo.name, info.activityInfo.flags);
-        } catch (SecurityException e) {
-            Slog.w(TAG, e.getMessage());
-            skip = true;
-        }
-        if ((info.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
-            if (ActivityManager.checkUidPermission(
-                    android.Manifest.permission.INTERACT_ACROSS_USERS,
-                    info.activityInfo.applicationInfo.uid)
-                            != PackageManager.PERMISSION_GRANTED) {
-                Slog.w(TAG, "Permission Denial: Receiver " + component.flattenToShortString()
-                        + " requests FLAG_SINGLE_USER, but app does not hold "
-                        + android.Manifest.permission.INTERACT_ACROSS_USERS);
-                skip = true;
-            }
-        }
-        if (!skip && info.activityInfo.applicationInfo.isInstantApp()
-                && r.callingUid != info.activityInfo.applicationInfo.uid) {
-            Slog.w(TAG, "Instant App Denial: receiving "
-                    + r.intent
-                    + " to " + component.flattenToShortString()
-                    + " due to sender " + r.callerPackage
-                    + " (uid " + r.callingUid + ")"
-                    + " Instant Apps do not support manifest receivers");
-            skip = true;
-        }
-        if (!skip && r.callerInstantApp
-                && (info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0
-                && r.callingUid != info.activityInfo.applicationInfo.uid) {
-            Slog.w(TAG, "Instant App Denial: receiving "
-                    + r.intent
-                    + " to " + component.flattenToShortString()
-                    + " requires receiver have visibleToInstantApps set"
-                    + " due to sender " + r.callerPackage
-                    + " (uid " + r.callingUid + ")");
-            skip = true;
-        }
-        if (r.curApp != null && r.curApp.mErrorState.isCrashing()) {
-            // If the target process is crashing, just skip it.
-            Slog.w(TAG, "Skipping deliver ordered [" + mQueueName + "] " + r
-                    + " to " + r.curApp + ": process crashing");
-            skip = true;
-        }
-        if (!skip) {
-            boolean isAvailable = false;
-            try {
-                isAvailable = AppGlobals.getPackageManager().isPackageAvailable(
-                        info.activityInfo.packageName,
-                        UserHandle.getUserId(info.activityInfo.applicationInfo.uid));
-            } catch (Exception e) {
-                // all such failures mean we skip this receiver
-                Slog.w(TAG, "Exception getting recipient info for "
-                        + info.activityInfo.packageName, e);
-            }
-            if (!isAvailable) {
-                Slog.w(TAG_BROADCAST,
-                        "Skipping delivery to " + info.activityInfo.packageName + " / "
-                        + info.activityInfo.applicationInfo.uid
-                        + " : package no longer available");
-                skip = true;
-            }
-        }
-
-        // If permissions need a review before any of the app components can run, we drop
-        // the broadcast and if the calling app is in the foreground and the broadcast is
-        // explicit we launch the review UI passing it a pending intent to send the skipped
-        // broadcast.
-        if (!skip) {
-            if (!requestStartTargetPermissionsReviewIfNeededLocked(r,
-                    info.activityInfo.packageName, UserHandle.getUserId(
-                            info.activityInfo.applicationInfo.uid))) {
-                Slog.w(TAG_BROADCAST,
-                        "Skipping delivery: permission review required for "
-                                + broadcastDescription(r, component));
-                skip = true;
-            }
-        }
-
-        // This is safe to do even if we are skipping the broadcast, and we need
-        // this information now to evaluate whether it is going to be allowed to run.
-        final int receiverUid = info.activityInfo.applicationInfo.uid;
-        // If it's a singleton, it needs to be the same app or a special app
-        if (r.callingUid != Process.SYSTEM_UID && isSingleton
-                && mService.isValidSingletonCall(r.callingUid, receiverUid)) {
-            info.activityInfo = mService.getActivityInfoForUser(info.activityInfo, 0);
-        }
-        String targetProcess = info.activityInfo.processName;
-        ProcessRecord app = mService.getProcessRecordLocked(targetProcess,
-                info.activityInfo.applicationInfo.uid);
-
-        if (!skip) {
-            final int allowed = mService.getAppStartModeLOSP(
-                    info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
-                    info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false, false);
-            if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
-                // We won't allow this receiver to be launched if the app has been
-                // completely disabled from launches, or it was not explicitly sent
-                // to it and the app is in a state that should not receive it
-                // (depending on how getAppStartModeLOSP has determined that).
-                if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
-                    Slog.w(TAG, "Background execution disabled: receiving "
-                            + r.intent + " to "
-                            + component.flattenToShortString());
-                    skip = true;
-                } else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
-                        || (r.intent.getComponent() == null
-                            && r.intent.getPackage() == null
-                            && ((r.intent.getFlags()
-                                    & Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
-                            && !isSignaturePerm(r.requiredPermissions))) {
-                    mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
-                            component.getPackageName());
-                    Slog.w(TAG, "Background execution not allowed: receiving "
-                            + r.intent + " to "
-                            + component.flattenToShortString());
-                    skip = true;
-                }
-            }
-        }
-
-        if (!skip && !Intent.ACTION_SHUTDOWN.equals(r.intent.getAction())
-                && !mService.mUserController
-                .isUserRunning(UserHandle.getUserId(info.activityInfo.applicationInfo.uid),
-                        0 /* flags */)) {
-            skip = true;
-            Slog.w(TAG,
-                    "Skipping delivery to " + info.activityInfo.packageName + " / "
-                            + info.activityInfo.applicationInfo.uid + " : user is not running");
-        }
-
-        if (!skip && r.excludedPermissions != null && r.excludedPermissions.length > 0) {
-            for (int i = 0; i < r.excludedPermissions.length; i++) {
-                String excludedPermission = r.excludedPermissions[i];
-                try {
-                    perm = AppGlobals.getPackageManager()
-                        .checkPermission(excludedPermission,
-                                info.activityInfo.applicationInfo.packageName,
-                                UserHandle
-                                .getUserId(info.activityInfo.applicationInfo.uid));
-                } catch (RemoteException e) {
-                    perm = PackageManager.PERMISSION_DENIED;
-                }
-
-                int appOp = AppOpsManager.permissionToOpCode(excludedPermission);
-                if (appOp != AppOpsManager.OP_NONE) {
-                    // When there is an app op associated with the permission,
-                    // skip when both the permission and the app op are
-                    // granted.
-                    if ((perm == PackageManager.PERMISSION_GRANTED) && (
-                                mService.getAppOpsManager().checkOpNoThrow(appOp,
-                                info.activityInfo.applicationInfo.uid,
-                                info.activityInfo.packageName)
-                            == AppOpsManager.MODE_ALLOWED)) {
-                        skip = true;
-                        break;
-                    }
-                } else {
-                    // When there is no app op associated with the permission,
-                    // skip when permission is granted.
-                    if (perm == PackageManager.PERMISSION_GRANTED) {
-                        skip = true;
-                        break;
-                    }
-                }
-            }
-        }
-
-        // Check that the receiver does *not* belong to any of the excluded packages
-        if (!skip && r.excludedPackages != null && r.excludedPackages.length > 0) {
-            if (ArrayUtils.contains(r.excludedPackages, component.getPackageName())) {
-                Slog.w(TAG, "Skipping delivery of excluded package "
-                        + r.intent + " to "
-                        + component.flattenToShortString()
-                        + " excludes package " + component.getPackageName()
-                        + " due to sender " + r.callerPackage
-                        + " (uid " + r.callingUid + ")");
-                skip = true;
-            }
-        }
-
-        if (!skip && info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID &&
-                r.requiredPermissions != null && r.requiredPermissions.length > 0) {
-            for (int i = 0; i < r.requiredPermissions.length; i++) {
-                String requiredPermission = r.requiredPermissions[i];
-                try {
-                    perm = AppGlobals.getPackageManager().
-                            checkPermission(requiredPermission,
-                                    info.activityInfo.applicationInfo.packageName,
-                                    UserHandle
-                                    .getUserId(info.activityInfo.applicationInfo.uid));
-                } catch (RemoteException e) {
-                    perm = PackageManager.PERMISSION_DENIED;
-                }
-                if (perm != PackageManager.PERMISSION_GRANTED) {
-                    Slog.w(TAG, "Permission Denial: receiving "
-                            + r.intent + " to "
-                            + component.flattenToShortString()
-                            + " requires " + requiredPermission
-                            + " due to sender " + r.callerPackage
-                            + " (uid " + r.callingUid + ")");
-                    skip = true;
-                    break;
-                }
-                int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
-                if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp) {
-                    if (!noteOpForManifestReceiver(appOp, r, info, component)) {
-                        skip = true;
-                        break;
-                    }
-                }
-            }
-        }
-        if (!skip && r.appOp != AppOpsManager.OP_NONE) {
-            if (!noteOpForManifestReceiver(r.appOp, r, info, component)) {
-                skip = true;
-            }
-        }
-
-        // Filter packages in the intent extras, skipping delivery if none of the packages is
-        // visible to the receiver.
-        Bundle filteredExtras = null;
-        if (!skip && r.filterExtrasForReceiver != null) {
-            final Bundle extras = r.intent.getExtras();
-            if (extras != null) {
-                filteredExtras = r.filterExtrasForReceiver.apply(receiverUid, extras);
-                if (filteredExtras == null) {
-                    if (DEBUG_BROADCAST) {
-                        Slog.v(TAG, "Skipping delivery to "
-                                + info.activityInfo.packageName + " / " + receiverUid
-                                + " : receiver is filtered by the package visibility");
-                    }
-                    skip = true;
-                }
-            }
-        }
-
-        if (skip) {
-            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                    "Skipping delivery of ordered [" + mQueueName + "] "
-                    + r + " for reason described above");
-            r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED;
-            r.receiver = null;
-            r.curFilter = null;
-            r.state = BroadcastRecord.IDLE;
-            r.manifestSkipCount++;
-            scheduleBroadcastsLocked();
-            return;
-        }
-        r.manifestCount++;
-
-        r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;
-        r.state = BroadcastRecord.APP_RECEIVE;
-        r.curComponent = component;
-        r.curReceiver = info.activityInfo;
-        r.curFilteredExtras = filteredExtras;
-        if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) {
-            Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, "
-                    + info.activityInfo + ", callingUid = " + r.callingUid + ", uid = "
-                    + receiverUid);
-        }
-        final boolean isActivityCapable =
-                (brOptions != null && brOptions.getTemporaryAppAllowlistDuration() > 0);
-        maybeScheduleTempAllowlistLocked(receiverUid, r, brOptions);
-
-        // Report that a component is used for explicit broadcasts.
-        if (r.intent.getComponent() != null && r.curComponent != null
-                && !TextUtils.equals(r.curComponent.getPackageName(), r.callerPackage)) {
-            mService.mUsageStatsService.reportEvent(
-                    r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED);
-        }
-
-        // Broadcast is being executed, its package can't be stopped.
-        try {
-            AppGlobals.getPackageManager().setPackageStoppedState(
-                    r.curComponent.getPackageName(), false, r.userId);
-        } catch (RemoteException e) {
-        } catch (IllegalArgumentException e) {
-            Slog.w(TAG, "Failed trying to unstop package "
-                    + r.curComponent.getPackageName() + ": " + e);
-        }
-
-        // Is this receiver's application already running?
-        if (app != null && app.getThread() != null && !app.isKilled()) {
-            try {
-                app.addPackage(info.activityInfo.packageName,
-                        info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
-                maybeAddAllowBackgroundActivityStartsToken(app, r);
-                r.mIsReceiverAppRunning = true;
-                processCurBroadcastLocked(r, app);
-                return;
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Exception when sending broadcast to "
-                      + r.curComponent, e);
-            } catch (RuntimeException e) {
-                Slog.wtf(TAG, "Failed sending broadcast to "
-                        + r.curComponent + " with " + r.intent, e);
-                // If some unexpected exception happened, just skip
-                // this broadcast.  At this point we are not in the call
-                // from a client, so throwing an exception out from here
-                // will crash the entire system instead of just whoever
-                // sent the broadcast.
-                logBroadcastReceiverDiscardLocked(r);
-                finishReceiverLocked(r, r.resultCode, r.resultData,
-                        r.resultExtras, r.resultAbort, false);
-                scheduleBroadcastsLocked();
-                // We need to reset the state if we failed to start the receiver.
-                r.state = BroadcastRecord.IDLE;
-                return;
-            }
-
-            // If a dead object exception was thrown -- fall through to
-            // restart the application.
-        }
-
-        // Not running -- get it started, to be executed when the app comes up.
-        if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
-                "Need to start app ["
-                + mQueueName + "] " + targetProcess + " for broadcast " + r);
-        r.curApp = mService.startProcessLocked(targetProcess,
-                info.activityInfo.applicationInfo, true,
-                r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
-                new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
-                        r.intent.getAction(), getHostingRecordTriggerType(r)),
-                isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
-                (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
-        if (r.curApp == null) {
-            // Ah, this recipient is unavailable.  Finish it if necessary,
-            // and mark the broadcast record as ready for the next.
-            Slog.w(TAG, "Unable to launch app "
-                    + info.activityInfo.applicationInfo.packageName + "/"
-                    + receiverUid + " for broadcast "
-                    + r.intent + ": process is bad");
-            logBroadcastReceiverDiscardLocked(r);
-            finishReceiverLocked(r, r.resultCode, r.resultData,
-                    r.resultExtras, r.resultAbort, false);
-            scheduleBroadcastsLocked();
-            r.state = BroadcastRecord.IDLE;
-            return;
-        }
-
-        maybeAddAllowBackgroundActivityStartsToken(r.curApp, r);
-        mPendingBroadcast = r;
-        mPendingBroadcastRecvIndex = recIdx;
-    }
-
-    private String getHostingRecordTriggerType(BroadcastRecord r) {
-        if (r.alarm) {
-            return HostingRecord.TRIGGER_TYPE_ALARM;
-        } else if (r.pushMessage) {
-            return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE;
-        } else if (r.pushMessageOverQuota) {
-            return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
-        }
-        return HostingRecord.TRIGGER_TYPE_UNKNOWN;
-    }
-
-    @Nullable
-    private String getTargetPackage(BroadcastRecord r) {
-        if (r.intent == null) {
-            return null;
-        }
-        if (r.intent.getPackage() != null) {
-            return r.intent.getPackage();
-        } else if (r.intent.getComponent() != null) {
-            return r.intent.getComponent().getPackageName();
-        }
-        return null;
-    }
-
-    private void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) {
-        // Only log after last receiver.
-        // In case of split BOOT_COMPLETED broadcast, make sure only call this method on the
-        // last BroadcastRecord of the split broadcast which has non-null resultTo.
-        final int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
-        if (r.nextReceiver < numReceivers) {
-            return;
-        }
-        final String action = r.intent.getAction();
-        int event = 0;
-        if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
-            event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
-        } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
-            event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
-        }
-        if (event != 0) {
-            final int dispatchLatency = (int)(r.dispatchTime - r.enqueueTime);
-            final int completeLatency = (int)
-                    (SystemClock.uptimeMillis() - r.enqueueTime);
-            final int dispatchRealLatency = (int)(r.dispatchRealTime - r.enqueueRealTime);
-            final int completeRealLatency = (int)
-                    (SystemClock.elapsedRealtime() - r.enqueueRealTime);
-            int userType = FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
-            // This method is called very infrequently, no performance issue we call
-            // LocalServices.getService() here.
-            final UserManagerInternal umInternal = LocalServices.getService(
-                    UserManagerInternal.class);
-            final UserInfo userInfo = umInternal.getUserInfo(r.userId);
-            if (userInfo != null) {
-                userType = UserManager.getUserTypeForStatsd(userInfo.userType);
-            }
-            Slog.i(TAG_BROADCAST,
-                    "BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:"
-                            + action
-                            + " dispatchLatency:" + dispatchLatency
-                            + " completeLatency:" + completeLatency
-                            + " dispatchRealLatency:" + dispatchRealLatency
-                            + " completeRealLatency:" + completeRealLatency
-                            + " receiversSize:" + numReceivers
-                            + " userId:" + r.userId
-                            + " userType:" + (userInfo != null? userInfo.userType : null));
-            FrameworkStatsLog.write(
-                    BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED,
-                    event,
-                    dispatchLatency,
-                    completeLatency,
-                    dispatchRealLatency,
-                    completeRealLatency,
-                    r.userId,
-                    userType);
-        }
-    }
-
-    private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r, int targetUid) {
-        if (r.options == null || r.options.getIdForResponseEvent() <= 0) {
-            return;
-        }
-        final String targetPackage = getTargetPackage(r);
-        // Ignore non-explicit broadcasts
-        if (targetPackage == null) {
-            return;
-        }
-        getUsageStatsManagerInternal().reportBroadcastDispatched(
-                r.callingUid, targetPackage, UserHandle.of(r.userId),
-                r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime(),
-                mService.getUidStateLocked(targetUid));
-    }
-
-    @NonNull
-    private UsageStatsManagerInternal getUsageStatsManagerInternal() {
-        final UsageStatsManagerInternal usageStatsManagerInternal =
-                LocalServices.getService(UsageStatsManagerInternal.class);
-        return usageStatsManagerInternal;
-    }
-
-    private boolean noteOpForManifestReceiver(int appOp, BroadcastRecord r, ResolveInfo info,
-            ComponentName component) {
-        if (ArrayUtils.isEmpty(info.activityInfo.attributionTags)) {
-            return noteOpForManifestReceiverInner(appOp, r, info, component, null);
-        } else {
-            // Attribution tags provided, noteOp each tag
-            for (String tag : info.activityInfo.attributionTags) {
-                if (!noteOpForManifestReceiverInner(appOp, r, info, component, tag)) {
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
-
-    private boolean noteOpForManifestReceiverInner(int appOp, BroadcastRecord r, ResolveInfo info,
-            ComponentName component, String tag) {
-        if (mService.getAppOpsManager().noteOpNoThrow(appOp,
-                    info.activityInfo.applicationInfo.uid,
-                    info.activityInfo.packageName,
-                    tag,
-                    "Broadcast delivered to " + info.activityInfo.name)
-                != AppOpsManager.MODE_ALLOWED) {
-            Slog.w(TAG, "Appop Denial: receiving "
-                    + r.intent + " to "
-                    + component.flattenToShortString()
-                    + " requires appop " + AppOpsManager.opToName(appOp)
-                    + " due to sender " + r.callerPackage
-                    + " (uid " + r.callingUid + ")");
-            return false;
-        }
-        return true;
-    }
-
-    private void maybeAddAllowBackgroundActivityStartsToken(ProcessRecord proc, BroadcastRecord r) {
-        if (r == null || proc == null || !r.allowBackgroundActivityStarts) {
-            return;
-        }
-        String msgToken = (proc.toShortString() + r.toString()).intern();
-        // first, if there exists a past scheduled request to remove this token, drop
-        // that request - we don't want the token to be swept from under our feet...
-        mHandler.removeCallbacksAndMessages(msgToken);
-        // ...then add the token
-        proc.addOrUpdateAllowBackgroundActivityStartsToken(r, r.mBackgroundActivityStartsToken);
-    }
-
-    final void setBroadcastTimeoutLocked(long timeoutTime) {
-        if (! mPendingBroadcastTimeoutMessage) {
-            Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
-            mHandler.sendMessageAtTime(msg, timeoutTime);
-            mPendingBroadcastTimeoutMessage = true;
-        }
-    }
-
-    final void cancelBroadcastTimeoutLocked() {
-        if (mPendingBroadcastTimeoutMessage) {
-            mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
-            mPendingBroadcastTimeoutMessage = false;
-        }
-    }
-
-    final void broadcastTimeoutLocked(boolean fromMsg) {
-        if (fromMsg) {
-            mPendingBroadcastTimeoutMessage = false;
-        }
-
-        if (mDispatcher.isEmpty() || mDispatcher.getActiveBroadcastLocked() == null) {
-            return;
-        }
-
-        long now = SystemClock.uptimeMillis();
-        BroadcastRecord r = mDispatcher.getActiveBroadcastLocked();
-        if (fromMsg) {
-            if (!mService.mProcessesReady) {
-                // Only process broadcast timeouts if the system is ready; some early
-                // broadcasts do heavy work setting up system facilities
-                return;
-            }
-
-            // If the broadcast is generally exempt from timeout tracking, we're done
-            if (r.timeoutExempt) {
-                if (DEBUG_BROADCAST) {
-                    Slog.i(TAG_BROADCAST, "Broadcast timeout but it's exempt: "
-                            + r.intent.getAction());
-                }
-                return;
-            }
-
-            long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
-            if (timeoutTime > now) {
-                // We can observe premature timeouts because we do not cancel and reset the
-                // broadcast timeout message after each receiver finishes.  Instead, we set up
-                // an initial timeout then kick it down the road a little further as needed
-                // when it expires.
-                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
-                        "Premature timeout ["
-                        + mQueueName + "] @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for "
-                        + timeoutTime);
-                setBroadcastTimeoutLocked(timeoutTime);
-                return;
-            }
-        }
-
-        if (r.state == BroadcastRecord.WAITING_SERVICES) {
-            // In this case the broadcast had already finished, but we had decided to wait
-            // for started services to finish as well before going on.  So if we have actually
-            // waited long enough time timeout the broadcast, let's give up on the whole thing
-            // and just move on to the next.
-            Slog.i(TAG, "Waited long enough for: " + (r.curComponent != null
-                    ? r.curComponent.flattenToShortString() : "(null)"));
-            r.curComponent = null;
-            r.state = BroadcastRecord.IDLE;
-            processNextBroadcastLocked(false, false);
-            return;
-        }
-
-        // If the receiver app is being debugged we quietly ignore unresponsiveness, just
-        // tidying up and moving on to the next broadcast without crashing or ANRing this
-        // app just because it's stopped at a breakpoint.
-        final boolean debugging = (r.curApp != null && r.curApp.isDebugging());
-
-        long timeoutDurationMs = now - r.receiverTime;
-        Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r.receiver
-                + ", started " + timeoutDurationMs + "ms ago");
-        r.receiverTime = now;
-        if (!debugging) {
-            r.anrCount++;
-        }
-
-        ProcessRecord app = null;
-        TimeoutRecord timeoutRecord = null;
-
-        Object curReceiver;
-        if (r.nextReceiver > 0) {
-            curReceiver = r.receivers.get(r.nextReceiver-1);
-            r.delivery[r.nextReceiver-1] = BroadcastRecord.DELIVERY_TIMEOUT;
-        } else {
-            curReceiver = r.curReceiver;
-        }
-        Slog.w(TAG, "Receiver during timeout of " + r + " : " + curReceiver);
-        logBroadcastReceiverDiscardLocked(r);
-        if (curReceiver != null && curReceiver instanceof BroadcastFilter) {
-            BroadcastFilter bf = (BroadcastFilter)curReceiver;
-            if (bf.receiverList.pid != 0
-                    && bf.receiverList.pid != ActivityManagerService.MY_PID) {
-                synchronized (mService.mPidsSelfLocked) {
-                    app = mService.mPidsSelfLocked.get(
-                            bf.receiverList.pid);
-                }
-            }
-        } else {
-            app = r.curApp;
-        }
-
-        if (app != null) {
-            String anrMessage =
-                    "Broadcast of " + r.intent.toString() + ", waited " + timeoutDurationMs
-                            + "ms";
-            timeoutRecord = TimeoutRecord.forBroadcastReceiver(anrMessage);
-        }
-
-        if (mPendingBroadcast == r) {
-            mPendingBroadcast = null;
-        }
-
-        // Move on to the next receiver.
-        finishReceiverLocked(r, r.resultCode, r.resultData,
-                r.resultExtras, r.resultAbort, false);
-        scheduleBroadcastsLocked();
-
-        if (!debugging && timeoutRecord != null) {
-            mService.mAnrHelper.appNotResponding(app, timeoutRecord);
-        }
-    }
-
-    private final int ringAdvance(int x, final int increment, final int ringSize) {
-        x += increment;
-        if (x < 0) return (ringSize - 1);
-        else if (x >= ringSize) return 0;
-        else return x;
-    }
-
-    private final void addBroadcastToHistoryLocked(BroadcastRecord original) {
-        if (original.callingUid < 0) {
-            // This was from a registerReceiver() call; ignore it.
-            return;
-        }
-        original.finishTime = SystemClock.uptimeMillis();
-
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                createBroadcastTraceTitle(original, BroadcastRecord.DELIVERY_DELIVERED),
-                System.identityHashCode(original));
-        }
-
-        final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
-        final String callerPackage = info != null ? info.packageName : original.callerPackage;
-        if (callerPackage != null) {
-            mService.mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
-                    original.callingUid, 0, callerPackage).sendToTarget();
-        }
-
-        // Note sometimes (only for sticky broadcasts?) we reuse BroadcastRecords,
-        // So don't change the incoming record directly.
-        final BroadcastRecord historyRecord = original.maybeStripForHistory();
-
-        mBroadcastHistory[mHistoryNext] = historyRecord;
-        mHistoryNext = ringAdvance(mHistoryNext, 1, MAX_BROADCAST_HISTORY);
-
-        mBroadcastSummaryHistory[mSummaryHistoryNext] = historyRecord.intent;
-        mSummaryHistoryEnqueueTime[mSummaryHistoryNext] = historyRecord.enqueueClockTime;
-        mSummaryHistoryDispatchTime[mSummaryHistoryNext] = historyRecord.dispatchClockTime;
-        mSummaryHistoryFinishTime[mSummaryHistoryNext] = System.currentTimeMillis();
-        mSummaryHistoryNext = ringAdvance(mSummaryHistoryNext, 1, MAX_BROADCAST_SUMMARY_HISTORY);
-    }
-
-    boolean cleanupDisabledPackageReceiversLocked(
-            String packageName, Set<String> filterByClasses, int userId, boolean doit) {
-        boolean didSomething = false;
-        for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
-            didSomething |= mParallelBroadcasts.get(i).cleanupDisabledPackageReceiversLocked(
-                    packageName, filterByClasses, userId, doit);
-            if (!doit && didSomething) {
-                return true;
-            }
-        }
-
-        didSomething |= mDispatcher.cleanupDisabledPackageReceiversLocked(packageName,
-                filterByClasses, userId, doit);
-
-        return didSomething;
-    }
-
-    final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) {
-        final int logIndex = r.nextReceiver - 1;
-        if (logIndex >= 0 && logIndex < r.receivers.size()) {
-            Object curReceiver = r.receivers.get(logIndex);
-            if (curReceiver instanceof BroadcastFilter) {
-                BroadcastFilter bf = (BroadcastFilter) curReceiver;
-                EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER,
-                        bf.owningUserId, System.identityHashCode(r),
-                        r.intent.getAction(), logIndex, System.identityHashCode(bf));
-            } else {
-                ResolveInfo ri = (ResolveInfo) curReceiver;
-                EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
-                        UserHandle.getUserId(ri.activityInfo.applicationInfo.uid),
-                        System.identityHashCode(r), r.intent.getAction(), logIndex, ri.toString());
-            }
-        } else {
-            if (logIndex < 0) Slog.w(TAG,
-                    "Discarding broadcast before first receiver is invoked: " + r);
-            EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
-                    -1, System.identityHashCode(r),
-                    r.intent.getAction(),
-                    r.nextReceiver,
-                    "NONE");
-        }
-    }
-
-    private String createBroadcastTraceTitle(BroadcastRecord record, int state) {
-        return formatSimple("Broadcast %s from %s (%s) %s",
-                state == BroadcastRecord.DELIVERY_PENDING ? "in queue" : "dispatched",
-                record.callerPackage == null ? "" : record.callerPackage,
-                record.callerApp == null ? "process unknown" : record.callerApp.toShortString(),
-                record.intent == null ? "" : record.intent.getAction());
-    }
-
-    boolean isIdle() {
-        return mParallelBroadcasts.isEmpty() && mDispatcher.isIdle()
-                && (mPendingBroadcast == null);
-    }
-
-    // Used by wait-for-broadcast-idle : fast-forward all current deferrals to
-    // be immediately deliverable.
-    void cancelDeferrals() {
-        synchronized (mService) {
-            mDispatcher.cancelDeferralsLocked();
-            scheduleBroadcastsLocked();
-        }
-    }
-
-    String describeState() {
-        synchronized (mService) {
-            return mParallelBroadcasts.size() + " parallel; "
-                    + mDispatcher.describeStateLocked();
-        }
-    }
-
-    void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        long token = proto.start(fieldId);
-        proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName);
-        int N;
-        N = mParallelBroadcasts.size();
-        for (int i = N - 1; i >= 0; i--) {
-            mParallelBroadcasts.get(i).dumpDebug(proto, BroadcastQueueProto.PARALLEL_BROADCASTS);
-        }
-        mDispatcher.dumpDebug(proto, BroadcastQueueProto.ORDERED_BROADCASTS);
-        if (mPendingBroadcast != null) {
-            mPendingBroadcast.dumpDebug(proto, BroadcastQueueProto.PENDING_BROADCAST);
-        }
-
-        int lastIndex = mHistoryNext;
-        int ringIndex = lastIndex;
-        do {
-            // increasing index = more recent entry, and we want to print the most
-            // recent first and work backwards, so we roll through the ring backwards.
-            ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_HISTORY);
-            BroadcastRecord r = mBroadcastHistory[ringIndex];
-            if (r != null) {
-                r.dumpDebug(proto, BroadcastQueueProto.HISTORICAL_BROADCASTS);
-            }
-        } while (ringIndex != lastIndex);
-
-        lastIndex = ringIndex = mSummaryHistoryNext;
-        do {
-            ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
-            Intent intent = mBroadcastSummaryHistory[ringIndex];
-            if (intent == null) {
-                continue;
-            }
-            long summaryToken = proto.start(BroadcastQueueProto.HISTORICAL_BROADCASTS_SUMMARY);
-            intent.dumpDebug(proto, BroadcastQueueProto.BroadcastSummary.INTENT,
-                    false, true, true, false);
-            proto.write(BroadcastQueueProto.BroadcastSummary.ENQUEUE_CLOCK_TIME_MS,
-                    mSummaryHistoryEnqueueTime[ringIndex]);
-            proto.write(BroadcastQueueProto.BroadcastSummary.DISPATCH_CLOCK_TIME_MS,
-                    mSummaryHistoryDispatchTime[ringIndex]);
-            proto.write(BroadcastQueueProto.BroadcastSummary.FINISH_CLOCK_TIME_MS,
-                    mSummaryHistoryFinishTime[ringIndex]);
-            proto.end(summaryToken);
-        } while (ringIndex != lastIndex);
-        proto.end(token);
-    }
-
-    final boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpAll, String dumpPackage, boolean needSep) {
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
-        if (!mParallelBroadcasts.isEmpty() || !mDispatcher.isEmpty()
-                || mPendingBroadcast != null) {
-            boolean printed = false;
-            for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
-                BroadcastRecord br = mParallelBroadcasts.get(i);
-                if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) {
-                    continue;
-                }
-                if (!printed) {
-                    if (needSep) {
-                        pw.println();
-                    }
-                    needSep = true;
-                    printed = true;
-                    pw.println("  Active broadcasts [" + mQueueName + "]:");
-                }
-                pw.println("  Active Broadcast " + mQueueName + " #" + i + ":");
-                br.dump(pw, "    ", sdf);
-            }
-
-            mDispatcher.dumpLocked(pw, dumpPackage, mQueueName, sdf);
-
-            if (dumpPackage == null || (mPendingBroadcast != null
-                    && dumpPackage.equals(mPendingBroadcast.callerPackage))) {
-                pw.println();
-                pw.println("  Pending broadcast [" + mQueueName + "]:");
-                if (mPendingBroadcast != null) {
-                    mPendingBroadcast.dump(pw, "    ", sdf);
-                } else {
-                    pw.println("    (null)");
-                }
-                needSep = true;
-            }
-        }
-
-        mConstants.dump(pw);
-
-        int i;
-        boolean printed = false;
-
-        i = -1;
-        int lastIndex = mHistoryNext;
-        int ringIndex = lastIndex;
-        do {
-            // increasing index = more recent entry, and we want to print the most
-            // recent first and work backwards, so we roll through the ring backwards.
-            ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_HISTORY);
-            BroadcastRecord r = mBroadcastHistory[ringIndex];
-            if (r == null) {
-                continue;
-            }
-
-            i++; // genuine record of some sort even if we're filtering it out
-            if (dumpPackage != null && !dumpPackage.equals(r.callerPackage)) {
-                continue;
-            }
-            if (!printed) {
-                if (needSep) {
-                    pw.println();
-                }
-                needSep = true;
-                pw.println("  Historical broadcasts [" + mQueueName + "]:");
-                printed = true;
-            }
-            if (dumpAll) {
-                pw.print("  Historical Broadcast " + mQueueName + " #");
-                        pw.print(i); pw.println(":");
-                r.dump(pw, "    ", sdf);
-            } else {
-                pw.print("  #"); pw.print(i); pw.print(": "); pw.println(r);
-                pw.print("    ");
-                pw.println(r.intent.toShortString(false, true, true, false));
-                if (r.targetComp != null && r.targetComp != r.intent.getComponent()) {
-                    pw.print("    targetComp: "); pw.println(r.targetComp.toShortString());
-                }
-                Bundle bundle = r.intent.getExtras();
-                if (bundle != null) {
-                    pw.print("    extras: "); pw.println(bundle.toString());
-                }
-            }
-        } while (ringIndex != lastIndex);
-
-        if (dumpPackage == null) {
-            lastIndex = ringIndex = mSummaryHistoryNext;
-            if (dumpAll) {
-                printed = false;
-                i = -1;
-            } else {
-                // roll over the 'i' full dumps that have already been issued
-                for (int j = i;
-                        j > 0 && ringIndex != lastIndex;) {
-                    ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
-                    BroadcastRecord r = mBroadcastHistory[ringIndex];
-                    if (r == null) {
-                        continue;
-                    }
-                    j--;
-                }
-            }
-            // done skipping; dump the remainder of the ring. 'i' is still the ordinal within
-            // the overall broadcast history.
-            do {
-                ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
-                Intent intent = mBroadcastSummaryHistory[ringIndex];
-                if (intent == null) {
-                    continue;
-                }
-                if (!printed) {
-                    if (needSep) {
-                        pw.println();
-                    }
-                    needSep = true;
-                    pw.println("  Historical broadcasts summary [" + mQueueName + "]:");
-                    printed = true;
-                }
-                if (!dumpAll && i >= 50) {
-                    pw.println("  ...");
-                    break;
-                }
-                i++;
-                pw.print("  #"); pw.print(i); pw.print(": ");
-                pw.println(intent.toShortString(false, true, true, false));
-                pw.print("    ");
-                TimeUtils.formatDuration(mSummaryHistoryDispatchTime[ringIndex]
-                        - mSummaryHistoryEnqueueTime[ringIndex], pw);
-                pw.print(" dispatch ");
-                TimeUtils.formatDuration(mSummaryHistoryFinishTime[ringIndex]
-                        - mSummaryHistoryDispatchTime[ringIndex], pw);
-                pw.println(" finish");
-                pw.print("    enq=");
-                pw.print(sdf.format(new Date(mSummaryHistoryEnqueueTime[ringIndex])));
-                pw.print(" disp=");
-                pw.print(sdf.format(new Date(mSummaryHistoryDispatchTime[ringIndex])));
-                pw.print(" fin=");
-                pw.println(sdf.format(new Date(mSummaryHistoryFinishTime[ringIndex])));
-                Bundle bundle = intent.getExtras();
-                if (bundle != null) {
-                    pw.print("    extras: "); pw.println(bundle.toString());
-                }
-            } while (ringIndex != lastIndex);
-        }
-
-        return needSep;
-    }
+    public abstract boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll, String dumpPackage, boolean needSep);
 }
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
new file mode 100644
index 0000000..4bffe35
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -0,0 +1,1960 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
+import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
+import static android.text.TextUtils.formatSimple;
+
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
+import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.BroadcastOptions;
+import android.app.IApplicationThread;
+import android.app.RemoteServiceException.CannotDeliverBroadcastException;
+import android.app.usage.UsageEvents.Event;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerExemptionManager.ReasonCode;
+import android.os.PowerExemptionManager.TempAllowListType;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.EventLog;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.os.TimeoutRecord;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Set;
+
+/**
+ * BROADCASTS
+ *
+ * We keep three broadcast queues and associated bookkeeping, one for those at
+ * foreground priority, and one for normal (background-priority) broadcasts, and one to
+ * offload special broadcasts that we know take a long time, such as BOOT_COMPLETED.
+ */
+public class BroadcastQueueImpl extends BroadcastQueue {
+    private static final String TAG_MU = TAG + POSTFIX_MU;
+    private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
+
+    static final int MAX_BROADCAST_HISTORY = ActivityManager.isLowRamDeviceStatic() ? 10 : 50;
+    static final int MAX_BROADCAST_SUMMARY_HISTORY
+            = ActivityManager.isLowRamDeviceStatic() ? 25 : 300;
+
+    /**
+     * If true, we can delay broadcasts while waiting services to finish in the previous
+     * receiver's process.
+     */
+    final boolean mDelayBehindServices;
+
+    /**
+     * Lists of all active broadcasts that are to be executed immediately
+     * (without waiting for another broadcast to finish).  Currently this only
+     * contains broadcasts to registered receivers, to avoid spinning up
+     * a bunch of processes to execute IntentReceiver components.  Background-
+     * and foreground-priority broadcasts are queued separately.
+     */
+    final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>();
+
+    /**
+     * Tracking of the ordered broadcast queue, including deferral policy and alarm
+     * prioritization.
+     */
+    final BroadcastDispatcher mDispatcher;
+
+    /**
+     * Refcounting for completion callbacks of split/deferred broadcasts.  The key
+     * is an opaque integer token assigned lazily when a broadcast is first split
+     * into multiple BroadcastRecord objects.
+     */
+    final SparseIntArray mSplitRefcounts = new SparseIntArray();
+    private int mNextToken = 0;
+
+    /**
+     * Historical data of past broadcasts, for debugging.  This is a ring buffer
+     * whose last element is at mHistoryNext.
+     */
+    final BroadcastRecord[] mBroadcastHistory = new BroadcastRecord[MAX_BROADCAST_HISTORY];
+    int mHistoryNext = 0;
+
+    /**
+     * Summary of historical data of past broadcasts, for debugging.  This is a
+     * ring buffer whose last element is at mSummaryHistoryNext.
+     */
+    final Intent[] mBroadcastSummaryHistory = new Intent[MAX_BROADCAST_SUMMARY_HISTORY];
+    int mSummaryHistoryNext = 0;
+
+    /**
+     * Various milestone timestamps of entries in the mBroadcastSummaryHistory ring
+     * buffer, also tracked via the mSummaryHistoryNext index.  These are all in wall
+     * clock time, not elapsed.
+     */
+    final long[] mSummaryHistoryEnqueueTime = new  long[MAX_BROADCAST_SUMMARY_HISTORY];
+    final long[] mSummaryHistoryDispatchTime = new  long[MAX_BROADCAST_SUMMARY_HISTORY];
+    final long[] mSummaryHistoryFinishTime = new  long[MAX_BROADCAST_SUMMARY_HISTORY];
+
+    /**
+     * Set when we current have a BROADCAST_INTENT_MSG in flight.
+     */
+    boolean mBroadcastsScheduled = false;
+
+    /**
+     * True if we have a pending unexpired BROADCAST_TIMEOUT_MSG posted to our handler.
+     */
+    boolean mPendingBroadcastTimeoutMessage;
+
+    /**
+     * Intent broadcasts that we have tried to start, but are
+     * waiting for the application's process to be created.  We only
+     * need one per scheduling class (instead of a list) because we always
+     * process broadcasts one at a time, so no others can be started while
+     * waiting for this one.
+     */
+    BroadcastRecord mPendingBroadcast = null;
+
+    /**
+     * The receiver index that is pending, to restart the broadcast if needed.
+     */
+    int mPendingBroadcastRecvIndex;
+
+    static final int BROADCAST_INTENT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG;
+    static final int BROADCAST_TIMEOUT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG + 1;
+
+    // log latency metrics for ordered broadcasts during BOOT_COMPLETED processing
+    boolean mLogLatencyMetrics = true;
+
+    final BroadcastHandler mHandler;
+
+    private final class BroadcastHandler extends Handler {
+        public BroadcastHandler(Looper looper) {
+            super(looper, null, true);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case BROADCAST_INTENT_MSG: {
+                    if (DEBUG_BROADCAST) Slog.v(
+                            TAG_BROADCAST, "Received BROADCAST_INTENT_MSG ["
+                            + mQueueName + "]");
+                    processNextBroadcast(true);
+                } break;
+                case BROADCAST_TIMEOUT_MSG: {
+                    synchronized (mService) {
+                        broadcastTimeoutLocked(true);
+                    }
+                } break;
+            }
+        }
+    }
+
+    BroadcastQueueImpl(ActivityManagerService service, Handler handler,
+            String name, BroadcastConstants constants, boolean allowDelayBehindServices) {
+        super(service, handler, name, constants);
+        mHandler = new BroadcastHandler(handler.getLooper());
+        mDelayBehindServices = allowDelayBehindServices;
+        mDispatcher = new BroadcastDispatcher(this, mConstants, mHandler, mService);
+    }
+
+    void start(ContentResolver resolver) {
+        mDispatcher.start();
+        mConstants.startObserving(mHandler, resolver);
+    }
+
+    public boolean isDelayBehindServices() {
+        return mDelayBehindServices;
+    }
+
+    public BroadcastRecord getPendingBroadcastLocked() {
+        return mPendingBroadcast;
+    }
+
+    public BroadcastRecord getActiveBroadcastLocked() {
+        return mDispatcher.getActiveBroadcastLocked();
+    }
+
+    public void enqueueBroadcastLocked(BroadcastRecord r) {
+        final boolean replacePending = (r.intent.getFlags()
+                & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
+
+        // Ordered broadcasts obviously need to be dispatched in serial order,
+        // but this implementation expects all manifest receivers to also be
+        // dispatched in a serial fashion
+        boolean serialDispatch = r.ordered;
+        if (!serialDispatch) {
+            final int N = (r.receivers != null) ? r.receivers.size() : 0;
+            for (int i = 0; i < N; i++) {
+                if (r.receivers.get(i) instanceof ResolveInfo) {
+                    serialDispatch = true;
+                    break;
+                }
+            }
+        }
+
+        if (serialDispatch) {
+            final BroadcastRecord oldRecord =
+                    replacePending ? replaceOrderedBroadcastLocked(r) : null;
+            if (oldRecord != null) {
+                // Replaced, fire the result-to receiver.
+                if (oldRecord.resultTo != null) {
+                    try {
+                        oldRecord.mIsReceiverAppRunning = true;
+                        performReceiveLocked(oldRecord.callerApp, oldRecord.resultTo,
+                                oldRecord.intent,
+                                Activity.RESULT_CANCELED, null, null,
+                                false, false, oldRecord.userId, oldRecord.callingUid, r.callingUid,
+                                SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Failure ["
+                                + mQueueName + "] sending broadcast result of "
+                                + oldRecord.intent, e);
+
+                    }
+                }
+            } else {
+                enqueueOrderedBroadcastLocked(r);
+                scheduleBroadcastsLocked();
+            }
+        } else {
+            final boolean replaced = replacePending
+                    && (replaceParallelBroadcastLocked(r) != null);
+            // Note: We assume resultTo is null for non-ordered broadcasts.
+            if (!replaced) {
+                enqueueParallelBroadcastLocked(r);
+                scheduleBroadcastsLocked();
+            }
+        }
+    }
+
+    public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
+        r.enqueueClockTime = System.currentTimeMillis();
+        r.enqueueTime = SystemClock.uptimeMillis();
+        r.enqueueRealTime = SystemClock.elapsedRealtime();
+        mParallelBroadcasts.add(r);
+        enqueueBroadcastHelper(r);
+    }
+
+    public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
+        r.enqueueClockTime = System.currentTimeMillis();
+        r.enqueueTime = SystemClock.uptimeMillis();
+        r.enqueueRealTime = SystemClock.elapsedRealtime();
+        mDispatcher.enqueueOrderedBroadcastLocked(r);
+        enqueueBroadcastHelper(r);
+    }
+
+    /**
+     * Don't call this method directly; call enqueueParallelBroadcastLocked or
+     * enqueueOrderedBroadcastLocked.
+     */
+    private void enqueueBroadcastHelper(BroadcastRecord r) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
+                System.identityHashCode(r));
+        }
+    }
+
+    /**
+     * Find the same intent from queued parallel broadcast, replace with a new one and return
+     * the old one.
+     */
+    public final BroadcastRecord replaceParallelBroadcastLocked(BroadcastRecord r) {
+        return replaceBroadcastLocked(mParallelBroadcasts, r, "PARALLEL");
+    }
+
+    /**
+     * Find the same intent from queued ordered broadcast, replace with a new one and return
+     * the old one.
+     */
+    public final BroadcastRecord replaceOrderedBroadcastLocked(BroadcastRecord r) {
+        return mDispatcher.replaceBroadcastLocked(r, "ORDERED");
+    }
+
+    private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> queue,
+            BroadcastRecord r, String typeForLogging) {
+        final Intent intent = r.intent;
+        for (int i = queue.size() - 1; i > 0; i--) {
+            final BroadcastRecord old = queue.get(i);
+            if (old.userId == r.userId && intent.filterEquals(old.intent)) {
+                if (DEBUG_BROADCAST) {
+                    Slog.v(TAG_BROADCAST, "***** DROPPING "
+                            + typeForLogging + " [" + mQueueName + "]: " + intent);
+                }
+                queue.set(i, r);
+                return old;
+            }
+        }
+        return null;
+    }
+
+    private final void processCurBroadcastLocked(BroadcastRecord r,
+            ProcessRecord app) throws RemoteException {
+        if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
+                "Process cur broadcast " + r + " for app " + app);
+        final IApplicationThread thread = app.getThread();
+        if (thread == null) {
+            throw new RemoteException();
+        }
+        if (app.isInFullBackup()) {
+            skipReceiverLocked(r);
+            return;
+        }
+
+        r.receiver = thread.asBinder();
+        r.curApp = app;
+        final ProcessReceiverRecord prr = app.mReceivers;
+        prr.addCurReceiver(r);
+        app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
+        // Don't bump its LRU position if it's in the background restricted.
+        if (mService.mInternal.getRestrictionLevel(app.info.packageName, app.userId)
+                < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
+            mService.updateLruProcessLocked(app, false, null);
+        }
+        // Make sure the oom adj score is updated before delivering the broadcast.
+        // Force an update, even if there are other pending requests, overall it still saves time,
+        // because time(updateOomAdj(N apps)) <= N * time(updateOomAdj(1 app)).
+        mService.enqueueOomAdjTargetLocked(app);
+        mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
+
+        // Tell the application to launch this receiver.
+        maybeReportBroadcastDispatchedEventLocked(r, r.curReceiver.applicationInfo.uid);
+        r.intent.setComponent(r.curComponent);
+
+        boolean started = false;
+        try {
+            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
+                    "Delivering to component " + r.curComponent
+                    + ": " + r);
+            mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
+                                      PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
+            thread.scheduleReceiver(prepareReceiverIntent(r.intent, r.curFilteredExtras),
+                    r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
+                    r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
+                    app.mState.getReportedProcState());
+            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
+                    "Process cur broadcast " + r + " DELIVERED for app " + app);
+            started = true;
+        } finally {
+            if (!started) {
+                if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
+                        "Process cur broadcast " + r + ": NOT STARTED!");
+                r.receiver = null;
+                r.curApp = null;
+                prr.removeCurReceiver(r);
+            }
+        }
+
+        // if something bad happens here, launch the app and try again
+        if (app.isKilled()) {
+            throw new RemoteException("app gets killed during broadcasting");
+        }
+    }
+
+    /**
+     * Called by ActivityManagerService to notify that the uid has process started, if there is any
+     * deferred BOOT_COMPLETED broadcast, the BroadcastDispatcher can dispatch the broadcast now.
+     * @param uid
+     */
+    public void updateUidReadyForBootCompletedBroadcastLocked(int uid) {
+        mDispatcher.updateUidReadyForBootCompletedBroadcastLocked(uid);
+        scheduleBroadcastsLocked();
+    }
+
+    public boolean sendPendingBroadcastsLocked(ProcessRecord app) {
+        boolean didSomething = false;
+        final BroadcastRecord br = mPendingBroadcast;
+        if (br != null && br.curApp.getPid() > 0 && br.curApp.getPid() == app.getPid()) {
+            if (br.curApp != app) {
+                Slog.e(TAG, "App mismatch when sending pending broadcast to "
+                        + app.processName + ", intended target is " + br.curApp.processName);
+                return false;
+            }
+            try {
+                mPendingBroadcast = null;
+                br.mIsReceiverAppRunning = false;
+                processCurBroadcastLocked(br, app);
+                didSomething = true;
+            } catch (Exception e) {
+                Slog.w(TAG, "Exception in new application when starting receiver "
+                        + br.curComponent.flattenToShortString(), e);
+                logBroadcastReceiverDiscardLocked(br);
+                finishReceiverLocked(br, br.resultCode, br.resultData,
+                        br.resultExtras, br.resultAbort, false);
+                scheduleBroadcastsLocked();
+                // We need to reset the state if we failed to start the receiver.
+                br.state = BroadcastRecord.IDLE;
+                throw new RuntimeException(e.getMessage());
+            }
+        }
+        return didSomething;
+    }
+
+    public void skipPendingBroadcastLocked(int pid) {
+        final BroadcastRecord br = mPendingBroadcast;
+        if (br != null && br.curApp.getPid() == pid) {
+            br.state = BroadcastRecord.IDLE;
+            br.nextReceiver = mPendingBroadcastRecvIndex;
+            mPendingBroadcast = null;
+            scheduleBroadcastsLocked();
+        }
+    }
+
+    // Skip the current receiver, if any, that is in flight to the given process
+    public void skipCurrentReceiverLocked(ProcessRecord app) {
+        BroadcastRecord r = null;
+        final BroadcastRecord curActive = mDispatcher.getActiveBroadcastLocked();
+        if (curActive != null && curActive.curApp == app) {
+            // confirmed: the current active broadcast is to the given app
+            r = curActive;
+        }
+
+        // If the current active broadcast isn't this BUT we're waiting for
+        // mPendingBroadcast to spin up the target app, that's what we use.
+        if (r == null && mPendingBroadcast != null && mPendingBroadcast.curApp == app) {
+            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
+                    "[" + mQueueName + "] skip & discard pending app " + r);
+            r = mPendingBroadcast;
+        }
+
+        if (r != null) {
+            skipReceiverLocked(r);
+        }
+    }
+
+    private void skipReceiverLocked(BroadcastRecord r) {
+        logBroadcastReceiverDiscardLocked(r);
+        finishReceiverLocked(r, r.resultCode, r.resultData,
+                r.resultExtras, r.resultAbort, false);
+        scheduleBroadcastsLocked();
+    }
+
+    public void scheduleBroadcastsLocked() {
+        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["
+                + mQueueName + "]: current="
+                + mBroadcastsScheduled);
+
+        if (mBroadcastsScheduled) {
+            return;
+        }
+        mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
+        mBroadcastsScheduled = true;
+    }
+
+    public BroadcastRecord getMatchingOrderedReceiver(IBinder receiver) {
+        BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
+        if (br != null && br.receiver == receiver) {
+            return br;
+        }
+        return null;
+    }
+
+    // > 0 only, no worry about "eventual" recycling
+    private int nextSplitTokenLocked() {
+        int next = mNextToken + 1;
+        if (next <= 0) {
+            next = 1;
+        }
+        mNextToken = next;
+        return next;
+    }
+
+    private void postActivityStartTokenRemoval(ProcessRecord app, BroadcastRecord r) {
+        // the receiver had run for less than allowed bg activity start timeout,
+        // so allow the process to still start activities from bg for some more time
+        String msgToken = (app.toShortString() + r.toString()).intern();
+        // first, if there exists a past scheduled request to remove this token, drop
+        // that request - we don't want the token to be swept from under our feet...
+        mHandler.removeCallbacksAndMessages(msgToken);
+        // ...then schedule the removal of the token after the extended timeout
+        mHandler.postAtTime(() -> {
+            synchronized (mService) {
+                app.removeAllowBackgroundActivityStartsToken(r);
+            }
+        }, msgToken, (r.receiverTime + mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT));
+    }
+
+    public boolean finishReceiverLocked(BroadcastRecord r, int resultCode,
+            String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
+        final int state = r.state;
+        final ActivityInfo receiver = r.curReceiver;
+        final long finishTime = SystemClock.uptimeMillis();
+        final long elapsed = finishTime - r.receiverTime;
+        r.state = BroadcastRecord.IDLE;
+        final int curIndex = r.nextReceiver - 1;
+        if (curIndex >= 0 && curIndex < r.receivers.size() && r.curApp != null) {
+            final Object curReceiver = r.receivers.get(curIndex);
+            FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, r.curApp.uid,
+                    r.callingUid == -1 ? Process.SYSTEM_UID : r.callingUid,
+                    ActivityManagerService.getShortAction(r.intent.getAction()),
+                    curReceiver instanceof BroadcastFilter
+                    ? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME
+                    : BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST,
+                    r.mIsReceiverAppRunning
+                    ? BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM
+                    : BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD,
+                    r.dispatchTime - r.enqueueTime,
+                    r.receiverTime - r.dispatchTime,
+                    finishTime - r.receiverTime);
+        }
+        if (state == BroadcastRecord.IDLE) {
+            Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
+        }
+        if (r.allowBackgroundActivityStarts && r.curApp != null) {
+            if (elapsed > mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT) {
+                // if the receiver has run for more than allowed bg activity start timeout,
+                // just remove the token for this process now and we're done
+                r.curApp.removeAllowBackgroundActivityStartsToken(r);
+            } else {
+                // It gets more time; post the removal to happen at the appropriate moment
+                postActivityStartTokenRemoval(r.curApp, r);
+            }
+        }
+        // If we're abandoning this broadcast before any receivers were actually spun up,
+        // nextReceiver is zero; in which case time-to-process bookkeeping doesn't apply.
+        if (r.nextReceiver > 0) {
+            r.duration[r.nextReceiver - 1] = elapsed;
+        }
+
+        // if this receiver was slow, impose deferral policy on the app.  This will kick in
+        // when processNextBroadcastLocked() next finds this uid as a receiver identity.
+        if (!r.timeoutExempt) {
+            // r.curApp can be null if finish has raced with process death - benign
+            // edge case, and we just ignore it because we're already cleaning up
+            // as expected.
+            if (r.curApp != null
+                    && mConstants.SLOW_TIME > 0 && elapsed > mConstants.SLOW_TIME) {
+                // Core system packages are exempt from deferral policy
+                if (!UserHandle.isCore(r.curApp.uid)) {
+                    if (DEBUG_BROADCAST_DEFERRAL) {
+                        Slog.i(TAG_BROADCAST, "Broadcast receiver " + (r.nextReceiver - 1)
+                                + " was slow: " + receiver + " br=" + r);
+                    }
+                    mDispatcher.startDeferring(r.curApp.uid);
+                } else {
+                    if (DEBUG_BROADCAST_DEFERRAL) {
+                        Slog.i(TAG_BROADCAST, "Core uid " + r.curApp.uid
+                                + " receiver was slow but not deferring: "
+                                + receiver + " br=" + r);
+                    }
+                }
+            }
+        } else {
+            if (DEBUG_BROADCAST_DEFERRAL) {
+                Slog.i(TAG_BROADCAST, "Finished broadcast " + r.intent.getAction()
+                        + " is exempt from deferral policy");
+            }
+        }
+
+        r.receiver = null;
+        r.intent.setComponent(null);
+        if (r.curApp != null && r.curApp.mReceivers.hasCurReceiver(r)) {
+            r.curApp.mReceivers.removeCurReceiver(r);
+            mService.enqueueOomAdjTargetLocked(r.curApp);
+        }
+        if (r.curFilter != null) {
+            r.curFilter.receiverList.curBroadcast = null;
+        }
+        r.curFilter = null;
+        r.curReceiver = null;
+        r.curApp = null;
+        r.curFilteredExtras = null;
+        mPendingBroadcast = null;
+
+        r.resultCode = resultCode;
+        r.resultData = resultData;
+        r.resultExtras = resultExtras;
+        if (resultAbort && (r.intent.getFlags()&Intent.FLAG_RECEIVER_NO_ABORT) == 0) {
+            r.resultAbort = resultAbort;
+        } else {
+            r.resultAbort = false;
+        }
+
+        // If we want to wait behind services *AND* we're finishing the head/
+        // active broadcast on its queue
+        if (waitForServices && r.curComponent != null && r.queue.isDelayBehindServices()
+                && r.queue.getActiveBroadcastLocked() == r) {
+            ActivityInfo nextReceiver;
+            if (r.nextReceiver < r.receivers.size()) {
+                Object obj = r.receivers.get(r.nextReceiver);
+                nextReceiver = (obj instanceof ActivityInfo) ? (ActivityInfo)obj : null;
+            } else {
+                nextReceiver = null;
+            }
+            // Don't do this if the next receive is in the same process as the current one.
+            if (receiver == null || nextReceiver == null
+                    || receiver.applicationInfo.uid != nextReceiver.applicationInfo.uid
+                    || !receiver.processName.equals(nextReceiver.processName)) {
+                // In this case, we are ready to process the next receiver for the current broadcast,
+                // but are on a queue that would like to wait for services to finish before moving
+                // on.  If there are background services currently starting, then we will go into a
+                // special state where we hold off on continuing this broadcast until they are done.
+                if (mService.mServices.hasBackgroundServicesLocked(r.userId)) {
+                    Slog.i(TAG, "Delay finish: " + r.curComponent.flattenToShortString());
+                    r.state = BroadcastRecord.WAITING_SERVICES;
+                    return false;
+                }
+            }
+        }
+
+        r.curComponent = null;
+
+        // We will process the next receiver right now if this is finishing
+        // an app receiver (which is always asynchronous) or after we have
+        // come back from calling a receiver.
+        return state == BroadcastRecord.APP_RECEIVE
+                || state == BroadcastRecord.CALL_DONE_RECEIVE;
+    }
+
+    public void backgroundServicesFinishedLocked(int userId) {
+        BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
+        if (br != null) {
+            if (br.userId == userId && br.state == BroadcastRecord.WAITING_SERVICES) {
+                Slog.i(TAG, "Resuming delayed broadcast");
+                br.curComponent = null;
+                br.state = BroadcastRecord.IDLE;
+                processNextBroadcastLocked(false, false);
+            }
+        }
+    }
+
+    public void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
+            Intent intent, int resultCode, String data, Bundle extras,
+            boolean ordered, boolean sticky, int sendingUser,
+            int receiverUid, int callingUid, long dispatchDelay,
+            long receiveDelay) throws RemoteException {
+        // Send the intent to the receiver asynchronously using one-way binder calls.
+        if (app != null) {
+            final IApplicationThread thread = app.getThread();
+            if (thread != null) {
+                // If we have an app thread, do the call through that so it is
+                // correctly ordered with other one-way calls.
+                try {
+                    thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
+                            data, extras, ordered, sticky, sendingUser,
+                            app.mState.getReportedProcState());
+                // TODO: Uncomment this when (b/28322359) is fixed and we aren't getting
+                // DeadObjectException when the process isn't actually dead.
+                //} catch (DeadObjectException ex) {
+                // Failed to call into the process.  It's dying so just let it die and move on.
+                //    throw ex;
+                } catch (RemoteException ex) {
+                    // Failed to call into the process. It's either dying or wedged. Kill it gently.
+                    synchronized (mService) {
+                        Slog.w(TAG, "Can't deliver broadcast to " + app.processName
+                                + " (pid " + app.getPid() + "). Crashing it.");
+                        app.scheduleCrashLocked("can't deliver broadcast",
+                                CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null);
+                    }
+                    throw ex;
+                }
+            } else {
+                // Application has died. Receiver doesn't exist.
+                throw new RemoteException("app.thread must not be null");
+            }
+        } else {
+            receiver.performReceive(intent, resultCode, data, extras, ordered,
+                    sticky, sendingUser);
+        }
+        if (!ordered) {
+            FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED,
+                    receiverUid == -1 ? Process.SYSTEM_UID : receiverUid,
+                    callingUid == -1 ? Process.SYSTEM_UID : callingUid,
+                    ActivityManagerService.getShortAction(intent.getAction()),
+                    BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME,
+                    BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
+                    dispatchDelay, receiveDelay, 0 /* finish_delay */);
+        }
+    }
+
+    private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
+            BroadcastFilter filter, boolean ordered, int index) {
+        boolean skip = mSkipPolicy.shouldSkip(r, filter);
+
+        // Filter packages in the intent extras, skipping delivery if none of the packages is
+        // visible to the receiver.
+        Bundle filteredExtras = null;
+        if (!skip && r.filterExtrasForReceiver != null) {
+            final Bundle extras = r.intent.getExtras();
+            if (extras != null) {
+                filteredExtras = r.filterExtrasForReceiver.apply(filter.receiverList.uid, extras);
+                if (filteredExtras == null) {
+                    if (DEBUG_BROADCAST) {
+                        Slog.v(TAG, "Skipping delivery to "
+                                + filter.receiverList.app
+                                + " : receiver is filtered by the package visibility");
+                    }
+                    skip = true;
+                }
+            }
+        }
+
+        if (skip) {
+            r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
+            return;
+        }
+
+        r.delivery[index] = BroadcastRecord.DELIVERY_DELIVERED;
+
+        // If this is not being sent as an ordered broadcast, then we
+        // don't want to touch the fields that keep track of the current
+        // state of ordered broadcasts.
+        if (ordered) {
+            r.receiver = filter.receiverList.receiver.asBinder();
+            r.curFilter = filter;
+            filter.receiverList.curBroadcast = r;
+            r.state = BroadcastRecord.CALL_IN_RECEIVE;
+            if (filter.receiverList.app != null) {
+                // Bump hosting application to no longer be in background
+                // scheduling class.  Note that we can't do that if there
+                // isn't an app...  but we can only be in that case for
+                // things that directly call the IActivityManager API, which
+                // are already core system stuff so don't matter for this.
+                r.curApp = filter.receiverList.app;
+                filter.receiverList.app.mReceivers.addCurReceiver(r);
+                mService.enqueueOomAdjTargetLocked(r.curApp);
+                mService.updateOomAdjPendingTargetsLocked(
+                        OOM_ADJ_REASON_START_RECEIVER);
+            }
+        } else if (filter.receiverList.app != null) {
+            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(filter.receiverList.app,
+                    OOM_ADJ_REASON_START_RECEIVER);
+        }
+
+        try {
+            if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
+                    "Delivering to " + filter + " : " + r);
+            if (filter.receiverList.app != null && filter.receiverList.app.isInFullBackup()) {
+                // Skip delivery if full backup in progress
+                // If it's an ordered broadcast, we need to continue to the next receiver.
+                if (ordered) {
+                    skipReceiverLocked(r);
+                }
+            } else {
+                r.receiverTime = SystemClock.uptimeMillis();
+                maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
+                maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
+                maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
+                performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
+                        prepareReceiverIntent(r.intent, filteredExtras), r.resultCode, r.resultData,
+                        r.resultExtras, r.ordered, r.initialSticky, r.userId,
+                        filter.receiverList.uid, r.callingUid,
+                        r.dispatchTime - r.enqueueTime,
+                        r.receiverTime - r.dispatchTime);
+                // parallel broadcasts are fire-and-forget, not bookended by a call to
+                // finishReceiverLocked(), so we manage their activity-start token here
+                if (filter.receiverList.app != null
+                        && r.allowBackgroundActivityStarts && !r.ordered) {
+                    postActivityStartTokenRemoval(filter.receiverList.app, r);
+                }
+            }
+            if (ordered) {
+                r.state = BroadcastRecord.CALL_DONE_RECEIVE;
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failure sending broadcast " + r.intent, e);
+            // Clean up ProcessRecord state related to this broadcast attempt
+            if (filter.receiverList.app != null) {
+                filter.receiverList.app.removeAllowBackgroundActivityStartsToken(r);
+                if (ordered) {
+                    filter.receiverList.app.mReceivers.removeCurReceiver(r);
+                    // Something wrong, its oom adj could be downgraded, but not in a hurry.
+                    mService.enqueueOomAdjTargetLocked(r.curApp);
+                }
+            }
+            // And BroadcastRecord state related to ordered delivery, if appropriate
+            if (ordered) {
+                r.receiver = null;
+                r.curFilter = null;
+                filter.receiverList.curBroadcast = null;
+            }
+        }
+    }
+
+    void maybeScheduleTempAllowlistLocked(int uid, BroadcastRecord r,
+            @Nullable BroadcastOptions brOptions) {
+        if (brOptions == null || brOptions.getTemporaryAppAllowlistDuration() <= 0) {
+            return;
+        }
+        long duration = brOptions.getTemporaryAppAllowlistDuration();
+        final @TempAllowListType int type = brOptions.getTemporaryAppAllowlistType();
+        final @ReasonCode int reasonCode = brOptions.getTemporaryAppAllowlistReasonCode();
+        final String reason = brOptions.getTemporaryAppAllowlistReason();
+
+        if (duration > Integer.MAX_VALUE) {
+            duration = Integer.MAX_VALUE;
+        }
+        // XXX ideally we should pause the broadcast until everything behind this is done,
+        // or else we will likely start dispatching the broadcast before we have opened
+        // access to the app (there is a lot of asynchronicity behind this).  It is probably
+        // not that big a deal, however, because the main purpose here is to allow apps
+        // to hold wake locks, and they will be able to acquire their wake lock immediately
+        // it just won't be enabled until we get through this work.
+        StringBuilder b = new StringBuilder();
+        b.append("broadcast:");
+        UserHandle.formatUid(b, r.callingUid);
+        b.append(":");
+        if (r.intent.getAction() != null) {
+            b.append(r.intent.getAction());
+        } else if (r.intent.getComponent() != null) {
+            r.intent.getComponent().appendShortString(b);
+        } else if (r.intent.getData() != null) {
+            b.append(r.intent.getData());
+        }
+        b.append(",reason:");
+        b.append(reason);
+        if (DEBUG_BROADCAST) {
+            Slog.v(TAG, "Broadcast temp allowlist uid=" + uid + " duration=" + duration
+                    + " type=" + type + " : " + b.toString());
+        }
+        mService.tempAllowlistUidLocked(uid, duration, reasonCode, b.toString(), type,
+                r.callingUid);
+    }
+
+    private void processNextBroadcast(boolean fromMsg) {
+        synchronized (mService) {
+            processNextBroadcastLocked(fromMsg, false);
+        }
+    }
+
+    private static Intent prepareReceiverIntent(@NonNull Intent originalIntent,
+            @Nullable Bundle filteredExtras) {
+        final Intent intent = new Intent(originalIntent);
+        if (filteredExtras != null) {
+            intent.replaceExtras(filteredExtras);
+        }
+        return intent;
+    }
+
+    public void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
+        BroadcastRecord r;
+
+        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "processNextBroadcast ["
+                + mQueueName + "]: "
+                + mParallelBroadcasts.size() + " parallel broadcasts; "
+                + mDispatcher.describeStateLocked());
+
+        mService.updateCpuStats();
+
+        if (fromMsg) {
+            mBroadcastsScheduled = false;
+        }
+
+        // First, deliver any non-serialized broadcasts right away.
+        while (mParallelBroadcasts.size() > 0) {
+            r = mParallelBroadcasts.remove(0);
+            r.dispatchTime = SystemClock.uptimeMillis();
+            r.dispatchRealTime = SystemClock.elapsedRealtime();
+            r.dispatchClockTime = System.currentTimeMillis();
+            r.mIsReceiverAppRunning = true;
+
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+                Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
+                    System.identityHashCode(r));
+                Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
+                    System.identityHashCode(r));
+            }
+
+            final int N = r.receivers.size();
+            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing parallel broadcast ["
+                    + mQueueName + "] " + r);
+            for (int i=0; i<N; i++) {
+                Object target = r.receivers.get(i);
+                if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
+                        "Delivering non-ordered on [" + mQueueName + "] to registered "
+                        + target + ": " + r);
+                deliverToRegisteredReceiverLocked(r,
+                        (BroadcastFilter) target, false, i);
+            }
+            addBroadcastToHistoryLocked(r);
+            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Done with parallel broadcast ["
+                    + mQueueName + "] " + r);
+        }
+
+        // Now take care of the next serialized one...
+
+        // If we are waiting for a process to come up to handle the next
+        // broadcast, then do nothing at this point.  Just in case, we
+        // check that the process we're waiting for still exists.
+        if (mPendingBroadcast != null) {
+            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
+                    "processNextBroadcast [" + mQueueName + "]: waiting for "
+                    + mPendingBroadcast.curApp);
+
+            boolean isDead;
+            if (mPendingBroadcast.curApp.getPid() > 0) {
+                synchronized (mService.mPidsSelfLocked) {
+                    ProcessRecord proc = mService.mPidsSelfLocked.get(
+                            mPendingBroadcast.curApp.getPid());
+                    isDead = proc == null || proc.mErrorState.isCrashing();
+                }
+            } else {
+                final ProcessRecord proc = mService.mProcessList.getProcessNamesLOSP().get(
+                        mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
+                isDead = proc == null || !proc.isPendingStart();
+            }
+            if (!isDead) {
+                // It's still alive, so keep waiting
+                return;
+            } else {
+                Slog.w(TAG, "pending app  ["
+                        + mQueueName + "]" + mPendingBroadcast.curApp
+                        + " died before responding to broadcast");
+                mPendingBroadcast.state = BroadcastRecord.IDLE;
+                mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
+                mPendingBroadcast = null;
+            }
+        }
+
+        boolean looped = false;
+
+        do {
+            final long now = SystemClock.uptimeMillis();
+            r = mDispatcher.getNextBroadcastLocked(now);
+
+            if (r == null) {
+                // No more broadcasts are deliverable right now, so all done!
+                mDispatcher.scheduleDeferralCheckLocked(false);
+                synchronized (mService.mAppProfiler.mProfilerLock) {
+                    mService.mAppProfiler.scheduleAppGcsLPf();
+                }
+                if (looped && !skipOomAdj) {
+                    // If we had finished the last ordered broadcast, then
+                    // make sure all processes have correct oom and sched
+                    // adjustments.
+                    mService.updateOomAdjPendingTargetsLocked(
+                            OOM_ADJ_REASON_START_RECEIVER);
+                }
+
+                // when we have no more ordered broadcast on this queue, stop logging
+                if (mService.mUserController.mBootCompleted && mLogLatencyMetrics) {
+                    mLogLatencyMetrics = false;
+                }
+
+                return;
+            }
+
+            boolean forceReceive = false;
+
+            // Ensure that even if something goes awry with the timeout
+            // detection, we catch "hung" broadcasts here, discard them,
+            // and continue to make progress.
+            //
+            // This is only done if the system is ready so that early-stage receivers
+            // don't get executed with timeouts; and of course other timeout-
+            // exempt broadcasts are ignored.
+            int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
+            if (mService.mProcessesReady && !r.timeoutExempt && r.dispatchTime > 0) {
+                if ((numReceivers > 0) &&
+                        (now > r.dispatchTime + (2 * mConstants.TIMEOUT * numReceivers))) {
+                    Slog.w(TAG, "Hung broadcast ["
+                            + mQueueName + "] discarded after timeout failure:"
+                            + " now=" + now
+                            + " dispatchTime=" + r.dispatchTime
+                            + " startTime=" + r.receiverTime
+                            + " intent=" + r.intent
+                            + " numReceivers=" + numReceivers
+                            + " nextReceiver=" + r.nextReceiver
+                            + " state=" + r.state);
+                    broadcastTimeoutLocked(false); // forcibly finish this broadcast
+                    forceReceive = true;
+                    r.state = BroadcastRecord.IDLE;
+                }
+            }
+
+            if (r.state != BroadcastRecord.IDLE) {
+                if (DEBUG_BROADCAST) Slog.d(TAG_BROADCAST,
+                        "processNextBroadcast("
+                        + mQueueName + ") called when not idle (state="
+                        + r.state + ")");
+                return;
+            }
+
+            // Is the current broadcast is done for any reason?
+            if (r.receivers == null || r.nextReceiver >= numReceivers
+                    || r.resultAbort || forceReceive) {
+                // Send the final result if requested
+                if (r.resultTo != null) {
+                    boolean sendResult = true;
+
+                    // if this was part of a split/deferral complex, update the refcount and only
+                    // send the completion when we clear all of them
+                    if (r.splitToken != 0) {
+                        int newCount = mSplitRefcounts.get(r.splitToken) - 1;
+                        if (newCount == 0) {
+                            // done!  clear out this record's bookkeeping and deliver
+                            if (DEBUG_BROADCAST_DEFERRAL) {
+                                Slog.i(TAG_BROADCAST,
+                                        "Sending broadcast completion for split token "
+                                        + r.splitToken + " : " + r.intent.getAction());
+                            }
+                            mSplitRefcounts.delete(r.splitToken);
+                        } else {
+                            // still have some split broadcast records in flight; update refcount
+                            // and hold off on the callback
+                            if (DEBUG_BROADCAST_DEFERRAL) {
+                                Slog.i(TAG_BROADCAST,
+                                        "Result refcount now " + newCount + " for split token "
+                                        + r.splitToken + " : " + r.intent.getAction()
+                                        + " - not sending completion yet");
+                            }
+                            sendResult = false;
+                            mSplitRefcounts.put(r.splitToken, newCount);
+                        }
+                    }
+                    if (sendResult) {
+                        if (r.callerApp != null) {
+                            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
+                                    r.callerApp, OOM_ADJ_REASON_FINISH_RECEIVER);
+                        }
+                        try {
+                            if (DEBUG_BROADCAST) {
+                                Slog.i(TAG_BROADCAST, "Finishing broadcast [" + mQueueName + "] "
+                                        + r.intent.getAction() + " app=" + r.callerApp);
+                            }
+                            if (r.dispatchTime == 0) {
+                                // The dispatch time here could be 0, in case it's a parallel
+                                // broadcast but it has a result receiver. Set it to now.
+                                r.dispatchTime = now;
+                            }
+                            r.mIsReceiverAppRunning = true;
+                            performReceiveLocked(r.callerApp, r.resultTo,
+                                    new Intent(r.intent), r.resultCode,
+                                    r.resultData, r.resultExtras, false, false, r.userId,
+                                    r.callingUid, r.callingUid,
+                                    r.dispatchTime - r.enqueueTime,
+                                    now - r.dispatchTime);
+                            logBootCompletedBroadcastCompletionLatencyIfPossible(r);
+                            // Set this to null so that the reference
+                            // (local and remote) isn't kept in the mBroadcastHistory.
+                            r.resultTo = null;
+                        } catch (RemoteException e) {
+                            r.resultTo = null;
+                            Slog.w(TAG, "Failure ["
+                                    + mQueueName + "] sending broadcast result of "
+                                    + r.intent, e);
+                        }
+                    }
+                }
+
+                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Cancelling BROADCAST_TIMEOUT_MSG");
+                cancelBroadcastTimeoutLocked();
+
+                if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
+                        "Finished with ordered broadcast " + r);
+
+                // ... and on to the next...
+                addBroadcastToHistoryLocked(r);
+                if (r.intent.getComponent() == null && r.intent.getPackage() == null
+                        && (r.intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+                    // This was an implicit broadcast... let's record it for posterity.
+                    mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
+                            r.manifestCount, r.manifestSkipCount, r.finishTime-r.dispatchTime);
+                }
+                mDispatcher.retireBroadcastLocked(r);
+                r = null;
+                looped = true;
+                continue;
+            }
+
+            // Check whether the next receiver is under deferral policy, and handle that
+            // accordingly.  If the current broadcast was already part of deferred-delivery
+            // tracking, we know that it must now be deliverable as-is without re-deferral.
+            if (!r.deferred) {
+                final int receiverUid = r.getReceiverUid(r.receivers.get(r.nextReceiver));
+                if (mDispatcher.isDeferringLocked(receiverUid)) {
+                    if (DEBUG_BROADCAST_DEFERRAL) {
+                        Slog.i(TAG_BROADCAST, "Next receiver in " + r + " uid " + receiverUid
+                                + " at " + r.nextReceiver + " is under deferral");
+                    }
+                    // If this is the only (remaining) receiver in the broadcast, "splitting"
+                    // doesn't make sense -- just defer it as-is and retire it as the
+                    // currently active outgoing broadcast.
+                    BroadcastRecord defer;
+                    if (r.nextReceiver + 1 == numReceivers) {
+                        if (DEBUG_BROADCAST_DEFERRAL) {
+                            Slog.i(TAG_BROADCAST, "Sole receiver of " + r
+                                    + " is under deferral; setting aside and proceeding");
+                        }
+                        defer = r;
+                        mDispatcher.retireBroadcastLocked(r);
+                    } else {
+                        // Nontrivial case; split out 'uid's receivers to a new broadcast record
+                        // and defer that, then loop and pick up continuing delivery of the current
+                        // record (now absent those receivers).
+
+                        // The split operation is guaranteed to match at least at 'nextReceiver'
+                        defer = r.splitRecipientsLocked(receiverUid, r.nextReceiver);
+                        if (DEBUG_BROADCAST_DEFERRAL) {
+                            Slog.i(TAG_BROADCAST, "Post split:");
+                            Slog.i(TAG_BROADCAST, "Original broadcast receivers:");
+                            for (int i = 0; i < r.receivers.size(); i++) {
+                                Slog.i(TAG_BROADCAST, "  " + r.receivers.get(i));
+                            }
+                            Slog.i(TAG_BROADCAST, "Split receivers:");
+                            for (int i = 0; i < defer.receivers.size(); i++) {
+                                Slog.i(TAG_BROADCAST, "  " + defer.receivers.get(i));
+                            }
+                        }
+                        // Track completion refcount as well if relevant
+                        if (r.resultTo != null) {
+                            int token = r.splitToken;
+                            if (token == 0) {
+                                // first split of this record; refcount for 'r' and 'deferred'
+                                r.splitToken = defer.splitToken = nextSplitTokenLocked();
+                                mSplitRefcounts.put(r.splitToken, 2);
+                                if (DEBUG_BROADCAST_DEFERRAL) {
+                                    Slog.i(TAG_BROADCAST,
+                                            "Broadcast needs split refcount; using new token "
+                                            + r.splitToken);
+                                }
+                            } else {
+                                // new split from an already-refcounted situation; increment count
+                                final int curCount = mSplitRefcounts.get(token);
+                                if (DEBUG_BROADCAST_DEFERRAL) {
+                                    if (curCount == 0) {
+                                        Slog.wtf(TAG_BROADCAST,
+                                                "Split refcount is zero with token for " + r);
+                                    }
+                                }
+                                mSplitRefcounts.put(token, curCount + 1);
+                                if (DEBUG_BROADCAST_DEFERRAL) {
+                                    Slog.i(TAG_BROADCAST, "New split count for token " + token
+                                            + " is " + (curCount + 1));
+                                }
+                            }
+                        }
+                    }
+                    mDispatcher.addDeferredBroadcast(receiverUid, defer);
+                    r = null;
+                    looped = true;
+                    continue;
+                }
+            }
+        } while (r == null);
+
+        // Get the next receiver...
+        int recIdx = r.nextReceiver++;
+
+        // Keep track of when this receiver started, and make sure there
+        // is a timeout message pending to kill it if need be.
+        r.receiverTime = SystemClock.uptimeMillis();
+        if (recIdx == 0) {
+            r.dispatchTime = r.receiverTime;
+            r.dispatchRealTime = SystemClock.elapsedRealtime();
+            r.dispatchClockTime = System.currentTimeMillis();
+
+            if (mLogLatencyMetrics) {
+                FrameworkStatsLog.write(
+                        FrameworkStatsLog.BROADCAST_DISPATCH_LATENCY_REPORTED,
+                        r.dispatchClockTime - r.enqueueClockTime);
+            }
+
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+                Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
+                    System.identityHashCode(r));
+                Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
+                    System.identityHashCode(r));
+            }
+            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing ordered broadcast ["
+                    + mQueueName + "] " + r);
+        }
+        if (! mPendingBroadcastTimeoutMessage) {
+            long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
+            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
+                    "Submitting BROADCAST_TIMEOUT_MSG ["
+                    + mQueueName + "] for " + r + " at " + timeoutTime);
+            setBroadcastTimeoutLocked(timeoutTime);
+        }
+
+        final BroadcastOptions brOptions = r.options;
+        final Object nextReceiver = r.receivers.get(recIdx);
+
+        if (nextReceiver instanceof BroadcastFilter) {
+            // Simple case: this is a registered receiver who gets
+            // a direct call.
+            BroadcastFilter filter = (BroadcastFilter)nextReceiver;
+            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
+                    "Delivering ordered ["
+                    + mQueueName + "] to registered "
+                    + filter + ": " + r);
+            r.mIsReceiverAppRunning = true;
+            deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
+            if (r.receiver == null || !r.ordered) {
+                // The receiver has already finished, so schedule to
+                // process the next one.
+                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Quick finishing ["
+                        + mQueueName + "]: ordered="
+                        + r.ordered + " receiver=" + r.receiver);
+                r.state = BroadcastRecord.IDLE;
+                scheduleBroadcastsLocked();
+            } else {
+                if (filter.receiverList != null) {
+                    maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
+                    // r is guaranteed ordered at this point, so we know finishReceiverLocked()
+                    // will get a callback and handle the activity start token lifecycle.
+                }
+            }
+            return;
+        }
+
+        // Hard case: need to instantiate the receiver, possibly
+        // starting its application process to host it.
+
+        final ResolveInfo info =
+            (ResolveInfo)nextReceiver;
+        final ComponentName component = new ComponentName(
+                info.activityInfo.applicationInfo.packageName,
+                info.activityInfo.name);
+        final int receiverUid = info.activityInfo.applicationInfo.uid;
+
+        final String targetProcess = info.activityInfo.processName;
+        final ProcessRecord app = mService.getProcessRecordLocked(targetProcess,
+                info.activityInfo.applicationInfo.uid);
+
+        boolean skip = mSkipPolicy.shouldSkip(r, info);
+
+        // Filter packages in the intent extras, skipping delivery if none of the packages is
+        // visible to the receiver.
+        Bundle filteredExtras = null;
+        if (!skip && r.filterExtrasForReceiver != null) {
+            final Bundle extras = r.intent.getExtras();
+            if (extras != null) {
+                filteredExtras = r.filterExtrasForReceiver.apply(receiverUid, extras);
+                if (filteredExtras == null) {
+                    if (DEBUG_BROADCAST) {
+                        Slog.v(TAG, "Skipping delivery to "
+                                + info.activityInfo.packageName + " / " + receiverUid
+                                + " : receiver is filtered by the package visibility");
+                    }
+                    skip = true;
+                }
+            }
+        }
+
+        if (skip) {
+            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
+                    "Skipping delivery of ordered [" + mQueueName + "] "
+                    + r + " for reason described above");
+            r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED;
+            r.receiver = null;
+            r.curFilter = null;
+            r.state = BroadcastRecord.IDLE;
+            r.manifestSkipCount++;
+            scheduleBroadcastsLocked();
+            return;
+        }
+        r.manifestCount++;
+
+        r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;
+        r.state = BroadcastRecord.APP_RECEIVE;
+        r.curComponent = component;
+        r.curReceiver = info.activityInfo;
+        r.curFilteredExtras = filteredExtras;
+        if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) {
+            Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, "
+                    + info.activityInfo + ", callingUid = " + r.callingUid + ", uid = "
+                    + receiverUid);
+        }
+        final boolean isActivityCapable =
+                (brOptions != null && brOptions.getTemporaryAppAllowlistDuration() > 0);
+        maybeScheduleTempAllowlistLocked(receiverUid, r, brOptions);
+
+        // Report that a component is used for explicit broadcasts.
+        if (r.intent.getComponent() != null && r.curComponent != null
+                && !TextUtils.equals(r.curComponent.getPackageName(), r.callerPackage)) {
+            mService.mUsageStatsService.reportEvent(
+                    r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED);
+        }
+
+        // Broadcast is being executed, its package can't be stopped.
+        try {
+            AppGlobals.getPackageManager().setPackageStoppedState(
+                    r.curComponent.getPackageName(), false, r.userId);
+        } catch (RemoteException e) {
+        } catch (IllegalArgumentException e) {
+            Slog.w(TAG, "Failed trying to unstop package "
+                    + r.curComponent.getPackageName() + ": " + e);
+        }
+
+        // Is this receiver's application already running?
+        if (app != null && app.getThread() != null && !app.isKilled()) {
+            try {
+                app.addPackage(info.activityInfo.packageName,
+                        info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
+                maybeAddAllowBackgroundActivityStartsToken(app, r);
+                r.mIsReceiverAppRunning = true;
+                processCurBroadcastLocked(r, app);
+                return;
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Exception when sending broadcast to "
+                      + r.curComponent, e);
+            } catch (RuntimeException e) {
+                Slog.wtf(TAG, "Failed sending broadcast to "
+                        + r.curComponent + " with " + r.intent, e);
+                // If some unexpected exception happened, just skip
+                // this broadcast.  At this point we are not in the call
+                // from a client, so throwing an exception out from here
+                // will crash the entire system instead of just whoever
+                // sent the broadcast.
+                logBroadcastReceiverDiscardLocked(r);
+                finishReceiverLocked(r, r.resultCode, r.resultData,
+                        r.resultExtras, r.resultAbort, false);
+                scheduleBroadcastsLocked();
+                // We need to reset the state if we failed to start the receiver.
+                r.state = BroadcastRecord.IDLE;
+                return;
+            }
+
+            // If a dead object exception was thrown -- fall through to
+            // restart the application.
+        }
+
+        // Not running -- get it started, to be executed when the app comes up.
+        if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
+                "Need to start app ["
+                + mQueueName + "] " + targetProcess + " for broadcast " + r);
+        r.curApp = mService.startProcessLocked(targetProcess,
+                info.activityInfo.applicationInfo, true,
+                r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
+                new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
+                        r.intent.getAction(), getHostingRecordTriggerType(r)),
+                isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
+                (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
+        if (r.curApp == null) {
+            // Ah, this recipient is unavailable.  Finish it if necessary,
+            // and mark the broadcast record as ready for the next.
+            Slog.w(TAG, "Unable to launch app "
+                    + info.activityInfo.applicationInfo.packageName + "/"
+                    + receiverUid + " for broadcast "
+                    + r.intent + ": process is bad");
+            logBroadcastReceiverDiscardLocked(r);
+            finishReceiverLocked(r, r.resultCode, r.resultData,
+                    r.resultExtras, r.resultAbort, false);
+            scheduleBroadcastsLocked();
+            r.state = BroadcastRecord.IDLE;
+            return;
+        }
+
+        maybeAddAllowBackgroundActivityStartsToken(r.curApp, r);
+        mPendingBroadcast = r;
+        mPendingBroadcastRecvIndex = recIdx;
+    }
+
+    private String getHostingRecordTriggerType(BroadcastRecord r) {
+        if (r.alarm) {
+            return HostingRecord.TRIGGER_TYPE_ALARM;
+        } else if (r.pushMessage) {
+            return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE;
+        } else if (r.pushMessageOverQuota) {
+            return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
+        }
+        return HostingRecord.TRIGGER_TYPE_UNKNOWN;
+    }
+
+    @Nullable
+    private String getTargetPackage(BroadcastRecord r) {
+        if (r.intent == null) {
+            return null;
+        }
+        if (r.intent.getPackage() != null) {
+            return r.intent.getPackage();
+        } else if (r.intent.getComponent() != null) {
+            return r.intent.getComponent().getPackageName();
+        }
+        return null;
+    }
+
+    private void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) {
+        // Only log after last receiver.
+        // In case of split BOOT_COMPLETED broadcast, make sure only call this method on the
+        // last BroadcastRecord of the split broadcast which has non-null resultTo.
+        final int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
+        if (r.nextReceiver < numReceivers) {
+            return;
+        }
+        final String action = r.intent.getAction();
+        int event = 0;
+        if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
+            event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
+        } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+            event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
+        }
+        if (event != 0) {
+            final int dispatchLatency = (int)(r.dispatchTime - r.enqueueTime);
+            final int completeLatency = (int)
+                    (SystemClock.uptimeMillis() - r.enqueueTime);
+            final int dispatchRealLatency = (int)(r.dispatchRealTime - r.enqueueRealTime);
+            final int completeRealLatency = (int)
+                    (SystemClock.elapsedRealtime() - r.enqueueRealTime);
+            int userType = FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
+            // This method is called very infrequently, no performance issue we call
+            // LocalServices.getService() here.
+            final UserManagerInternal umInternal = LocalServices.getService(
+                    UserManagerInternal.class);
+            final UserInfo userInfo = umInternal.getUserInfo(r.userId);
+            if (userInfo != null) {
+                userType = UserManager.getUserTypeForStatsd(userInfo.userType);
+            }
+            Slog.i(TAG_BROADCAST,
+                    "BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:"
+                            + action
+                            + " dispatchLatency:" + dispatchLatency
+                            + " completeLatency:" + completeLatency
+                            + " dispatchRealLatency:" + dispatchRealLatency
+                            + " completeRealLatency:" + completeRealLatency
+                            + " receiversSize:" + numReceivers
+                            + " userId:" + r.userId
+                            + " userType:" + (userInfo != null? userInfo.userType : null));
+            FrameworkStatsLog.write(
+                    BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED,
+                    event,
+                    dispatchLatency,
+                    completeLatency,
+                    dispatchRealLatency,
+                    completeRealLatency,
+                    r.userId,
+                    userType);
+        }
+    }
+
+    private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r, int targetUid) {
+        if (r.options == null || r.options.getIdForResponseEvent() <= 0) {
+            return;
+        }
+        final String targetPackage = getTargetPackage(r);
+        // Ignore non-explicit broadcasts
+        if (targetPackage == null) {
+            return;
+        }
+        getUsageStatsManagerInternal().reportBroadcastDispatched(
+                r.callingUid, targetPackage, UserHandle.of(r.userId),
+                r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime(),
+                mService.getUidStateLocked(targetUid));
+    }
+
+    @NonNull
+    private UsageStatsManagerInternal getUsageStatsManagerInternal() {
+        final UsageStatsManagerInternal usageStatsManagerInternal =
+                LocalServices.getService(UsageStatsManagerInternal.class);
+        return usageStatsManagerInternal;
+    }
+
+    private void maybeAddAllowBackgroundActivityStartsToken(ProcessRecord proc, BroadcastRecord r) {
+        if (r == null || proc == null || !r.allowBackgroundActivityStarts) {
+            return;
+        }
+        String msgToken = (proc.toShortString() + r.toString()).intern();
+        // first, if there exists a past scheduled request to remove this token, drop
+        // that request - we don't want the token to be swept from under our feet...
+        mHandler.removeCallbacksAndMessages(msgToken);
+        // ...then add the token
+        proc.addOrUpdateAllowBackgroundActivityStartsToken(r, r.mBackgroundActivityStartsToken);
+    }
+
+    final void setBroadcastTimeoutLocked(long timeoutTime) {
+        if (! mPendingBroadcastTimeoutMessage) {
+            Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
+            mHandler.sendMessageAtTime(msg, timeoutTime);
+            mPendingBroadcastTimeoutMessage = true;
+        }
+    }
+
+    final void cancelBroadcastTimeoutLocked() {
+        if (mPendingBroadcastTimeoutMessage) {
+            mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
+            mPendingBroadcastTimeoutMessage = false;
+        }
+    }
+
+    final void broadcastTimeoutLocked(boolean fromMsg) {
+        if (fromMsg) {
+            mPendingBroadcastTimeoutMessage = false;
+        }
+
+        if (mDispatcher.isEmpty() || mDispatcher.getActiveBroadcastLocked() == null) {
+            return;
+        }
+
+        long now = SystemClock.uptimeMillis();
+        BroadcastRecord r = mDispatcher.getActiveBroadcastLocked();
+        if (fromMsg) {
+            if (!mService.mProcessesReady) {
+                // Only process broadcast timeouts if the system is ready; some early
+                // broadcasts do heavy work setting up system facilities
+                return;
+            }
+
+            // If the broadcast is generally exempt from timeout tracking, we're done
+            if (r.timeoutExempt) {
+                if (DEBUG_BROADCAST) {
+                    Slog.i(TAG_BROADCAST, "Broadcast timeout but it's exempt: "
+                            + r.intent.getAction());
+                }
+                return;
+            }
+
+            long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
+            if (timeoutTime > now) {
+                // We can observe premature timeouts because we do not cancel and reset the
+                // broadcast timeout message after each receiver finishes.  Instead, we set up
+                // an initial timeout then kick it down the road a little further as needed
+                // when it expires.
+                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
+                        "Premature timeout ["
+                        + mQueueName + "] @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for "
+                        + timeoutTime);
+                setBroadcastTimeoutLocked(timeoutTime);
+                return;
+            }
+        }
+
+        if (r.state == BroadcastRecord.WAITING_SERVICES) {
+            // In this case the broadcast had already finished, but we had decided to wait
+            // for started services to finish as well before going on.  So if we have actually
+            // waited long enough time timeout the broadcast, let's give up on the whole thing
+            // and just move on to the next.
+            Slog.i(TAG, "Waited long enough for: " + (r.curComponent != null
+                    ? r.curComponent.flattenToShortString() : "(null)"));
+            r.curComponent = null;
+            r.state = BroadcastRecord.IDLE;
+            processNextBroadcastLocked(false, false);
+            return;
+        }
+
+        // If the receiver app is being debugged we quietly ignore unresponsiveness, just
+        // tidying up and moving on to the next broadcast without crashing or ANRing this
+        // app just because it's stopped at a breakpoint.
+        final boolean debugging = (r.curApp != null && r.curApp.isDebugging());
+
+        long timeoutDurationMs = now - r.receiverTime;
+        Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r.receiver
+                + ", started " + timeoutDurationMs + "ms ago");
+        r.receiverTime = now;
+        if (!debugging) {
+            r.anrCount++;
+        }
+
+        ProcessRecord app = null;
+        TimeoutRecord timeoutRecord = null;
+
+        Object curReceiver;
+        if (r.nextReceiver > 0) {
+            curReceiver = r.receivers.get(r.nextReceiver-1);
+            r.delivery[r.nextReceiver-1] = BroadcastRecord.DELIVERY_TIMEOUT;
+        } else {
+            curReceiver = r.curReceiver;
+        }
+        Slog.w(TAG, "Receiver during timeout of " + r + " : " + curReceiver);
+        logBroadcastReceiverDiscardLocked(r);
+        if (curReceiver != null && curReceiver instanceof BroadcastFilter) {
+            BroadcastFilter bf = (BroadcastFilter)curReceiver;
+            if (bf.receiverList.pid != 0
+                    && bf.receiverList.pid != ActivityManagerService.MY_PID) {
+                synchronized (mService.mPidsSelfLocked) {
+                    app = mService.mPidsSelfLocked.get(
+                            bf.receiverList.pid);
+                }
+            }
+        } else {
+            app = r.curApp;
+        }
+
+        if (app != null) {
+            String anrMessage =
+                    "Broadcast of " + r.intent.toString() + ", waited " + timeoutDurationMs
+                            + "ms";
+            timeoutRecord = TimeoutRecord.forBroadcastReceiver(anrMessage);
+        }
+
+        if (mPendingBroadcast == r) {
+            mPendingBroadcast = null;
+        }
+
+        // Move on to the next receiver.
+        finishReceiverLocked(r, r.resultCode, r.resultData,
+                r.resultExtras, r.resultAbort, false);
+        scheduleBroadcastsLocked();
+
+        if (!debugging && timeoutRecord != null) {
+            mService.mAnrHelper.appNotResponding(app, timeoutRecord);
+        }
+    }
+
+    private final int ringAdvance(int x, final int increment, final int ringSize) {
+        x += increment;
+        if (x < 0) return (ringSize - 1);
+        else if (x >= ringSize) return 0;
+        else return x;
+    }
+
+    private final void addBroadcastToHistoryLocked(BroadcastRecord original) {
+        if (original.callingUid < 0) {
+            // This was from a registerReceiver() call; ignore it.
+            return;
+        }
+        original.finishTime = SystemClock.uptimeMillis();
+
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                createBroadcastTraceTitle(original, BroadcastRecord.DELIVERY_DELIVERED),
+                System.identityHashCode(original));
+        }
+
+        final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
+        final String callerPackage = info != null ? info.packageName : original.callerPackage;
+        if (callerPackage != null) {
+            mService.mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
+                    original.callingUid, 0, callerPackage).sendToTarget();
+        }
+
+        // Note sometimes (only for sticky broadcasts?) we reuse BroadcastRecords,
+        // So don't change the incoming record directly.
+        final BroadcastRecord historyRecord = original.maybeStripForHistory();
+
+        mBroadcastHistory[mHistoryNext] = historyRecord;
+        mHistoryNext = ringAdvance(mHistoryNext, 1, MAX_BROADCAST_HISTORY);
+
+        mBroadcastSummaryHistory[mSummaryHistoryNext] = historyRecord.intent;
+        mSummaryHistoryEnqueueTime[mSummaryHistoryNext] = historyRecord.enqueueClockTime;
+        mSummaryHistoryDispatchTime[mSummaryHistoryNext] = historyRecord.dispatchClockTime;
+        mSummaryHistoryFinishTime[mSummaryHistoryNext] = System.currentTimeMillis();
+        mSummaryHistoryNext = ringAdvance(mSummaryHistoryNext, 1, MAX_BROADCAST_SUMMARY_HISTORY);
+    }
+
+    public boolean cleanupDisabledPackageReceiversLocked(
+            String packageName, Set<String> filterByClasses, int userId, boolean doit) {
+        boolean didSomething = false;
+        for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
+            didSomething |= mParallelBroadcasts.get(i).cleanupDisabledPackageReceiversLocked(
+                    packageName, filterByClasses, userId, doit);
+            if (!doit && didSomething) {
+                return true;
+            }
+        }
+
+        didSomething |= mDispatcher.cleanupDisabledPackageReceiversLocked(packageName,
+                filterByClasses, userId, doit);
+
+        return didSomething;
+    }
+
+    final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) {
+        final int logIndex = r.nextReceiver - 1;
+        if (logIndex >= 0 && logIndex < r.receivers.size()) {
+            Object curReceiver = r.receivers.get(logIndex);
+            if (curReceiver instanceof BroadcastFilter) {
+                BroadcastFilter bf = (BroadcastFilter) curReceiver;
+                EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER,
+                        bf.owningUserId, System.identityHashCode(r),
+                        r.intent.getAction(), logIndex, System.identityHashCode(bf));
+            } else {
+                ResolveInfo ri = (ResolveInfo) curReceiver;
+                EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
+                        UserHandle.getUserId(ri.activityInfo.applicationInfo.uid),
+                        System.identityHashCode(r), r.intent.getAction(), logIndex, ri.toString());
+            }
+        } else {
+            if (logIndex < 0) Slog.w(TAG,
+                    "Discarding broadcast before first receiver is invoked: " + r);
+            EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
+                    -1, System.identityHashCode(r),
+                    r.intent.getAction(),
+                    r.nextReceiver,
+                    "NONE");
+        }
+    }
+
+    private String createBroadcastTraceTitle(BroadcastRecord record, int state) {
+        return formatSimple("Broadcast %s from %s (%s) %s",
+                state == BroadcastRecord.DELIVERY_PENDING ? "in queue" : "dispatched",
+                record.callerPackage == null ? "" : record.callerPackage,
+                record.callerApp == null ? "process unknown" : record.callerApp.toShortString(),
+                record.intent == null ? "" : record.intent.getAction());
+    }
+
+    public boolean isIdle() {
+        return mParallelBroadcasts.isEmpty() && mDispatcher.isIdle()
+                && (mPendingBroadcast == null);
+    }
+
+    public void flush() {
+        cancelDeferrals();
+    }
+
+    // Used by wait-for-broadcast-idle : fast-forward all current deferrals to
+    // be immediately deliverable.
+    public void cancelDeferrals() {
+        synchronized (mService) {
+            mDispatcher.cancelDeferralsLocked();
+            scheduleBroadcastsLocked();
+        }
+    }
+
+    public String describeState() {
+        synchronized (mService) {
+            return mParallelBroadcasts.size() + " parallel; "
+                    + mDispatcher.describeStateLocked();
+        }
+    }
+
+    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName);
+        int N;
+        N = mParallelBroadcasts.size();
+        for (int i = N - 1; i >= 0; i--) {
+            mParallelBroadcasts.get(i).dumpDebug(proto, BroadcastQueueProto.PARALLEL_BROADCASTS);
+        }
+        mDispatcher.dumpDebug(proto, BroadcastQueueProto.ORDERED_BROADCASTS);
+        if (mPendingBroadcast != null) {
+            mPendingBroadcast.dumpDebug(proto, BroadcastQueueProto.PENDING_BROADCAST);
+        }
+
+        int lastIndex = mHistoryNext;
+        int ringIndex = lastIndex;
+        do {
+            // increasing index = more recent entry, and we want to print the most
+            // recent first and work backwards, so we roll through the ring backwards.
+            ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_HISTORY);
+            BroadcastRecord r = mBroadcastHistory[ringIndex];
+            if (r != null) {
+                r.dumpDebug(proto, BroadcastQueueProto.HISTORICAL_BROADCASTS);
+            }
+        } while (ringIndex != lastIndex);
+
+        lastIndex = ringIndex = mSummaryHistoryNext;
+        do {
+            ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
+            Intent intent = mBroadcastSummaryHistory[ringIndex];
+            if (intent == null) {
+                continue;
+            }
+            long summaryToken = proto.start(BroadcastQueueProto.HISTORICAL_BROADCASTS_SUMMARY);
+            intent.dumpDebug(proto, BroadcastQueueProto.BroadcastSummary.INTENT,
+                    false, true, true, false);
+            proto.write(BroadcastQueueProto.BroadcastSummary.ENQUEUE_CLOCK_TIME_MS,
+                    mSummaryHistoryEnqueueTime[ringIndex]);
+            proto.write(BroadcastQueueProto.BroadcastSummary.DISPATCH_CLOCK_TIME_MS,
+                    mSummaryHistoryDispatchTime[ringIndex]);
+            proto.write(BroadcastQueueProto.BroadcastSummary.FINISH_CLOCK_TIME_MS,
+                    mSummaryHistoryFinishTime[ringIndex]);
+            proto.end(summaryToken);
+        } while (ringIndex != lastIndex);
+        proto.end(token);
+    }
+
+    public boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll, String dumpPackage, boolean needSep) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+        if (!mParallelBroadcasts.isEmpty() || !mDispatcher.isEmpty()
+                || mPendingBroadcast != null) {
+            boolean printed = false;
+            for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
+                BroadcastRecord br = mParallelBroadcasts.get(i);
+                if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) {
+                    continue;
+                }
+                if (!printed) {
+                    if (needSep) {
+                        pw.println();
+                    }
+                    needSep = true;
+                    printed = true;
+                    pw.println("  Active broadcasts [" + mQueueName + "]:");
+                }
+                pw.println("  Active Broadcast " + mQueueName + " #" + i + ":");
+                br.dump(pw, "    ", sdf);
+            }
+
+            mDispatcher.dumpLocked(pw, dumpPackage, mQueueName, sdf);
+
+            if (dumpPackage == null || (mPendingBroadcast != null
+                    && dumpPackage.equals(mPendingBroadcast.callerPackage))) {
+                pw.println();
+                pw.println("  Pending broadcast [" + mQueueName + "]:");
+                if (mPendingBroadcast != null) {
+                    mPendingBroadcast.dump(pw, "    ", sdf);
+                } else {
+                    pw.println("    (null)");
+                }
+                needSep = true;
+            }
+        }
+
+        mConstants.dump(pw);
+
+        int i;
+        boolean printed = false;
+
+        i = -1;
+        int lastIndex = mHistoryNext;
+        int ringIndex = lastIndex;
+        do {
+            // increasing index = more recent entry, and we want to print the most
+            // recent first and work backwards, so we roll through the ring backwards.
+            ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_HISTORY);
+            BroadcastRecord r = mBroadcastHistory[ringIndex];
+            if (r == null) {
+                continue;
+            }
+
+            i++; // genuine record of some sort even if we're filtering it out
+            if (dumpPackage != null && !dumpPackage.equals(r.callerPackage)) {
+                continue;
+            }
+            if (!printed) {
+                if (needSep) {
+                    pw.println();
+                }
+                needSep = true;
+                pw.println("  Historical broadcasts [" + mQueueName + "]:");
+                printed = true;
+            }
+            if (dumpAll) {
+                pw.print("  Historical Broadcast " + mQueueName + " #");
+                        pw.print(i); pw.println(":");
+                r.dump(pw, "    ", sdf);
+            } else {
+                pw.print("  #"); pw.print(i); pw.print(": "); pw.println(r);
+                pw.print("    ");
+                pw.println(r.intent.toShortString(false, true, true, false));
+                if (r.targetComp != null && r.targetComp != r.intent.getComponent()) {
+                    pw.print("    targetComp: "); pw.println(r.targetComp.toShortString());
+                }
+                Bundle bundle = r.intent.getExtras();
+                if (bundle != null) {
+                    pw.print("    extras: "); pw.println(bundle.toString());
+                }
+            }
+        } while (ringIndex != lastIndex);
+
+        if (dumpPackage == null) {
+            lastIndex = ringIndex = mSummaryHistoryNext;
+            if (dumpAll) {
+                printed = false;
+                i = -1;
+            } else {
+                // roll over the 'i' full dumps that have already been issued
+                for (int j = i;
+                        j > 0 && ringIndex != lastIndex;) {
+                    ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
+                    BroadcastRecord r = mBroadcastHistory[ringIndex];
+                    if (r == null) {
+                        continue;
+                    }
+                    j--;
+                }
+            }
+            // done skipping; dump the remainder of the ring. 'i' is still the ordinal within
+            // the overall broadcast history.
+            do {
+                ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
+                Intent intent = mBroadcastSummaryHistory[ringIndex];
+                if (intent == null) {
+                    continue;
+                }
+                if (!printed) {
+                    if (needSep) {
+                        pw.println();
+                    }
+                    needSep = true;
+                    pw.println("  Historical broadcasts summary [" + mQueueName + "]:");
+                    printed = true;
+                }
+                if (!dumpAll && i >= 50) {
+                    pw.println("  ...");
+                    break;
+                }
+                i++;
+                pw.print("  #"); pw.print(i); pw.print(": ");
+                pw.println(intent.toShortString(false, true, true, false));
+                pw.print("    ");
+                TimeUtils.formatDuration(mSummaryHistoryDispatchTime[ringIndex]
+                        - mSummaryHistoryEnqueueTime[ringIndex], pw);
+                pw.print(" dispatch ");
+                TimeUtils.formatDuration(mSummaryHistoryFinishTime[ringIndex]
+                        - mSummaryHistoryDispatchTime[ringIndex], pw);
+                pw.println(" finish");
+                pw.print("    enq=");
+                pw.print(sdf.format(new Date(mSummaryHistoryEnqueueTime[ringIndex])));
+                pw.print(" disp=");
+                pw.print(sdf.format(new Date(mSummaryHistoryDispatchTime[ringIndex])));
+                pw.print(" fin=");
+                pw.println(sdf.format(new Date(mSummaryHistoryFinishTime[ringIndex])));
+                Bundle bundle = intent.getExtras();
+                if (bundle != null) {
+                    pw.print("    extras: "); pw.println(bundle.toString());
+                }
+            } while (ringIndex != lastIndex);
+        }
+
+        return needSep;
+    }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
new file mode 100644
index 0000000..9569848
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
@@ -0,0 +1,715 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
+import static com.android.server.am.ActivityManagerService.checkComponentPermission;
+import static com.android.server.am.BroadcastQueue.TAG;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.permission.IPermissionManager;
+import android.util.Slog;
+
+import com.android.internal.util.ArrayUtils;
+
+/**
+ * Policy logic that decides if delivery of a particular {@link BroadcastRecord}
+ * should be skipped for a given {@link ResolveInfo} or {@link BroadcastFilter}.
+ * <p>
+ * This policy should be consulted as close as possible to the actual dispatch.
+ */
+public class BroadcastSkipPolicy {
+    private final ActivityManagerService mService;
+
+    public BroadcastSkipPolicy(ActivityManagerService service) {
+        mService = service;
+    }
+
+    /**
+     * Determine if the given {@link BroadcastRecord} is eligible to be sent to
+     * the given {@link ResolveInfo}.
+     */
+    public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull ResolveInfo info) {
+        final BroadcastOptions brOptions = r.options;
+        final ComponentName component = new ComponentName(
+                info.activityInfo.applicationInfo.packageName,
+                info.activityInfo.name);
+
+        if (brOptions != null &&
+                (info.activityInfo.applicationInfo.targetSdkVersion
+                        < brOptions.getMinManifestReceiverApiLevel() ||
+                info.activityInfo.applicationInfo.targetSdkVersion
+                        > brOptions.getMaxManifestReceiverApiLevel())) {
+            Slog.w(TAG, "Target SDK mismatch: receiver " + info.activityInfo
+                    + " targets " + info.activityInfo.applicationInfo.targetSdkVersion
+                    + " but delivery restricted to ["
+                    + brOptions.getMinManifestReceiverApiLevel() + ", "
+                    + brOptions.getMaxManifestReceiverApiLevel()
+                    + "] broadcasting " + broadcastDescription(r, component));
+            return true;
+        }
+        if (brOptions != null &&
+                !brOptions.testRequireCompatChange(info.activityInfo.applicationInfo.uid)) {
+            Slog.w(TAG, "Compat change filtered: broadcasting " + broadcastDescription(r, component)
+                    + " to uid " + info.activityInfo.applicationInfo.uid + " due to compat change "
+                    + r.options.getRequireCompatChangeId());
+            return true;
+        }
+        if (!mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
+                component.getPackageName(), info.activityInfo.applicationInfo.uid)) {
+            Slog.w(TAG, "Association not allowed: broadcasting "
+                    + broadcastDescription(r, component));
+            return true;
+        }
+        if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
+                r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid)) {
+            Slog.w(TAG, "Firewall blocked: broadcasting "
+                    + broadcastDescription(r, component));
+            return true;
+        }
+        int perm = checkComponentPermission(info.activityInfo.permission,
+                r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
+                info.activityInfo.exported);
+        if (perm != PackageManager.PERMISSION_GRANTED) {
+            if (!info.activityInfo.exported) {
+                Slog.w(TAG, "Permission Denial: broadcasting "
+                        + broadcastDescription(r, component)
+                        + " is not exported from uid " + info.activityInfo.applicationInfo.uid);
+            } else {
+                Slog.w(TAG, "Permission Denial: broadcasting "
+                        + broadcastDescription(r, component)
+                        + " requires " + info.activityInfo.permission);
+            }
+            return true;
+        } else if (info.activityInfo.permission != null) {
+            final int opCode = AppOpsManager.permissionToOpCode(info.activityInfo.permission);
+            if (opCode != AppOpsManager.OP_NONE && mService.getAppOpsManager().noteOpNoThrow(opCode,
+                    r.callingUid, r.callerPackage, r.callerFeatureId,
+                    "Broadcast delivered to " + info.activityInfo.name)
+                    != AppOpsManager.MODE_ALLOWED) {
+                Slog.w(TAG, "Appop Denial: broadcasting "
+                        + broadcastDescription(r, component)
+                        + " requires appop " + AppOpsManager.permissionToOp(
+                                info.activityInfo.permission));
+                return true;
+            }
+        }
+
+        boolean isSingleton = false;
+        try {
+            isSingleton = mService.isSingleton(info.activityInfo.processName,
+                    info.activityInfo.applicationInfo,
+                    info.activityInfo.name, info.activityInfo.flags);
+        } catch (SecurityException e) {
+            Slog.w(TAG, e.getMessage());
+            return true;
+        }
+        if ((info.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
+            if (ActivityManager.checkUidPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS,
+                    info.activityInfo.applicationInfo.uid)
+                            != PackageManager.PERMISSION_GRANTED) {
+                Slog.w(TAG, "Permission Denial: Receiver " + component.flattenToShortString()
+                        + " requests FLAG_SINGLE_USER, but app does not hold "
+                        + android.Manifest.permission.INTERACT_ACROSS_USERS);
+                return true;
+            }
+        }
+        if (info.activityInfo.applicationInfo.isInstantApp()
+                && r.callingUid != info.activityInfo.applicationInfo.uid) {
+            Slog.w(TAG, "Instant App Denial: receiving "
+                    + r.intent
+                    + " to " + component.flattenToShortString()
+                    + " due to sender " + r.callerPackage
+                    + " (uid " + r.callingUid + ")"
+                    + " Instant Apps do not support manifest receivers");
+            return true;
+        }
+        if (r.callerInstantApp
+                && (info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0
+                && r.callingUid != info.activityInfo.applicationInfo.uid) {
+            Slog.w(TAG, "Instant App Denial: receiving "
+                    + r.intent
+                    + " to " + component.flattenToShortString()
+                    + " requires receiver have visibleToInstantApps set"
+                    + " due to sender " + r.callerPackage
+                    + " (uid " + r.callingUid + ")");
+            return true;
+        }
+        if (r.curApp != null && r.curApp.mErrorState.isCrashing()) {
+            // If the target process is crashing, just skip it.
+            Slog.w(TAG, "Skipping deliver ordered [" + r.queue.toString() + "] " + r
+                    + " to " + r.curApp + ": process crashing");
+            return true;
+        }
+
+        boolean isAvailable = false;
+        try {
+            isAvailable = AppGlobals.getPackageManager().isPackageAvailable(
+                    info.activityInfo.packageName,
+                    UserHandle.getUserId(info.activityInfo.applicationInfo.uid));
+        } catch (Exception e) {
+            // all such failures mean we skip this receiver
+            Slog.w(TAG, "Exception getting recipient info for "
+                    + info.activityInfo.packageName, e);
+        }
+        if (!isAvailable) {
+            Slog.w(TAG,
+                    "Skipping delivery to " + info.activityInfo.packageName + " / "
+                    + info.activityInfo.applicationInfo.uid
+                    + " : package no longer available");
+            return true;
+        }
+
+        // If permissions need a review before any of the app components can run, we drop
+        // the broadcast and if the calling app is in the foreground and the broadcast is
+        // explicit we launch the review UI passing it a pending intent to send the skipped
+        // broadcast.
+        if (!requestStartTargetPermissionsReviewIfNeededLocked(r,
+                info.activityInfo.packageName, UserHandle.getUserId(
+                        info.activityInfo.applicationInfo.uid))) {
+            Slog.w(TAG,
+                    "Skipping delivery: permission review required for "
+                            + broadcastDescription(r, component));
+            return true;
+        }
+
+        // This is safe to do even if we are skipping the broadcast, and we need
+        // this information now to evaluate whether it is going to be allowed to run.
+        final int receiverUid = info.activityInfo.applicationInfo.uid;
+        // If it's a singleton, it needs to be the same app or a special app
+        if (r.callingUid != Process.SYSTEM_UID && isSingleton
+                && mService.isValidSingletonCall(r.callingUid, receiverUid)) {
+            info.activityInfo = mService.getActivityInfoForUser(info.activityInfo, 0);
+        }
+
+        final int allowed = mService.getAppStartModeLOSP(
+                info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
+                info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false, false);
+        if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
+            // We won't allow this receiver to be launched if the app has been
+            // completely disabled from launches, or it was not explicitly sent
+            // to it and the app is in a state that should not receive it
+            // (depending on how getAppStartModeLOSP has determined that).
+            if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
+                Slog.w(TAG, "Background execution disabled: receiving "
+                        + r.intent + " to "
+                        + component.flattenToShortString());
+                return true;
+            } else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
+                    || (r.intent.getComponent() == null
+                        && r.intent.getPackage() == null
+                        && ((r.intent.getFlags()
+                                & Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
+                        && !isSignaturePerm(r.requiredPermissions))) {
+                mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
+                        component.getPackageName());
+                Slog.w(TAG, "Background execution not allowed: receiving "
+                        + r.intent + " to "
+                        + component.flattenToShortString());
+                return true;
+            }
+        }
+
+        if (!Intent.ACTION_SHUTDOWN.equals(r.intent.getAction())
+                && !mService.mUserController
+                .isUserRunning(UserHandle.getUserId(info.activityInfo.applicationInfo.uid),
+                        0 /* flags */)) {
+            Slog.w(TAG,
+                    "Skipping delivery to " + info.activityInfo.packageName + " / "
+                            + info.activityInfo.applicationInfo.uid + " : user is not running");
+            return true;
+        }
+
+        if (r.excludedPermissions != null && r.excludedPermissions.length > 0) {
+            for (int i = 0; i < r.excludedPermissions.length; i++) {
+                String excludedPermission = r.excludedPermissions[i];
+                try {
+                    perm = AppGlobals.getPackageManager()
+                        .checkPermission(excludedPermission,
+                                info.activityInfo.applicationInfo.packageName,
+                                UserHandle
+                                .getUserId(info.activityInfo.applicationInfo.uid));
+                } catch (RemoteException e) {
+                    perm = PackageManager.PERMISSION_DENIED;
+                }
+
+                int appOp = AppOpsManager.permissionToOpCode(excludedPermission);
+                if (appOp != AppOpsManager.OP_NONE) {
+                    // When there is an app op associated with the permission,
+                    // skip when both the permission and the app op are
+                    // granted.
+                    if ((perm == PackageManager.PERMISSION_GRANTED) && (
+                                mService.getAppOpsManager().checkOpNoThrow(appOp,
+                                info.activityInfo.applicationInfo.uid,
+                                info.activityInfo.packageName)
+                            == AppOpsManager.MODE_ALLOWED)) {
+                        return true;
+                    }
+                } else {
+                    // When there is no app op associated with the permission,
+                    // skip when permission is granted.
+                    if (perm == PackageManager.PERMISSION_GRANTED) {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        // Check that the receiver does *not* belong to any of the excluded packages
+        if (r.excludedPackages != null && r.excludedPackages.length > 0) {
+            if (ArrayUtils.contains(r.excludedPackages, component.getPackageName())) {
+                Slog.w(TAG, "Skipping delivery of excluded package "
+                        + r.intent + " to "
+                        + component.flattenToShortString()
+                        + " excludes package " + component.getPackageName()
+                        + " due to sender " + r.callerPackage
+                        + " (uid " + r.callingUid + ")");
+                return true;
+            }
+        }
+
+        if (info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID &&
+                r.requiredPermissions != null && r.requiredPermissions.length > 0) {
+            for (int i = 0; i < r.requiredPermissions.length; i++) {
+                String requiredPermission = r.requiredPermissions[i];
+                try {
+                    perm = AppGlobals.getPackageManager().
+                            checkPermission(requiredPermission,
+                                    info.activityInfo.applicationInfo.packageName,
+                                    UserHandle
+                                    .getUserId(info.activityInfo.applicationInfo.uid));
+                } catch (RemoteException e) {
+                    perm = PackageManager.PERMISSION_DENIED;
+                }
+                if (perm != PackageManager.PERMISSION_GRANTED) {
+                    Slog.w(TAG, "Permission Denial: receiving "
+                            + r.intent + " to "
+                            + component.flattenToShortString()
+                            + " requires " + requiredPermission
+                            + " due to sender " + r.callerPackage
+                            + " (uid " + r.callingUid + ")");
+                    return true;
+                }
+                int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
+                if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp) {
+                    if (!noteOpForManifestReceiver(appOp, r, info, component)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        if (r.appOp != AppOpsManager.OP_NONE) {
+            if (!noteOpForManifestReceiver(r.appOp, r, info, component)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Determine if the given {@link BroadcastRecord} is eligible to be sent to
+     * the given {@link BroadcastFilter}.
+     */
+    public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull BroadcastFilter filter) {
+        if (r.options != null && !r.options.testRequireCompatChange(filter.owningUid)) {
+            Slog.w(TAG, "Compat change filtered: broadcasting " + r.intent.toString()
+                    + " to uid " + filter.owningUid + " due to compat change "
+                    + r.options.getRequireCompatChangeId());
+            return true;
+        }
+        if (!mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
+                filter.packageName, filter.owningUid)) {
+            Slog.w(TAG, "Association not allowed: broadcasting "
+                    + r.intent.toString()
+                    + " from " + r.callerPackage + " (pid=" + r.callingPid
+                    + ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
+                    + filter);
+            return true;
+        }
+        if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
+                r.callingPid, r.resolvedType, filter.receiverList.uid)) {
+            Slog.w(TAG, "Firewall blocked: broadcasting "
+                    + r.intent.toString()
+                    + " from " + r.callerPackage + " (pid=" + r.callingPid
+                    + ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
+                    + filter);
+            return true;
+        }
+        // Check that the sender has permission to send to this receiver
+        if (filter.requiredPermission != null) {
+            int perm = checkComponentPermission(filter.requiredPermission,
+                    r.callingPid, r.callingUid, -1, true);
+            if (perm != PackageManager.PERMISSION_GRANTED) {
+                Slog.w(TAG, "Permission Denial: broadcasting "
+                        + r.intent.toString()
+                        + " from " + r.callerPackage + " (pid="
+                        + r.callingPid + ", uid=" + r.callingUid + ")"
+                        + " requires " + filter.requiredPermission
+                        + " due to registered receiver " + filter);
+                return true;
+            } else {
+                final int opCode = AppOpsManager.permissionToOpCode(filter.requiredPermission);
+                if (opCode != AppOpsManager.OP_NONE
+                        && mService.getAppOpsManager().noteOpNoThrow(opCode, r.callingUid,
+                        r.callerPackage, r.callerFeatureId, "Broadcast sent to protected receiver")
+                        != AppOpsManager.MODE_ALLOWED) {
+                    Slog.w(TAG, "Appop Denial: broadcasting "
+                            + r.intent.toString()
+                            + " from " + r.callerPackage + " (pid="
+                            + r.callingPid + ", uid=" + r.callingUid + ")"
+                            + " requires appop " + AppOpsManager.permissionToOp(
+                                    filter.requiredPermission)
+                            + " due to registered receiver " + filter);
+                    return true;
+                }
+            }
+        }
+
+        if ((filter.receiverList.app == null || filter.receiverList.app.isKilled()
+                || filter.receiverList.app.mErrorState.isCrashing())) {
+            Slog.w(TAG, "Skipping deliver [" + r.queue.toString() + "] " + r
+                    + " to " + filter.receiverList + ": process gone or crashing");
+            return true;
+        }
+
+        // Ensure that broadcasts are only sent to other Instant Apps if they are marked as
+        // visible to Instant Apps.
+        final boolean visibleToInstantApps =
+                (r.intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
+
+        if (!visibleToInstantApps && filter.instantApp
+                && filter.receiverList.uid != r.callingUid) {
+            Slog.w(TAG, "Instant App Denial: receiving "
+                    + r.intent.toString()
+                    + " to " + filter.receiverList.app
+                    + " (pid=" + filter.receiverList.pid
+                    + ", uid=" + filter.receiverList.uid + ")"
+                    + " due to sender " + r.callerPackage
+                    + " (uid " + r.callingUid + ")"
+                    + " not specifying FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS");
+            return true;
+        }
+
+        if (!filter.visibleToInstantApp && r.callerInstantApp
+                && filter.receiverList.uid != r.callingUid) {
+            Slog.w(TAG, "Instant App Denial: receiving "
+                    + r.intent.toString()
+                    + " to " + filter.receiverList.app
+                    + " (pid=" + filter.receiverList.pid
+                    + ", uid=" + filter.receiverList.uid + ")"
+                    + " requires receiver be visible to instant apps"
+                    + " due to sender " + r.callerPackage
+                    + " (uid " + r.callingUid + ")");
+            return true;
+        }
+
+        // Check that the receiver has the required permission(s) to receive this broadcast.
+        if (r.requiredPermissions != null && r.requiredPermissions.length > 0) {
+            for (int i = 0; i < r.requiredPermissions.length; i++) {
+                String requiredPermission = r.requiredPermissions[i];
+                int perm = checkComponentPermission(requiredPermission,
+                        filter.receiverList.pid, filter.receiverList.uid, -1, true);
+                if (perm != PackageManager.PERMISSION_GRANTED) {
+                    Slog.w(TAG, "Permission Denial: receiving "
+                            + r.intent.toString()
+                            + " to " + filter.receiverList.app
+                            + " (pid=" + filter.receiverList.pid
+                            + ", uid=" + filter.receiverList.uid + ")"
+                            + " requires " + requiredPermission
+                            + " due to sender " + r.callerPackage
+                            + " (uid " + r.callingUid + ")");
+                    return true;
+                }
+                int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
+                if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp
+                        && mService.getAppOpsManager().noteOpNoThrow(appOp,
+                        filter.receiverList.uid, filter.packageName, filter.featureId,
+                        "Broadcast delivered to registered receiver " + filter.receiverId)
+                        != AppOpsManager.MODE_ALLOWED) {
+                    Slog.w(TAG, "Appop Denial: receiving "
+                            + r.intent.toString()
+                            + " to " + filter.receiverList.app
+                            + " (pid=" + filter.receiverList.pid
+                            + ", uid=" + filter.receiverList.uid + ")"
+                            + " requires appop " + AppOpsManager.permissionToOp(
+                            requiredPermission)
+                            + " due to sender " + r.callerPackage
+                            + " (uid " + r.callingUid + ")");
+                    return true;
+                }
+            }
+        }
+        if ((r.requiredPermissions == null || r.requiredPermissions.length == 0)) {
+            int perm = checkComponentPermission(null,
+                    filter.receiverList.pid, filter.receiverList.uid, -1, true);
+            if (perm != PackageManager.PERMISSION_GRANTED) {
+                Slog.w(TAG, "Permission Denial: security check failed when receiving "
+                        + r.intent.toString()
+                        + " to " + filter.receiverList.app
+                        + " (pid=" + filter.receiverList.pid
+                        + ", uid=" + filter.receiverList.uid + ")"
+                        + " due to sender " + r.callerPackage
+                        + " (uid " + r.callingUid + ")");
+                return true;
+            }
+        }
+        // Check that the receiver does *not* have any excluded permissions
+        if (r.excludedPermissions != null && r.excludedPermissions.length > 0) {
+            for (int i = 0; i < r.excludedPermissions.length; i++) {
+                String excludedPermission = r.excludedPermissions[i];
+                final int perm = checkComponentPermission(excludedPermission,
+                        filter.receiverList.pid, filter.receiverList.uid, -1, true);
+
+                int appOp = AppOpsManager.permissionToOpCode(excludedPermission);
+                if (appOp != AppOpsManager.OP_NONE) {
+                    // When there is an app op associated with the permission,
+                    // skip when both the permission and the app op are
+                    // granted.
+                    if ((perm == PackageManager.PERMISSION_GRANTED) && (
+                            mService.getAppOpsManager().checkOpNoThrow(appOp,
+                                    filter.receiverList.uid,
+                                    filter.packageName)
+                                    == AppOpsManager.MODE_ALLOWED)) {
+                        Slog.w(TAG, "Appop Denial: receiving "
+                                + r.intent.toString()
+                                + " to " + filter.receiverList.app
+                                + " (pid=" + filter.receiverList.pid
+                                + ", uid=" + filter.receiverList.uid + ")"
+                                + " excludes appop " + AppOpsManager.permissionToOp(
+                                excludedPermission)
+                                + " due to sender " + r.callerPackage
+                                + " (uid " + r.callingUid + ")");
+                        return true;
+                    }
+                } else {
+                    // When there is no app op associated with the permission,
+                    // skip when permission is granted.
+                    if (perm == PackageManager.PERMISSION_GRANTED) {
+                        Slog.w(TAG, "Permission Denial: receiving "
+                                + r.intent.toString()
+                                + " to " + filter.receiverList.app
+                                + " (pid=" + filter.receiverList.pid
+                                + ", uid=" + filter.receiverList.uid + ")"
+                                + " excludes " + excludedPermission
+                                + " due to sender " + r.callerPackage
+                                + " (uid " + r.callingUid + ")");
+                        return true;
+                    }
+                }
+            }
+        }
+
+        // Check that the receiver does *not* belong to any of the excluded packages
+        if (r.excludedPackages != null && r.excludedPackages.length > 0) {
+            if (ArrayUtils.contains(r.excludedPackages, filter.packageName)) {
+                Slog.w(TAG, "Skipping delivery of excluded package "
+                        + r.intent.toString()
+                        + " to " + filter.receiverList.app
+                        + " (pid=" + filter.receiverList.pid
+                        + ", uid=" + filter.receiverList.uid + ")"
+                        + " excludes package " + filter.packageName
+                        + " due to sender " + r.callerPackage
+                        + " (uid " + r.callingUid + ")");
+                return true;
+            }
+        }
+
+        // If the broadcast also requires an app op check that as well.
+        if (r.appOp != AppOpsManager.OP_NONE
+                && mService.getAppOpsManager().noteOpNoThrow(r.appOp,
+                filter.receiverList.uid, filter.packageName, filter.featureId,
+                "Broadcast delivered to registered receiver " + filter.receiverId)
+                != AppOpsManager.MODE_ALLOWED) {
+            Slog.w(TAG, "Appop Denial: receiving "
+                    + r.intent.toString()
+                    + " to " + filter.receiverList.app
+                    + " (pid=" + filter.receiverList.pid
+                    + ", uid=" + filter.receiverList.uid + ")"
+                    + " requires appop " + AppOpsManager.opToName(r.appOp)
+                    + " due to sender " + r.callerPackage
+                    + " (uid " + r.callingUid + ")");
+            return true;
+        }
+
+        // Ensure that broadcasts are only sent to other apps if they are explicitly marked as
+        // exported, or are System level broadcasts
+        if (!filter.exported && checkComponentPermission(null, r.callingPid,
+                r.callingUid, filter.receiverList.uid, filter.exported)
+                != PackageManager.PERMISSION_GRANTED) {
+            Slog.w(TAG, "Exported Denial: sending "
+                    + r.intent.toString()
+                    + ", action: " + r.intent.getAction()
+                    + " from " + r.callerPackage
+                    + " (uid=" + r.callingUid + ")"
+                    + " due to receiver " + filter.receiverList.app
+                    + " (uid " + filter.receiverList.uid + ")"
+                    + " not specifying RECEIVER_EXPORTED");
+            return true;
+        }
+
+        // If permissions need a review before any of the app components can run, we drop
+        // the broadcast and if the calling app is in the foreground and the broadcast is
+        // explicit we launch the review UI passing it a pending intent to send the skipped
+        // broadcast.
+        if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName,
+                filter.owningUserId)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private static String broadcastDescription(BroadcastRecord r, ComponentName component) {
+        return r.intent.toString()
+                + " from " + r.callerPackage + " (pid=" + r.callingPid
+                + ", uid=" + r.callingUid + ") to " + component.flattenToShortString();
+    }
+
+    private boolean noteOpForManifestReceiver(int appOp, BroadcastRecord r, ResolveInfo info,
+            ComponentName component) {
+        if (ArrayUtils.isEmpty(info.activityInfo.attributionTags)) {
+            return noteOpForManifestReceiverInner(appOp, r, info, component, null);
+        } else {
+            // Attribution tags provided, noteOp each tag
+            for (String tag : info.activityInfo.attributionTags) {
+                if (!noteOpForManifestReceiverInner(appOp, r, info, component, tag)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    private boolean noteOpForManifestReceiverInner(int appOp, BroadcastRecord r, ResolveInfo info,
+            ComponentName component, String tag) {
+        if (mService.getAppOpsManager().noteOpNoThrow(appOp,
+                    info.activityInfo.applicationInfo.uid,
+                    info.activityInfo.packageName,
+                    tag,
+                    "Broadcast delivered to " + info.activityInfo.name)
+                != AppOpsManager.MODE_ALLOWED) {
+            Slog.w(TAG, "Appop Denial: receiving "
+                    + r.intent + " to "
+                    + component.flattenToShortString()
+                    + " requires appop " + AppOpsManager.opToName(appOp)
+                    + " due to sender " + r.callerPackage
+                    + " (uid " + r.callingUid + ")");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Return true if all given permissions are signature-only perms.
+     */
+    private boolean isSignaturePerm(String[] perms) {
+        if (perms == null) {
+            return false;
+        }
+        IPermissionManager pm = AppGlobals.getPermissionManager();
+        for (int i = perms.length-1; i >= 0; i--) {
+            try {
+                PermissionInfo pi = pm.getPermissionInfo(perms[i], "android", 0);
+                if (pi == null) {
+                    // a required permission that no package has actually
+                    // defined cannot be signature-required.
+                    return false;
+                }
+                if ((pi.protectionLevel & (PermissionInfo.PROTECTION_MASK_BASE
+                        | PermissionInfo.PROTECTION_FLAG_PRIVILEGED))
+                        != PermissionInfo.PROTECTION_SIGNATURE) {
+                    // If this a signature permission and NOT allowed for privileged apps, it
+                    // is okay...  otherwise, nope!
+                    return false;
+                }
+            } catch (RemoteException e) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean requestStartTargetPermissionsReviewIfNeededLocked(
+            BroadcastRecord receiverRecord, String receivingPackageName,
+            final int receivingUserId) {
+        if (!mService.getPackageManagerInternal().isPermissionsReviewRequired(
+                receivingPackageName, receivingUserId)) {
+            return true;
+        }
+
+        final boolean callerForeground = receiverRecord.callerApp != null
+                ? receiverRecord.callerApp.mState.getSetSchedGroup()
+                != ProcessList.SCHED_GROUP_BACKGROUND : true;
+
+        // Show a permission review UI only for explicit broadcast from a foreground app
+        if (callerForeground && receiverRecord.intent.getComponent() != null) {
+            IIntentSender target = mService.mPendingIntentController.getIntentSender(
+                    ActivityManager.INTENT_SENDER_BROADCAST, receiverRecord.callerPackage,
+                    receiverRecord.callerFeatureId, receiverRecord.callingUid,
+                    receiverRecord.userId, null, null, 0,
+                    new Intent[]{receiverRecord.intent},
+                    new String[]{receiverRecord.intent.resolveType(mService.mContext
+                            .getContentResolver())},
+                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
+                            | PendingIntent.FLAG_IMMUTABLE, null);
+
+            final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+            intent.putExtra(Intent.EXTRA_PACKAGE_NAME, receivingPackageName);
+            intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
+
+            if (DEBUG_PERMISSIONS_REVIEW) {
+                Slog.i(TAG, "u" + receivingUserId + " Launching permission review for package "
+                        + receivingPackageName);
+            }
+
+            mService.mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mService.mContext.startActivityAsUser(intent, new UserHandle(receivingUserId));
+                }
+            });
+        } else {
+            Slog.w(TAG, "u" + receivingUserId + " Receiving a broadcast in package"
+                    + receivingPackageName + " requires a permissions review");
+        }
+
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 1302e22..134e206 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -502,6 +502,14 @@
 
         GamePackageConfiguration(PackageManager packageManager, String packageName, int userId) {
             mPackageName = packageName;
+
+            // set flag default values
+            mPerfModeOptedIn = false;
+            mBatteryModeOptedIn = false;
+            mAllowDownscale = true;
+            mAllowAngle = true;
+            mAllowFpsOverride = true;
+
             try {
                 final ApplicationInfo ai = packageManager.getApplicationInfoAsUser(packageName,
                         PackageManager.GET_META_DATA, userId);
@@ -511,12 +519,6 @@
                         mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
                         mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
                         mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true);
-                    } else {
-                        mPerfModeOptedIn = false;
-                        mBatteryModeOptedIn = false;
-                        mAllowDownscale = true;
-                        mAllowAngle = true;
-                        mAllowFpsOverride = true;
                     }
                 }
             } catch (NameNotFoundException e) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index a5bcb05..248e35e 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -63,7 +63,6 @@
 import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
 import static android.app.AppOpsManager.UID_STATE_PERSISTENT;
 import static android.app.AppOpsManager.UID_STATE_TOP;
-import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
 import static android.app.AppOpsManager._NUM_OP;
 import static android.app.AppOpsManager.extractFlagsFromKey;
 import static android.app.AppOpsManager.extractUidStateFromKey;
@@ -563,6 +562,7 @@
         public ArrayMap<String, Ops> pkgOps;
 
         // true indicates there is an interested observer, false there isn't but it has such an op
+        //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface.
         public SparseBooleanArray foregroundOps;
         public boolean hasForegroundWatchers;
 
@@ -658,48 +658,24 @@
             return mode;
         }
 
-        private void evalForegroundWatchers(int op, SparseArray<ArraySet<ModeCallback>> watchers,
-                SparseBooleanArray which) {
-            boolean curValue = which.get(op, false);
-            ArraySet<ModeCallback> callbacks = watchers.get(op);
-            if (callbacks != null) {
-                for (int cbi = callbacks.size() - 1; !curValue && cbi >= 0; cbi--) {
-                    if ((callbacks.valueAt(cbi).mFlags
-                            & AppOpsManager.WATCH_FOREGROUND_CHANGES) != 0) {
-                        hasForegroundWatchers = true;
-                        curValue = true;
-                    }
-                }
-            }
-            which.put(op, curValue);
-        }
-
-        public void evalForegroundOps(SparseArray<ArraySet<ModeCallback>> watchers) {
-            SparseBooleanArray which = null;
-            hasForegroundWatchers = false;
-            final SparseIntArray opModes = getNonDefaultUidModes();
-            for (int i = opModes.size() - 1; i >= 0; i--) {
-                if (opModes.valueAt(i) == AppOpsManager.MODE_FOREGROUND) {
-                    if (which == null) {
-                        which = new SparseBooleanArray();
-                    }
-                    evalForegroundWatchers(opModes.keyAt(i), watchers, which);
-                }
-            }
+        public void evalForegroundOps() {
+            foregroundOps = null;
+            foregroundOps = mAppOpsServiceInterface.evalForegroundUidOps(uid, foregroundOps);
             if (pkgOps != null) {
                 for (int i = pkgOps.size() - 1; i >= 0; i--) {
-                    Ops ops = pkgOps.valueAt(i);
-                    for (int j = ops.size() - 1; j >= 0; j--) {
-                        if (ops.valueAt(j).getMode() == AppOpsManager.MODE_FOREGROUND) {
-                            if (which == null) {
-                                which = new SparseBooleanArray();
-                            }
-                            evalForegroundWatchers(ops.keyAt(j), watchers, which);
-                        }
+                    foregroundOps = mAppOpsServiceInterface
+                            .evalForegroundPackageOps(pkgOps.valueAt(i).packageName, foregroundOps);
+                }
+            }
+            hasForegroundWatchers = false;
+            if (foregroundOps != null) {
+                for (int i = 0;  i < foregroundOps.size(); i++) {
+                    if (foregroundOps.valueAt(i)) {
+                        hasForegroundWatchers = true;
+                        break;
                     }
                 }
             }
-            foregroundOps = which;
         }
     }
 
@@ -1562,33 +1538,24 @@
         }
     }
 
-    final SparseArray<ArraySet<ModeCallback>> mOpModeWatchers = new SparseArray<>();
-    final ArrayMap<String, ArraySet<ModeCallback>> mPackageModeWatchers = new ArrayMap<>();
     final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
     final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
     final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>();
     final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
     final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager();
 
-    final class ModeCallback implements DeathRecipient {
+    final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient  {
         /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */
         public static final int ALL_OPS = -2;
 
-        final IAppOpsCallback mCallback;
-        final int mWatchingUid;
-        final int mFlags;
-        final int mWatchedOpCode;
-        final int mCallingUid;
-        final int mCallingPid;
+        // Need to keep this only because stopWatchingMode needs an IAppOpsCallback.
+        // Otherwise we can just use the IBinder object.
+        private final IAppOpsCallback mCallback;
 
-        ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOp,
+        ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode,
                 int callingUid, int callingPid) {
-            mCallback = callback;
-            mWatchingUid = watchingUid;
-            mFlags = flags;
-            mWatchedOpCode = watchedOp;
-            mCallingUid = callingUid;
-            mCallingPid = callingPid;
+            super(watchingUid, flags, watchedOpCode, callingUid, callingPid);
+            this.mCallback = callback;
             try {
                 mCallback.asBinder().linkToDeath(this, 0);
             } catch (RemoteException e) {
@@ -1596,20 +1563,16 @@
             }
         }
 
-        public boolean isWatchingUid(int uid) {
-            return uid == UID_ANY || mWatchingUid < 0 || mWatchingUid == uid;
-        }
-
         @Override
         public String toString() {
             StringBuilder sb = new StringBuilder(128);
             sb.append("ModeCallback{");
             sb.append(Integer.toHexString(System.identityHashCode(this)));
             sb.append(" watchinguid=");
-            UserHandle.formatUid(sb, mWatchingUid);
+            UserHandle.formatUid(sb, getWatchingUid());
             sb.append(" flags=0x");
-            sb.append(Integer.toHexString(mFlags));
-            switch (mWatchedOpCode) {
+            sb.append(Integer.toHexString(getFlags()));
+            switch (getWatchedOpCode()) {
                 case OP_NONE:
                     break;
                 case ALL_OPS:
@@ -1617,13 +1580,13 @@
                     break;
                 default:
                     sb.append(" op=");
-                    sb.append(opToName(mWatchedOpCode));
+                    sb.append(opToName(getWatchedOpCode()));
                     break;
             }
             sb.append(" from uid=");
-            UserHandle.formatUid(sb, mCallingUid);
+            UserHandle.formatUid(sb, getCallingUid());
             sb.append(" pid=");
-            sb.append(mCallingPid);
+            sb.append(getCallingPid());
             sb.append('}');
             return sb.toString();
         }
@@ -1636,6 +1599,11 @@
         public void binderDied() {
             stopWatchingMode(mCallback);
         }
+
+        @Override
+        public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
+            mCallback.opChanged(op, uid, packageName);
+        }
     }
 
     final class ActiveCallback implements DeathRecipient {
@@ -1804,7 +1772,14 @@
 
     public AppOpsService(File storagePath, Handler handler, Context context) {
         mContext = context;
-        mAppOpsServiceInterface = new LegacyAppOpsServiceInterfaceImpl(this, this);
+
+        for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
+            int switchCode = AppOpsManager.opToSwitch(switchedCode);
+            mSwitchedOps.put(switchCode,
+                    ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
+        }
+        mAppOpsServiceInterface =
+                new LegacyAppOpsServiceInterfaceImpl(this, this, handler, context, mSwitchedOps);
 
         LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
         mFile = new AtomicFile(storagePath, "appops");
@@ -1818,12 +1793,6 @@
         mHandler = handler;
         mConstants = new Constants(mHandler);
         readState();
-
-        for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
-            int switchCode = AppOpsManager.opToSwitch(switchedCode);
-            mSwitchedOps.put(switchCode,
-                    ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
-        }
     }
 
     public void publish() {
@@ -1982,20 +1951,20 @@
                 final String[] changedPkgs = intent.getStringArrayExtra(
                         Intent.EXTRA_CHANGED_PACKAGE_LIST);
                 for (int code : OPS_RESTRICTED_ON_SUSPEND) {
-                    ArraySet<ModeCallback> callbacks;
+                    ArraySet<OnOpModeChangedListener> onModeChangedListeners;
                     synchronized (AppOpsService.this) {
-                        callbacks = mOpModeWatchers.get(code);
-                        if (callbacks == null) {
+                        onModeChangedListeners =
+                                mAppOpsServiceInterface.getOpModeChangedListeners(code);
+                        if (onModeChangedListeners == null) {
                             continue;
                         }
-                        callbacks = new ArraySet<>(callbacks);
                     }
                     for (int i = 0; i < changedUids.length; i++) {
                         final int changedUid = changedUids[i];
                         final String changedPkg = changedPkgs[i];
                         // We trust packagemanager to insert matching uid and packageNames in the
                         // extras
-                        notifyOpChanged(callbacks, code, changedUid, changedPkg);
+                        notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
                     }
                 }
             }
@@ -2596,7 +2565,7 @@
             if (!uidState.setUidMode(code, mode)) {
                 return;
             }
-            uidState.evalForegroundOps(mOpModeWatchers);
+            uidState.evalForegroundOps();
             if (mode != MODE_ERRORED && mode != previousMode) {
                 updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
             }
@@ -2615,78 +2584,10 @@
      */
     private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
             @Nullable IAppOpsCallback callbackToIgnore) {
-        String[] uidPackageNames = getPackagesForUid(uid);
-        ArrayMap<ModeCallback, ArraySet<String>> callbackSpecs = null;
-
-        synchronized (this) {
-            ArraySet<ModeCallback> callbacks = mOpModeWatchers.get(code);
-            if (callbacks != null) {
-                final int callbackCount = callbacks.size();
-                for (int i = 0; i < callbackCount; i++) {
-                    ModeCallback callback = callbacks.valueAt(i);
-                    if (onlyForeground && (callback.mFlags & WATCH_FOREGROUND_CHANGES) == 0) {
-                        continue;
-                    }
-
-                    ArraySet<String> changedPackages = new ArraySet<>();
-                    Collections.addAll(changedPackages, uidPackageNames);
-                    if (callbackSpecs == null) {
-                        callbackSpecs = new ArrayMap<>();
-                    }
-                    callbackSpecs.put(callback, changedPackages);
-                }
-            }
-
-            for (String uidPackageName : uidPackageNames) {
-                callbacks = mPackageModeWatchers.get(uidPackageName);
-                if (callbacks != null) {
-                    if (callbackSpecs == null) {
-                        callbackSpecs = new ArrayMap<>();
-                    }
-                    final int callbackCount = callbacks.size();
-                    for (int i = 0; i < callbackCount; i++) {
-                        ModeCallback callback = callbacks.valueAt(i);
-                        if (onlyForeground && (callback.mFlags & WATCH_FOREGROUND_CHANGES) == 0) {
-                            continue;
-                        }
-
-                        ArraySet<String> changedPackages = callbackSpecs.get(callback);
-                        if (changedPackages == null) {
-                            changedPackages = new ArraySet<>();
-                            callbackSpecs.put(callback, changedPackages);
-                        }
-                        changedPackages.add(uidPackageName);
-                    }
-                }
-            }
-
-            if (callbackSpecs != null && callbackToIgnore != null) {
-                callbackSpecs.remove(mModeWatchers.get(callbackToIgnore.asBinder()));
-            }
-        }
-
-        if (callbackSpecs == null) {
-            return;
-        }
-
-        for (int i = 0; i < callbackSpecs.size(); i++) {
-            final ModeCallback callback = callbackSpecs.keyAt(i);
-            final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
-            if (reportedPackageNames == null) {
-                mHandler.sendMessage(PooledLambda.obtainMessage(
-                        AppOpsService::notifyOpChanged,
-                        this, callback, code, uid, (String) null));
-
-            } else {
-                final int reportedPackageCount = reportedPackageNames.size();
-                for (int j = 0; j < reportedPackageCount; j++) {
-                    final String reportedPackageName = reportedPackageNames.valueAt(j);
-                    mHandler.sendMessage(PooledLambda.obtainMessage(
-                            AppOpsService::notifyOpChanged,
-                            this, callback, code, uid, reportedPackageName));
-                }
-            }
-        }
+        ModeCallback listenerToIgnore = callbackToIgnore != null
+                ? mModeWatchers.get(callbackToIgnore.asBinder()) : null;
+        mAppOpsServiceInterface.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground,
+                listenerToIgnore);
     }
 
     private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
@@ -2810,7 +2711,7 @@
             return;
         }
 
-        ArraySet<ModeCallback> repCbs = null;
+        ArraySet<OnOpModeChangedListener> repCbs = null;
         code = AppOpsManager.opToSwitch(code);
 
         PackageVerificationResult pvr;
@@ -2831,16 +2732,17 @@
                     op.setMode(mode);
 
                     if (uidState != null) {
-                        uidState.evalForegroundOps(mOpModeWatchers);
+                        uidState.evalForegroundOps();
                     }
-                    ArraySet<ModeCallback> cbs = mOpModeWatchers.get(code);
+                    ArraySet<OnOpModeChangedListener> cbs =
+                            mAppOpsServiceInterface.getOpModeChangedListeners(code);
                     if (cbs != null) {
                         if (repCbs == null) {
                             repCbs = new ArraySet<>();
                         }
                         repCbs.addAll(cbs);
                     }
-                    cbs = mPackageModeWatchers.get(packageName);
+                    cbs = mAppOpsServiceInterface.getPackageModeChangedListeners(packageName);
                     if (cbs != null) {
                         if (repCbs == null) {
                             repCbs = new ArraySet<>();
@@ -2871,47 +2773,17 @@
         notifyOpChangedSync(code, uid, packageName, mode, previousMode);
     }
 
-    private void notifyOpChanged(ArraySet<ModeCallback> callbacks, int code,
+    private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
             int uid, String packageName) {
         for (int i = 0; i < callbacks.size(); i++) {
-            final ModeCallback callback = callbacks.valueAt(i);
+            final OnOpModeChangedListener callback = callbacks.valueAt(i);
             notifyOpChanged(callback, code, uid, packageName);
         }
     }
 
-    private void notifyOpChanged(ModeCallback callback, int code,
+    private void notifyOpChanged(OnOpModeChangedListener callback, int code,
             int uid, String packageName) {
-        if (uid != UID_ANY && callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
-            return;
-        }
-
-        // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE
-        int[] switchedCodes;
-        if (callback.mWatchedOpCode == ALL_OPS) {
-            switchedCodes = mSwitchedOps.get(code);
-        } else if (callback.mWatchedOpCode == OP_NONE) {
-            switchedCodes = new int[]{code};
-        } else {
-            switchedCodes = new int[]{callback.mWatchedOpCode};
-        }
-
-        for (int switchedCode : switchedCodes) {
-            // There are features watching for mode changes such as window manager
-            // and location manager which are in our process. The callbacks in these
-            // features may require permissions our remote caller does not have.
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                if (shouldIgnoreCallback(switchedCode, callback.mCallingPid,
-                        callback.mCallingUid)) {
-                    continue;
-                }
-                callback.mCallback.opChanged(switchedCode, uid, packageName);
-            } catch (RemoteException e) {
-                /* ignore */
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
+        mAppOpsServiceInterface.notifyOpChanged(callback, code, uid, packageName);
     }
 
     private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
@@ -2936,9 +2808,10 @@
         return reports;
     }
 
-    private static HashMap<ModeCallback, ArrayList<ChangeRec>> addCallbacks(
-            HashMap<ModeCallback, ArrayList<ChangeRec>> callbacks,
-            int op, int uid, String packageName, int previousMode, ArraySet<ModeCallback> cbs) {
+    private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks(
+            HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks,
+            int op, int uid, String packageName, int previousMode,
+            ArraySet<OnOpModeChangedListener> cbs) {
         if (cbs == null) {
             return callbacks;
         }
@@ -2947,7 +2820,7 @@
         }
         final int N = cbs.size();
         for (int i=0; i<N; i++) {
-            ModeCallback cb = cbs.valueAt(i);
+            OnOpModeChangedListener cb = cbs.valueAt(i);
             ArrayList<ChangeRec> reports = callbacks.get(cb);
             ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode);
             if (changed != reports) {
@@ -2990,7 +2863,7 @@
 
         enforceManageAppOpsModes(callingPid, callingUid, reqUid);
 
-        HashMap<ModeCallback, ArrayList<ChangeRec>> callbacks = null;
+        HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null;
         ArrayList<ChangeRec> allChanges = new ArrayList<>();
         synchronized (this) {
             boolean changed = false;
@@ -3007,9 +2880,11 @@
                             uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code));
                             for (String packageName : getPackagesForUid(uidState.uid)) {
                                 callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
-                                        previousMode, mOpModeWatchers.get(code));
+                                        previousMode,
+                                        mAppOpsServiceInterface.getOpModeChangedListeners(code));
                                 callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
-                                        previousMode, mPackageModeWatchers.get(packageName));
+                                        previousMode, mAppOpsServiceInterface
+                                                .getPackageModeChangedListeners(packageName));
 
                                 allChanges = addChange(allChanges, code, uidState.uid,
                                         packageName, previousMode);
@@ -3053,9 +2928,11 @@
                             uidChanged = true;
                             final int uid = curOp.uidState.uid;
                             callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
-                                    previousMode, mOpModeWatchers.get(curOp.op));
+                                    previousMode,
+                                    mAppOpsServiceInterface.getOpModeChangedListeners(curOp.op));
                             callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
-                                    previousMode, mPackageModeWatchers.get(packageName));
+                                    previousMode, mAppOpsServiceInterface
+                                            .getPackageModeChangedListeners(packageName));
 
                             allChanges = addChange(allChanges, curOp.op, uid, packageName,
                                     previousMode);
@@ -3075,7 +2952,7 @@
                     mUidStates.remove(uidState.uid);
                 }
                 if (uidChanged) {
-                    uidState.evalForegroundOps(mOpModeWatchers);
+                    uidState.evalForegroundOps();
                 }
             }
 
@@ -3084,8 +2961,9 @@
             }
         }
         if (callbacks != null) {
-            for (Map.Entry<ModeCallback, ArrayList<ChangeRec>> ent : callbacks.entrySet()) {
-                ModeCallback cb = ent.getKey();
+            for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent
+                    : callbacks.entrySet()) {
+                OnOpModeChangedListener cb = ent.getKey();
                 ArrayList<ChangeRec> reports = ent.getValue();
                 for (int i=0; i<reports.size(); i++) {
                     ChangeRec rep = reports.get(i);
@@ -3121,7 +2999,7 @@
         for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) {
             final UidState uidState = mUidStates.valueAt(uidi);
             if (uidState.foregroundOps != null) {
-                uidState.evalForegroundOps(mOpModeWatchers);
+                uidState.evalForegroundOps();
             }
         }
     }
@@ -3169,20 +3047,10 @@
                 mModeWatchers.put(callback.asBinder(), cb);
             }
             if (switchOp != AppOpsManager.OP_NONE) {
-                ArraySet<ModeCallback> cbs = mOpModeWatchers.get(switchOp);
-                if (cbs == null) {
-                    cbs = new ArraySet<>();
-                    mOpModeWatchers.put(switchOp, cbs);
-                }
-                cbs.add(cb);
+                mAppOpsServiceInterface.startWatchingOpModeChanged(cb, switchOp);
             }
             if (mayWatchPackageName) {
-                ArraySet<ModeCallback> cbs = mPackageModeWatchers.get(packageName);
-                if (cbs == null) {
-                    cbs = new ArraySet<>();
-                    mPackageModeWatchers.put(packageName, cbs);
-                }
-                cbs.add(cb);
+                mAppOpsServiceInterface.startWatchingPackageModeChanged(cb, packageName);
             }
             evalAllForegroundOpsLocked();
         }
@@ -3197,21 +3065,9 @@
             ModeCallback cb = mModeWatchers.remove(callback.asBinder());
             if (cb != null) {
                 cb.unlinkToDeath();
-                for (int i=mOpModeWatchers.size()-1; i>=0; i--) {
-                    ArraySet<ModeCallback> cbs = mOpModeWatchers.valueAt(i);
-                    cbs.remove(cb);
-                    if (cbs.size() <= 0) {
-                        mOpModeWatchers.removeAt(i);
-                    }
-                }
-                for (int i=mPackageModeWatchers.size()-1; i>=0; i--) {
-                    ArraySet<ModeCallback> cbs = mPackageModeWatchers.valueAt(i);
-                    cbs.remove(cb);
-                    if (cbs.size() <= 0) {
-                        mPackageModeWatchers.removeAt(i);
-                    }
-                }
+                mAppOpsServiceInterface.removeListener(cb);
             }
+
             evalAllForegroundOpsLocked();
         }
     }
@@ -4542,12 +4398,14 @@
                             AppOpsService::notifyOpChangedForAllPkgsInUid,
                             this, code, uidState.uid, true, null));
                 } else if (uidState.pkgOps != null) {
-                    final ArraySet<ModeCallback> callbacks = mOpModeWatchers.get(code);
-                    if (callbacks != null) {
-                        for (int cbi = callbacks.size() - 1; cbi >= 0; cbi--) {
-                            final ModeCallback callback = callbacks.valueAt(cbi);
-                            if ((callback.mFlags & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
-                                    || !callback.isWatchingUid(uidState.uid)) {
+                    final ArraySet<OnOpModeChangedListener> listenerSet =
+                            mAppOpsServiceInterface.getOpModeChangedListeners(code);
+                    if (listenerSet != null) {
+                        for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) {
+                            final OnOpModeChangedListener listener = listenerSet.valueAt(cbi);
+                            if ((listener.getFlags()
+                                    & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
+                                    || !listener.isWatchingUid(uidState.uid)) {
                                 continue;
                             }
                             for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
@@ -4558,7 +4416,7 @@
                                 if (op.getMode() == AppOpsManager.MODE_FOREGROUND) {
                                     mHandler.sendMessage(PooledLambda.obtainMessage(
                                             AppOpsService::notifyOpChanged,
-                                            this, callback, code, uidState.uid,
+                                            this, listenerSet.valueAt(cbi), code, uidState.uid,
                                             uidState.pkgOps.keyAt(pkgi)));
                                 }
                             }
@@ -5045,7 +4903,7 @@
                 }
             }
             if (changed) {
-                uidState.evalForegroundOps(mOpModeWatchers);
+                uidState.evalForegroundOps();
             }
         }
     }
@@ -5131,7 +4989,7 @@
                 XmlUtils.skipCurrentTag(parser);
             }
         }
-        uidState.evalForegroundOps(mOpModeWatchers);
+        uidState.evalForegroundOps();
     }
 
     private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
@@ -6122,62 +5980,17 @@
                 }
                 pw.println();
             }
-            if (mOpModeWatchers.size() > 0 && !dumpHistory) {
-                boolean printedHeader = false;
-                for (int i=0; i<mOpModeWatchers.size(); i++) {
-                    if (dumpOp >= 0 && dumpOp != mOpModeWatchers.keyAt(i)) {
-                        continue;
-                    }
-                    boolean printedOpHeader = false;
-                    ArraySet<ModeCallback> callbacks = mOpModeWatchers.valueAt(i);
-                    for (int j=0; j<callbacks.size(); j++) {
-                        final ModeCallback cb = callbacks.valueAt(j);
-                        if (dumpPackage != null
-                                && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
-                            continue;
-                        }
-                        needSep = true;
-                        if (!printedHeader) {
-                            pw.println("  Op mode watchers:");
-                            printedHeader = true;
-                        }
-                        if (!printedOpHeader) {
-                            pw.print("    Op ");
-                            pw.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i)));
-                            pw.println(":");
-                            printedOpHeader = true;
-                        }
-                        pw.print("      #"); pw.print(j); pw.print(": ");
-                        pw.println(cb);
-                    }
-                }
+
+            if (!dumpHistory) {
+                needSep |= mAppOpsServiceInterface.dumpListeners(dumpOp, dumpUid, dumpPackage, pw);
             }
-            if (mPackageModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
-                boolean printedHeader = false;
-                for (int i=0; i<mPackageModeWatchers.size(); i++) {
-                    if (dumpPackage != null && !dumpPackage.equals(mPackageModeWatchers.keyAt(i))) {
-                        continue;
-                    }
-                    needSep = true;
-                    if (!printedHeader) {
-                        pw.println("  Package mode watchers:");
-                        printedHeader = true;
-                    }
-                    pw.print("    Pkg "); pw.print(mPackageModeWatchers.keyAt(i));
-                    pw.println(":");
-                    ArraySet<ModeCallback> callbacks = mPackageModeWatchers.valueAt(i);
-                    for (int j=0; j<callbacks.size(); j++) {
-                        pw.print("      #"); pw.print(j); pw.print(": ");
-                        pw.println(callbacks.valueAt(j));
-                    }
-                }
-            }
+
             if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
                 boolean printedHeader = false;
-                for (int i=0; i<mModeWatchers.size(); i++) {
+                for (int i = 0; i < mModeWatchers.size(); i++) {
                     final ModeCallback cb = mModeWatchers.valueAt(i);
                     if (dumpPackage != null
-                            && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+                            && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) {
                         continue;
                     }
                     needSep = true;
@@ -6729,16 +6542,15 @@
     }
 
     private void notifyWatchersOfChange(int code, int uid) {
-        final ArraySet<ModeCallback> clonedCallbacks;
+        final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
         synchronized (this) {
-            ArraySet<ModeCallback> callbacks = mOpModeWatchers.get(code);
-            if (callbacks == null) {
+            modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code);
+            if (modeChangedListenerSet == null) {
                 return;
             }
-            clonedCallbacks = new ArraySet<>(callbacks);
         }
 
-        notifyOpChanged(clonedCallbacks,  code, uid, null);
+        notifyOpChanged(modeChangedListenerSet,  code, uid, null);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
index cd5ea12..c707086 100644
--- a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
@@ -14,12 +14,20 @@
  * limitations under the License.
  */
 package com.android.server.appop;
+
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppOpsManager.Mode;
+import android.util.ArraySet;
+import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
+
+import java.io.PrintWriter;
+
 /**
  * Interface for accessing and modifying modes for app-ops i.e. package and uid modes.
- * In the future this interface will also include mode callbacks and op restrictions.
+ * This interface also includes functions for added and removing op mode watchers.
+ * In the future this interface will also include op restrictions.
  */
 public interface AppOpsServiceInterface {
     /**
@@ -95,4 +103,93 @@
      * Stop tracking app-op modes for all uid and packages.
      */
     void clearAllModes();
+
+    /**
+     * Registers changedListener to listen to op's mode change.
+     * @param changedListener the listener that must be trigger on the op's mode change.
+     * @param op op representing the app-op whose mode change needs to be listened to.
+     */
+    void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op);
+
+    /**
+     * Registers changedListener to listen to package's app-op's mode change.
+     * @param changedListener the listener that must be trigger on the mode change.
+     * @param packageName of the package whose app-op's mode change needs to be listened to.
+     */
+    void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
+            @NonNull String packageName);
+
+    /**
+     * Stop the changedListener from triggering on any mode change.
+     * @param changedListener the listener that needs to be removed.
+     */
+    void removeListener(@NonNull OnOpModeChangedListener changedListener);
+
+    /**
+     * Temporary API which will be removed once we can safely untangle the methods that use this.
+     * Returns a set of OnOpModeChangedListener that are listening for op's mode changes.
+     * @param op app-op whose mode change is being listened to.
+     */
+    ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op);
+
+    /**
+     * Temporary API which will be removed once we can safely untangle the methods that use this.
+     * Returns a set of OnOpModeChangedListener that are listening for package's op's mode changes.
+     * @param packageName of package whose app-op's mode change is being listened to.
+     */
+    ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(@NonNull String packageName);
+
+    /**
+     * Temporary API which will be removed once we can safely untangle the methods that use this.
+     * Notify that the app-op's mode is changed by triggering the change listener.
+     * @param changedListener the change listener.
+     * @param op App-op whose mode has changed
+     * @param uid user id associated with the app-op
+     * @param packageName package name that is associated with the app-op
+     */
+    void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid,
+            @Nullable String packageName);
+
+    /**
+     * Temporary API which will be removed once we can safely untangle the methods that use this.
+     * Notify that the app-op's mode is changed to all packages associated with the uid by
+     * triggering the appropriate change listener.
+     * @param op App-op whose mode has changed
+     * @param uid user id associated with the app-op
+     * @param onlyForeground true if only watchers that
+     * @param callbackToIgnore callback that should be ignored.
+     */
+    void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground,
+            @Nullable OnOpModeChangedListener callbackToIgnore);
+
+    /**
+     * TODO: Move hasForegroundWatchers and foregroundOps into this.
+     * Go over the list of app-ops for the uid and mark app-ops with MODE_FOREGROUND in
+     * foregroundOps.
+     * @param uid for which the app-op's mode needs to be marked.
+     * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
+     * @return  foregroundOps.
+     */
+    SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps);
+
+    /**
+     * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in
+     * foregroundOps.
+     * @param packageName for which the app-op's mode needs to be marked.
+     * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
+     * @return foregroundOps.
+     */
+    SparseBooleanArray evalForegroundPackageOps(String packageName,
+            SparseBooleanArray foregroundOps);
+
+    /**
+     * Dump op mode and package mode listeners and their details.
+     * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's set to an
+     *               app-op, only the watchers for that app-op are dumped.
+     * @param dumpUid uid for which we want to dump op mode watchers.
+     * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name.
+     * @param printWriter writer to dump to.
+     */
+    boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
+
 }
diff --git a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java b/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
index c27c0d3..2d498ea 100644
--- a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
+++ b/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
@@ -16,15 +16,39 @@
 
 package com.android.server.appop;
 
+import static android.app.AppOpsManager.OP_NONE;
+import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
+import static android.app.AppOpsManager.opRestrictsRead;
+
+import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
+
+import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.AppOpsManager.Mode;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import libcore.util.EmptyArray;
+
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Objects;
 
 
 /**
@@ -33,8 +57,13 @@
  */
 public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface {
 
-    // Should be the same object that the AppOpsService is using for locking.
+    static final String TAG = "LegacyAppOpsServiceInterfaceImpl";
+
+    // Must be the same object that the AppOpsService is using for locking.
     final Object mLock;
+    final Handler mHandler;
+    final Context mContext;
+    final SparseArray<int[]> mSwitchedOps;
 
     @GuardedBy("mLock")
     @VisibleForTesting
@@ -43,13 +72,25 @@
     @GuardedBy("mLock")
     final ArrayMap<String, SparseIntArray> mPackageModes = new ArrayMap<>();
 
+    final SparseArray<ArraySet<OnOpModeChangedListener>> mOpModeWatchers = new SparseArray<>();
+    final ArrayMap<String, ArraySet<OnOpModeChangedListener>> mPackageModeWatchers =
+            new ArrayMap<>();
+
     final PersistenceScheduler mPersistenceScheduler;
 
 
+    // Constant meaning that any UID should be matched when dispatching callbacks
+    private static final int UID_ANY = -2;
+
+
     LegacyAppOpsServiceInterfaceImpl(PersistenceScheduler persistenceScheduler,
-            @NonNull Object lock) {
+            @NonNull Object lock, Handler handler, Context context,
+            SparseArray<int[]> switchedOps) {
         this.mPersistenceScheduler = persistenceScheduler;
         this.mLock = lock;
+        this.mHandler = handler;
+        this.mContext = context;
+        this.mSwitchedOps = switchedOps;
     }
 
     @Override
@@ -158,7 +199,6 @@
         }
     }
 
-
     @Override
     public boolean areUidModesDefault(int uid) {
         synchronized (mLock) {
@@ -195,4 +235,335 @@
         }
     }
 
-}
+    @Override
+    public void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener,
+            int op) {
+        Objects.requireNonNull(changedListener);
+        synchronized (mLock) {
+            ArraySet<OnOpModeChangedListener> modeWatcherSet = mOpModeWatchers.get(op);
+            if (modeWatcherSet == null) {
+                modeWatcherSet = new ArraySet<>();
+                mOpModeWatchers.put(op, modeWatcherSet);
+            }
+            modeWatcherSet.add(changedListener);
+        }
+    }
+
+    @Override
+    public void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
+            @NonNull String packageName) {
+        Objects.requireNonNull(changedListener);
+        Objects.requireNonNull(packageName);
+        synchronized (mLock) {
+            ArraySet<OnOpModeChangedListener> modeWatcherSet =
+                    mPackageModeWatchers.get(packageName);
+            if (modeWatcherSet == null) {
+                modeWatcherSet = new ArraySet<>();
+                mPackageModeWatchers.put(packageName, modeWatcherSet);
+            }
+            modeWatcherSet.add(changedListener);
+        }
+    }
+
+    @Override
+    public void removeListener(@NonNull OnOpModeChangedListener changedListener) {
+        Objects.requireNonNull(changedListener);
+
+        synchronized (mLock) {
+            for (int i = mOpModeWatchers.size() - 1; i >= 0; i--) {
+                ArraySet<OnOpModeChangedListener> cbs = mOpModeWatchers.valueAt(i);
+                cbs.remove(changedListener);
+                if (cbs.size() <= 0) {
+                    mOpModeWatchers.removeAt(i);
+                }
+            }
+
+            for (int i = mPackageModeWatchers.size() - 1; i >= 0; i--) {
+                ArraySet<OnOpModeChangedListener> cbs = mPackageModeWatchers.valueAt(i);
+                cbs.remove(changedListener);
+                if (cbs.size() <= 0) {
+                    mPackageModeWatchers.removeAt(i);
+                }
+            }
+        }
+    }
+
+    @Override
+    public ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op) {
+        synchronized (mLock) {
+            ArraySet<OnOpModeChangedListener> modeChangedListenersSet = mOpModeWatchers.get(op);
+            if (modeChangedListenersSet == null) {
+                return new ArraySet<>();
+            }
+            return new ArraySet<>(modeChangedListenersSet);
+        }
+    }
+
+    @Override
+    public ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(
+            @NonNull String packageName) {
+        Objects.requireNonNull(packageName);
+
+        synchronized (mLock) {
+            ArraySet<OnOpModeChangedListener> modeChangedListenersSet =
+                    mPackageModeWatchers.get(packageName);
+            if (modeChangedListenersSet == null) {
+                return new ArraySet<>();
+            }
+            return new ArraySet<>(modeChangedListenersSet);
+        }
+    }
+
+    @Override
+    public void notifyOpChanged(@NonNull OnOpModeChangedListener onModeChangedListener, int code,
+            int uid, @Nullable String packageName) {
+        Objects.requireNonNull(onModeChangedListener);
+
+        if (uid != UID_ANY && onModeChangedListener.getWatchingUid() >= 0
+                && onModeChangedListener.getWatchingUid() != uid) {
+            return;
+        }
+
+        // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE
+        int[] switchedCodes;
+        if (onModeChangedListener.getWatchedOpCode() == ALL_OPS) {
+            switchedCodes = mSwitchedOps.get(code);
+        } else if (onModeChangedListener.getWatchedOpCode() == OP_NONE) {
+            switchedCodes = new int[]{code};
+        } else {
+            switchedCodes = new int[]{onModeChangedListener.getWatchedOpCode()};
+        }
+
+        for (int switchedCode : switchedCodes) {
+            // There are features watching for mode changes such as window manager
+            // and location manager which are in our process. The callbacks in these
+            // features may require permissions our remote caller does not have.
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                if (shouldIgnoreCallback(switchedCode, onModeChangedListener.getCallingPid(),
+                        onModeChangedListener.getCallingUid())) {
+                    continue;
+                }
+                onModeChangedListener.onOpModeChanged(switchedCode, uid, packageName);
+            } catch (RemoteException e) {
+                /* ignore */
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
+        // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
+        // as watcher should not use this to signal if the value is changed.
+        return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
+                watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
+    }
+
+    @Override
+    public void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
+            @Nullable OnOpModeChangedListener callbackToIgnore) {
+        String[] uidPackageNames = getPackagesForUid(uid);
+        ArrayMap<OnOpModeChangedListener, ArraySet<String>> callbackSpecs = null;
+
+        synchronized (mLock) {
+            ArraySet<OnOpModeChangedListener> callbacks = mOpModeWatchers.get(code);
+            if (callbacks != null) {
+                final int callbackCount = callbacks.size();
+                for (int i = 0; i < callbackCount; i++) {
+                    OnOpModeChangedListener callback = callbacks.valueAt(i);
+
+                    if (onlyForeground && (callback.getFlags()
+                            & WATCH_FOREGROUND_CHANGES) == 0) {
+                        continue;
+                    }
+
+                    ArraySet<String> changedPackages = new ArraySet<>();
+                    Collections.addAll(changedPackages, uidPackageNames);
+                    if (callbackSpecs == null) {
+                        callbackSpecs = new ArrayMap<>();
+                    }
+                    callbackSpecs.put(callback, changedPackages);
+                }
+            }
+
+            for (String uidPackageName : uidPackageNames) {
+                callbacks = mPackageModeWatchers.get(uidPackageName);
+                if (callbacks != null) {
+                    if (callbackSpecs == null) {
+                        callbackSpecs = new ArrayMap<>();
+                    }
+                    final int callbackCount = callbacks.size();
+                    for (int i = 0; i < callbackCount; i++) {
+                        OnOpModeChangedListener callback = callbacks.valueAt(i);
+
+                        if (onlyForeground && (callback.getFlags()
+                                & WATCH_FOREGROUND_CHANGES) == 0) {
+                            continue;
+                        }
+
+                        ArraySet<String> changedPackages = callbackSpecs.get(callback);
+                        if (changedPackages == null) {
+                            changedPackages = new ArraySet<>();
+                            callbackSpecs.put(callback, changedPackages);
+                        }
+                        changedPackages.add(uidPackageName);
+                    }
+                }
+            }
+
+            if (callbackSpecs != null && callbackToIgnore != null) {
+                callbackSpecs.remove(callbackToIgnore);
+            }
+        }
+
+        if (callbackSpecs == null) {
+            return;
+        }
+
+        for (int i = 0; i < callbackSpecs.size(); i++) {
+            final OnOpModeChangedListener callback = callbackSpecs.keyAt(i);
+            final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
+            if (reportedPackageNames == null) {
+                mHandler.sendMessage(PooledLambda.obtainMessage(
+                        LegacyAppOpsServiceInterfaceImpl::notifyOpChanged,
+                        this, callback, code, uid, (String) null));
+
+            } else {
+                final int reportedPackageCount = reportedPackageNames.size();
+                for (int j = 0; j < reportedPackageCount; j++) {
+                    final String reportedPackageName = reportedPackageNames.valueAt(j);
+                    mHandler.sendMessage(PooledLambda.obtainMessage(
+                            LegacyAppOpsServiceInterfaceImpl::notifyOpChanged,
+                            this, callback, code, uid, reportedPackageName));
+                }
+            }
+        }
+    }
+
+    private static String[] getPackagesForUid(int uid) {
+        String[] packageNames = null;
+
+        // Very early during boot the package manager is not yet or not yet fully started. At this
+        // time there are no packages yet.
+        if (AppGlobals.getPackageManager() != null) {
+            try {
+                packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
+            } catch (RemoteException e) {
+                /* ignore - local call */
+            }
+        }
+        if (packageNames == null) {
+            return EmptyArray.STRING;
+        }
+        return packageNames;
+    }
+
+    @Override
+    public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) {
+        synchronized (mLock) {
+            return evalForegroundOps(mUidModes.get(uid), foregroundOps);
+        }
+    }
+
+    @Override
+    public SparseBooleanArray evalForegroundPackageOps(String packageName,
+            SparseBooleanArray foregroundOps) {
+        synchronized (mLock) {
+            return evalForegroundOps(mPackageModes.get(packageName), foregroundOps);
+        }
+    }
+
+    private SparseBooleanArray evalForegroundOps(SparseIntArray opModes,
+            SparseBooleanArray foregroundOps) {
+        SparseBooleanArray tempForegroundOps = foregroundOps;
+        if (opModes != null) {
+            for (int i = opModes.size() - 1; i >= 0; i--) {
+                if (opModes.valueAt(i) == AppOpsManager.MODE_FOREGROUND) {
+                    if (tempForegroundOps == null) {
+                        tempForegroundOps = new SparseBooleanArray();
+                    }
+                    evalForegroundWatchers(opModes.keyAt(i), tempForegroundOps);
+                }
+            }
+        }
+        return tempForegroundOps;
+    }
+
+    private void evalForegroundWatchers(int op, SparseBooleanArray foregroundOps) {
+        boolean curValue = foregroundOps.get(op, false);
+        ArraySet<OnOpModeChangedListener> listenerSet = mOpModeWatchers.get(op);
+        if (listenerSet != null) {
+            for (int cbi = listenerSet.size() - 1; !curValue && cbi >= 0; cbi--) {
+                if ((listenerSet.valueAt(cbi).getFlags()
+                        & AppOpsManager.WATCH_FOREGROUND_CHANGES) != 0) {
+                    curValue = true;
+                }
+            }
+        }
+        foregroundOps.put(op, curValue);
+    }
+
+    @Override
+    public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage,
+            PrintWriter printWriter) {
+        boolean needSep = false;
+        if (mOpModeWatchers.size() > 0) {
+            boolean printedHeader = false;
+            for (int i = 0; i < mOpModeWatchers.size(); i++) {
+                if (dumpOp >= 0 && dumpOp != mOpModeWatchers.keyAt(i)) {
+                    continue;
+                }
+                boolean printedOpHeader = false;
+                ArraySet<OnOpModeChangedListener> modeChangedListenerSet =
+                        mOpModeWatchers.valueAt(i);
+                for (int j = 0; j < modeChangedListenerSet.size(); j++) {
+                    final OnOpModeChangedListener listener = modeChangedListenerSet.valueAt(j);
+                    if (dumpPackage != null
+                            && dumpUid != UserHandle.getAppId(listener.getWatchingUid())) {
+                        continue;
+                    }
+                    needSep = true;
+                    if (!printedHeader) {
+                        printWriter.println("  Op mode watchers:");
+                        printedHeader = true;
+                    }
+                    if (!printedOpHeader) {
+                        printWriter.print("    Op ");
+                        printWriter.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i)));
+                        printWriter.println(":");
+                        printedOpHeader = true;
+                    }
+                    printWriter.print("      #"); printWriter.print(j); printWriter.print(": ");
+                    printWriter.println(listener.toString());
+                }
+            }
+        }
+
+        if (mPackageModeWatchers.size() > 0 && dumpOp < 0) {
+            boolean printedHeader = false;
+            for (int i = 0; i < mPackageModeWatchers.size(); i++) {
+                if (dumpPackage != null
+                        && !dumpPackage.equals(mPackageModeWatchers.keyAt(i))) {
+                    continue;
+                }
+                needSep = true;
+                if (!printedHeader) {
+                    printWriter.println("  Package mode watchers:");
+                    printedHeader = true;
+                }
+                printWriter.print("    Pkg "); printWriter.print(mPackageModeWatchers.keyAt(i));
+                printWriter.println(":");
+                ArraySet<OnOpModeChangedListener> modeChangedListenerSet =
+                        mPackageModeWatchers.valueAt(i);
+
+                for (int j = 0; j < modeChangedListenerSet.size(); j++) {
+                    printWriter.print("      #"); printWriter.print(j); printWriter.print(": ");
+                    printWriter.println(modeChangedListenerSet.valueAt(j).toString());
+                }
+            }
+        }
+        return needSep;
+    }
+
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
new file mode 100644
index 0000000..5ebe811
--- /dev/null
+++ b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import android.os.RemoteException;
+
+/**
+ * Listener for mode changes, encapsulates methods that should be triggered in the event of a mode
+ * change.
+ */
+abstract class OnOpModeChangedListener {
+
+    // Constant meaning that any UID should be matched when dispatching callbacks
+    private static final int UID_ANY = -2;
+
+    private int mWatchingUid;
+    private int mFlags;
+    private int mWatchedOpCode;
+    private int mCallingUid;
+    private int mCallingPid;
+
+    OnOpModeChangedListener(int watchingUid, int flags, int watchedOpCode, int callingUid,
+            int callingPid) {
+        this.mWatchingUid = watchingUid;
+        this.mFlags = flags;
+        this.mWatchedOpCode = watchedOpCode;
+        this.mCallingUid = callingUid;
+        this.mCallingPid = callingPid;
+    }
+
+    /**
+     * Returns the user id that is watching for the mode change.
+     */
+    public int getWatchingUid() {
+        return mWatchingUid;
+    }
+
+    /**
+     * Returns the flags associated with the mode change listener.
+     */
+    public int getFlags() {
+        return mFlags;
+    }
+
+    /**
+     * Get the app-op whose mode change should trigger the callback.
+     */
+    public int getWatchedOpCode() {
+        return mWatchedOpCode;
+    }
+
+    /**
+     * Get the user-id that triggered the app-op mode change to be watched.
+     */
+    public int getCallingUid() {
+        return mCallingUid;
+    }
+
+    /**
+     * Get the process-id that triggered the app-op mode change to be watched.
+     */
+    public int getCallingPid() {
+        return mCallingPid;
+    }
+
+    /**
+     * returns true if the user id passed in the param is the one that is watching for op mode
+     * changed.
+     */
+    public boolean isWatchingUid(int uid) {
+        return uid == UID_ANY || mWatchingUid < 0 || mWatchingUid == uid;
+    }
+
+    /**
+     * Method that should be triggered when the app-op's mode is changed.
+     * @param op app-op whose mode-change is being listened to.
+     * @param uid user-is associated with the app-op.
+     * @param packageName package name associated with the app-op.
+     */
+    public abstract void onOpModeChanged(int op, int uid, String packageName)
+            throws RemoteException;
+
+    /**
+     * Return human readable string representing the listener.
+     */
+    public abstract String toString();
+
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index eba9c7a..e97d9c2 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1771,8 +1771,8 @@
             return;
         }
         Log.w(TAG, "Communication client died");
-        removeCommunicationRouteClient(client.getBinder(), true);
-        onUpdateCommunicationRouteClient("onCommunicationRouteClientDied");
+        setCommunicationRouteForClient(client.getBinder(), client.getPid(), null,
+                BtHelper.SCO_MODE_UNDEFINED, "onCommunicationRouteClientDied");
     }
 
     /**
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index aedbe4e..b7e817e 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -729,8 +729,11 @@
     }
 
     private boolean isDeviceCompatibleWithSpatializationModes(@NonNull AudioDeviceAttributes ada) {
+        // modeForDevice will be neither transaural or binaural for devices that do not support
+        // spatial audio. For instance mono devices like earpiece, speaker safe or sco must
+        // not be included.
         final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(ada.getType(),
-                /*default when type not found*/ SpatializationMode.SPATIALIZER_BINAURAL);
+                /*default when type not found*/ -1);
         if ((modeForDevice == SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported)
                 || (modeForDevice == SpatializationMode.SPATIALIZER_TRANSAURAL
                         && mTransauralSupported)) {
@@ -1538,8 +1541,8 @@
 
         @Override
         public String toString() {
-            return "type:" + mDeviceType + " addr:" + mDeviceAddress + " enabled:" + mEnabled
-                    + " HT:" + mHasHeadTracker + " HTenabled:" + mHeadTrackerEnabled;
+            return "type: " + mDeviceType + " addr: " + mDeviceAddress + " enabled: " + mEnabled
+                    + " HT: " + mHasHeadTracker + " HTenabled: " + mHeadTrackerEnabled;
         }
 
         String toPersistableString() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java
new file mode 100644
index 0000000..0f1fe68
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.hardware.biometrics.SensorPropertiesInternal;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Common attributes for all biometric service providers.
+ *
+ * @param <T> Internal settings type.
+ */
+public interface BiometricServiceProvider<T extends SensorPropertiesInternal> {
+
+    /** Checks if the specified sensor is owned by this provider. */
+    boolean containsSensor(int sensorId);
+
+    /** All sensor properties. */
+    @NonNull
+    List<T> getSensorProperties();
+
+    /** Properties for the given sensor id. */
+    @NonNull
+    T getSensorProperties(int sensorId);
+
+    boolean isHardwareDetected(int sensorId);
+
+    /** If the user has any enrollments for the given sensor. */
+    boolean hasEnrollments(int sensorId, int userId);
+
+    long getAuthenticatorId(int sensorId, int userId);
+
+    @LockoutTracker.LockoutMode
+    int getLockoutModeForUser(int sensorId, int userId);
+
+    void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
+            boolean clearSchedulerBuffer);
+
+    void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd);
+
+    void dumpInternal(int sensorId, @NonNull PrintWriter pw);
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceRegistry.java
new file mode 100644
index 0000000..4779f6f
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceRegistry.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.biometrics.IBiometricAuthenticator;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.SensorPropertiesInternal;
+import android.os.Handler;
+import android.os.IInterface;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * Container for all BiometricServiceProvider implementations.
+ *
+ * @param <T> The service provider type.
+ * @param <P> The internal properties type.
+ * @param <C> The registration callback for {@link #invokeRegisteredCallback(IInterface, List)}.
+ */
+public abstract class BiometricServiceRegistry<T extends BiometricServiceProvider<P>,
+        P extends SensorPropertiesInternal,
+        C extends IInterface> {
+
+    private static final String TAG = "BiometricServiceRegistry";
+
+    // Volatile so they can be read without a lock once all services are registered.
+    // But, ideally remove this and provide immutable copies via the callback instead.
+    @Nullable
+    private volatile List<T> mServiceProviders;
+    @Nullable
+    private volatile List<P> mAllProps;
+
+    @NonNull
+    private final Supplier<IBiometricService> mBiometricServiceSupplier;
+    @NonNull
+    private final RemoteCallbackList<C> mRegisteredCallbacks = new RemoteCallbackList<>();
+
+    public BiometricServiceRegistry(@NonNull Supplier<IBiometricService> biometricSupplier) {
+        mBiometricServiceSupplier = biometricSupplier;
+    }
+
+    /**
+     * Register an implementation by creating a new authenticator and initializing it via
+     * {@link IBiometricService#registerAuthenticator(int, int, int, IBiometricAuthenticator)}
+     * using the given properties.
+     *
+     * @param service service to register with
+     * @param props   internal properties to initialize the authenticator
+     */
+    protected abstract void registerService(@NonNull IBiometricService service, @NonNull P props);
+
+    /**
+     * Invoke the callback to notify clients that all authenticators have been registered.
+     *
+     * @param callback callback to invoke
+     * @param allProps properties of all authenticators
+     */
+    protected abstract void invokeRegisteredCallback(@NonNull C callback,
+            @NonNull List<P> allProps) throws RemoteException;
+
+    /**
+     * Register all authenticators in a background thread.
+     *
+     * @param serviceProvider Supplier function that will be invoked on the background thread.
+     */
+    public void registerAll(Supplier<List<T>> serviceProvider) {
+        // Some HAL might not be started before the system service and will cause the code below
+        // to wait, and some of the operations below might take a significant amount of time to
+        // complete (calls to the HALs). To avoid blocking the rest of system server we put
+        // this on a background thread.
+        final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
+                true /* allowIo */);
+        thread.start();
+        final Handler handler = new Handler(thread.getLooper());
+        handler.post(() -> registerAllInBackground(serviceProvider));
+        thread.quitSafely();
+    }
+
+    /** Register authenticators now, only called by {@link #registerAll(Supplier).} */
+    @VisibleForTesting
+    public void registerAllInBackground(Supplier<List<T>> serviceProvider) {
+        List<T> providers = serviceProvider.get();
+        if (providers == null) {
+            providers = new ArrayList<>();
+        }
+
+        final IBiometricService biometricService = mBiometricServiceSupplier.get();
+        if (biometricService == null) {
+            throw new IllegalStateException("biometric service cannot be null");
+        }
+
+        // Register each sensor individually with BiometricService
+        final List<P> allProps = new ArrayList<>();
+        for (T provider : providers) {
+            final List<P> props = provider.getSensorProperties();
+            for (P prop : props) {
+                registerService(biometricService, prop);
+            }
+            allProps.addAll(props);
+        }
+
+        finishRegistration(providers, allProps);
+    }
+
+    private synchronized void finishRegistration(
+            @NonNull List<T> providers, @NonNull List<P> allProps) {
+        mServiceProviders = Collections.unmodifiableList(providers);
+        mAllProps = Collections.unmodifiableList(allProps);
+        broadcastAllAuthenticatorsRegistered();
+    }
+
+    /**
+     * Add a callback that will be invoked once the work from {@link #registerAll(Supplier)}
+     * has finished registering all providers (executes immediately if already done).
+     *
+     * @param callback registration callback
+     */
+    public synchronized void addAllRegisteredCallback(@Nullable C callback) {
+        if (callback == null) {
+            Slog.e(TAG, "addAllRegisteredCallback, callback is null");
+            return;
+        }
+
+        final boolean registered = mRegisteredCallbacks.register(callback);
+        final boolean allRegistered = mServiceProviders != null;
+        if (registered && allRegistered) {
+            broadcastAllAuthenticatorsRegistered();
+        } else if (!registered) {
+            Slog.e(TAG, "addAllRegisteredCallback failed to register callback");
+        }
+    }
+
+    private synchronized void broadcastAllAuthenticatorsRegistered() {
+        final int n = mRegisteredCallbacks.beginBroadcast();
+        for (int i = 0; i < n; ++i) {
+            final C cb = mRegisteredCallbacks.getBroadcastItem(i);
+            try {
+                invokeRegisteredCallback(cb, mAllProps);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception in broadcastAllAuthenticatorsRegistered", e);
+            } finally {
+                mRegisteredCallbacks.unregister(cb);
+            }
+        }
+        mRegisteredCallbacks.finishBroadcast();
+    }
+
+    /**
+     * Get a list of registered providers.
+     *
+     * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
+     */
+    @NonNull
+    public List<T> getProviders() {
+        return mServiceProviders != null ? mServiceProviders : Collections.emptyList();
+    }
+
+    /**
+     * Gets the provider for given sensor id or null if not registered.
+     *
+     * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
+     */
+    @Nullable
+    public T getProviderForSensor(int sensorId) {
+        if (mServiceProviders != null) {
+            for (T provider : mServiceProviders) {
+                if (provider.containsSensor(sensorId)) {
+                    return provider;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Finds the provider for devices with only a single sensor.
+     *
+     * If no providers returns null. If multiple sensors are present this method
+     * will return the first one that is found (this is a legacy for test devices that
+     * use aidl/hidl concurrently and should not occur on real devices).
+     *
+     * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
+     */
+    @Nullable
+    public Pair<Integer, T> getSingleProvider() {
+        if (mAllProps == null || mAllProps.isEmpty()) {
+            Slog.e(TAG, "No sensors found");
+            return null;
+        }
+
+        if (mAllProps.size() > 1) {
+            Slog.e(TAG, "getSingleProvider() called but multiple sensors present: "
+                    + mAllProps.size());
+        }
+
+        final int sensorId = mAllProps.get(0).sensorId;
+        final T provider = getProviderForSensor(sensorId);
+        if (provider != null) {
+            return new Pair<>(sensorId, provider);
+        }
+
+        Slog.e(TAG, "Single sensor: " + sensorId + ", but provider not found");
+        return null;
+    }
+
+    /**
+     * Get the properties for all providers.
+     *
+     * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
+     */
+    @NonNull
+    public List<P> getAllProperties() {
+        return mAllProps != null ? mAllProps : Collections.emptyList();
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
index 0d789f7..2263e80 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
@@ -23,32 +23,66 @@
 import static android.hardware.biometrics.BiometricStateListener.STATE_KEYGUARD_AUTH;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricStateListener;
 import android.hardware.biometrics.IBiometricStateListener;
+import android.hardware.biometrics.SensorPropertiesInternal;
+import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.UserManager;
 import android.util.Slog;
 
 import com.android.server.biometrics.Utils;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * A callback for receiving notifications about biometric sensor state changes.
+ *
+ * @param <T> service provider type
+ * @param <P> internal property type
  */
-public class BiometricStateCallback implements ClientMonitorCallback {
+public class BiometricStateCallback<T extends BiometricServiceProvider<P>,
+        P extends SensorPropertiesInternal>
+        implements ClientMonitorCallback, IBinder.DeathRecipient {
 
     private static final String TAG = "BiometricStateCallback";
 
     @NonNull
-    private final CopyOnWriteArrayList<IBiometricStateListener>
-            mBiometricStateListeners = new CopyOnWriteArrayList<>();
+    private final CopyOnWriteArrayList<IBiometricStateListener> mBiometricStateListeners =
+            new CopyOnWriteArrayList<>();
+    @NonNull
+    private final UserManager mUserManager;
+    @BiometricStateListener.State
+    private int mBiometricState;
+    @NonNull
+    private List<T> mProviders = List.of();
 
-    private @BiometricStateListener.State int mBiometricState;
-
-    public BiometricStateCallback() {
+    /**
+     * Create a new callback that must be {@link #start(List)}ed.
+     *
+     * @param userManager user manager
+     */
+    public BiometricStateCallback(@NonNull UserManager userManager) {
         mBiometricState = STATE_IDLE;
+        mUserManager = userManager;
     }
 
+    /**
+     * This should be called when the service has been initialized and all providers are ready.
+     *
+     * @param allProviders all registered biometric service providers
+     */
+    public synchronized void start(@NonNull List<T> allProviders) {
+        mProviders = Collections.unmodifiableList(allProviders);
+        broadcastCurrentEnrollmentState(null /* listener */);
+    }
+
+    /** Get the current state. */
+    @BiometricStateListener.State
     public int getBiometricState() {
         return mBiometricState;
     }
@@ -120,23 +154,48 @@
     }
 
     /**
-     * This should be invoked when:
-     * 1) Enrolled --> None-enrolled
-     * 2) None-enrolled --> enrolled
-     * 3) HAL becomes ready
-     * 4) Listener is registered
+     * Enables clients to register a BiometricStateListener. For example, this is used to forward
+     * fingerprint sensor state changes to SideFpsEventHandler.
+     *
+     * @param listener listener to register
      */
-    public void notifyAllEnrollmentStateChanged(int userId, int sensorId,
+    public synchronized void registerBiometricStateListener(
+            @NonNull IBiometricStateListener listener) {
+        mBiometricStateListeners.add(listener);
+        broadcastCurrentEnrollmentState(listener);
+        try {
+            listener.asBinder().linkToDeath(this, 0 /* flags */);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to link to death", e);
+        }
+    }
+
+    private synchronized void broadcastCurrentEnrollmentState(
+            @Nullable IBiometricStateListener listener) {
+        for (T provider : mProviders) {
+            for (SensorPropertiesInternal prop : provider.getSensorProperties()) {
+                for (UserInfo userInfo : mUserManager.getAliveUsers()) {
+                    final boolean enrolled = provider.hasEnrollments(prop.sensorId, userInfo.id);
+                    if (listener != null) {
+                        notifyEnrollmentStateChanged(
+                                listener, userInfo.id, prop.sensorId, enrolled);
+                    } else {
+                        notifyAllEnrollmentStateChanged(
+                                userInfo.id, prop.sensorId, enrolled);
+                    }
+                }
+            }
+        }
+    }
+
+    private void notifyAllEnrollmentStateChanged(int userId, int sensorId,
             boolean hasEnrollments) {
         for (IBiometricStateListener listener : mBiometricStateListeners) {
             notifyEnrollmentStateChanged(listener, userId, sensorId, hasEnrollments);
         }
     }
 
-    /**
-     * Notifies the listener of enrollment state changes.
-     */
-    public void notifyEnrollmentStateChanged(@NonNull IBiometricStateListener listener,
+    private void notifyEnrollmentStateChanged(@NonNull IBiometricStateListener listener,
             int userId, int sensorId, boolean hasEnrollments) {
         try {
             listener.onEnrollmentsChanged(userId, sensorId, hasEnrollments);
@@ -145,13 +204,18 @@
         }
     }
 
-    /**
-     * Enables clients to register a BiometricStateListener. For example, this is used to forward
-     * fingerprint sensor state changes to SideFpsEventHandler.
-     *
-     * @param listener
-     */
-    public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
-        mBiometricStateListeners.add(listener);
+    @Override
+    public void binderDied() {
+        // Do nothing, handled below
     }
-}
+
+    @Override
+    public void binderDied(IBinder who) {
+        Slog.w(TAG, "Callback binder died: " + who);
+        if (mBiometricStateListeners.removeIf(listener -> listener.asBinder().equals(who))) {
+            Slog.w(TAG, "Removed dead listener for " + who);
+        } else {
+            Slog.w(TAG, "No dead listeners found");
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 79e65cc..271bce9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -17,20 +17,18 @@
 package com.android.server.biometrics.sensors.face;
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
-import static android.Manifest.permission.MANAGE_BIOMETRIC;
 import static android.Manifest.permission.MANAGE_FACE;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
-import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.IBiometricStateListener;
 import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
@@ -39,18 +37,18 @@
 import android.hardware.face.Face;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.face.FaceServiceReceiver;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
 import android.hardware.face.IFaceService;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Binder;
-import android.os.Handler;
 import android.os.IBinder;
 import android.os.NativeHandle;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
@@ -58,10 +56,10 @@
 
 import com.android.internal.util.DumpUtils;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.server.ServiceThread;
 import com.android.server.SystemService;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -88,51 +86,10 @@
     private final LockoutResetDispatcher mLockoutResetDispatcher;
     private final LockPatternUtils mLockPatternUtils;
     @NonNull
-    private final List<ServiceProvider> mServiceProviders;
-
-    @Nullable
-    private ServiceProvider getProviderForSensor(int sensorId) {
-        for (ServiceProvider provider : mServiceProviders) {
-            if (provider.containsSensor(sensorId)) {
-                return provider;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * For devices with only a single provider, returns that provider. If no providers, or multiple
-     * providers exist, returns null.
-     */
-    @Nullable
-    private Pair<Integer, ServiceProvider> getSingleProvider() {
-        final List<FaceSensorPropertiesInternal> properties = getSensorProperties();
-        if (properties.size() != 1) {
-            Slog.e(TAG, "Multiple sensors found: " + properties.size());
-            return null;
-        }
-
-        // Theoretically we can just return the first provider, but maybe this is easier to
-        // understand.
-        final int sensorId = properties.get(0).sensorId;
-        for (ServiceProvider provider : mServiceProviders) {
-            if (provider.containsSensor(sensorId)) {
-                return new Pair<>(sensorId, provider);
-            }
-        }
-
-        Slog.e(TAG, "Single sensor, but provider not found");
-        return null;
-    }
-
+    private final FaceServiceRegistry mRegistry;
     @NonNull
-    private List<FaceSensorPropertiesInternal> getSensorProperties() {
-        final List<FaceSensorPropertiesInternal> properties = new ArrayList<>();
-        for (ServiceProvider provider : mServiceProviders) {
-            properties.addAll(provider.getSensorProperties());
-        }
-        return properties;
-    }
+    private final BiometricStateCallback<ServiceProvider, FaceSensorPropertiesInternal>
+            mBiometricStateCallback;
 
     /**
      * Receives the incoming binder calls from FaceManager.
@@ -142,8 +99,7 @@
         @Override
         public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
                 @NonNull String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
 
             if (provider == null) {
                 Slog.w(TAG, "Null provider for createTestSession, sensorId: " + sensorId);
@@ -156,9 +112,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) {
-
             final ProtoOutputStream proto = new ProtoOutputStream();
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider != null) {
                 provider.dumpProtoState(sensorId, proto, clearSchedulerBuffer);
             }
@@ -170,16 +125,14 @@
         @Override // Binder call
         public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(
                 String opPackageName) {
-
-            return FaceService.this.getSensorProperties();
+            return mRegistry.getAllProperties();
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public FaceSensorPropertiesInternal getSensorProperties(int sensorId,
                 @NonNull String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId
                         + ", caller: " + opPackageName);
@@ -193,8 +146,7 @@
         @Override // Binder call
         public void generateChallenge(IBinder token, int sensorId, int userId,
                 IFaceServiceReceiver receiver, String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
                 return;
@@ -207,8 +159,7 @@
         @Override // Binder call
         public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName,
                 long challenge) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
                 return;
@@ -222,8 +173,7 @@
         public long enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
                 final IFaceServiceReceiver receiver, final String opPackageName,
                 final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) {
-
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for enroll");
                 return -1;
@@ -245,8 +195,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
         @Override // Binder call
         public void cancelEnrollment(final IBinder token, long requestId) {
-
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelEnrollment");
                 return;
@@ -260,7 +209,6 @@
         public long authenticate(final IBinder token, final long operationId, int userId,
                 final IFaceServiceReceiver receiver, final String opPackageName,
                 boolean isKeyguardBypassEnabled) {
-
             // TODO(b/152413782): If the sensor supports face detect and the device is encrypted or
             //  lockdown, something wrong happened. See similar path in FingerprintService.
 
@@ -273,7 +221,7 @@
             // permission.
             final boolean isKeyguard = Utils.isKeyguard(getContext(), opPackageName);
 
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for authenticate");
                 return -1;
@@ -301,7 +249,7 @@
                 return -1;
             }
 
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for detectFace");
                 return -1;
@@ -318,8 +266,7 @@
                 IBinder token, long operationId, int userId,
                 IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId,
                 int cookie, boolean allowBackgroundAuthentication) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for prepareForAuthentication");
                 return;
@@ -336,8 +283,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public void startPreparedClient(int sensorId, int cookie) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for startPreparedClient");
                 return;
@@ -350,8 +296,7 @@
         @Override // Binder call
         public void cancelAuthentication(final IBinder token, final String opPackageName,
                 final long requestId) {
-
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelAuthentication");
                 return;
@@ -370,7 +315,7 @@
                 return;
             }
 
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelFaceDetect");
                 return;
@@ -383,8 +328,7 @@
         @Override // Binder call
         public void cancelAuthenticationFromService(int sensorId, final IBinder token,
                 final String opPackageName, final long requestId) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelAuthenticationFromService");
                 return;
@@ -397,8 +341,7 @@
         @Override // Binder call
         public void remove(final IBinder token, final int faceId, final int userId,
                 final IFaceServiceReceiver receiver, final String opPackageName) {
-
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for remove");
                 return;
@@ -412,7 +355,6 @@
         @Override // Binder call
         public void removeAll(final IBinder token, final int userId,
                 final IFaceServiceReceiver receiver, final String opPackageName) {
-
             final FaceServiceReceiver internalReceiver = new FaceServiceReceiver() {
                 int sensorsFinishedRemoving = 0;
                 final int numSensors = getSensorPropertiesInternal(
@@ -432,7 +374,7 @@
 
             // This effectively iterates through all sensors, but has to do so by finding all
             // sensors under each provider.
-            for (ServiceProvider provider : mServiceProviders) {
+            for (ServiceProvider provider : mRegistry.getProviders()) {
                 List<FaceSensorPropertiesInternal> props = provider.getSensorProperties();
                 for (FaceSensorPropertiesInternal prop : props) {
                     provider.scheduleRemoveAll(prop.sensorId, token, userId, internalReceiver,
@@ -467,27 +409,27 @@
             try {
                 if (args.length > 1 && "--proto".equals(args[0]) && "--state".equals(args[1])) {
                     final ProtoOutputStream proto = new ProtoOutputStream(fd);
-                    for (ServiceProvider provider : mServiceProviders) {
+                    for (ServiceProvider provider : mRegistry.getProviders()) {
                         for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
                             provider.dumpProtoState(props.sensorId, proto, false);
                         }
                     }
                     proto.flush();
                 } else if (args.length > 0 && "--proto".equals(args[0])) {
-                    for (ServiceProvider provider : mServiceProviders) {
+                    for (ServiceProvider provider : mRegistry.getProviders()) {
                         for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
                             provider.dumpProtoMetrics(props.sensorId, fd);
                         }
                     }
                 } else if (args.length > 1 && "--hal".equals(args[0])) {
-                    for (ServiceProvider provider : mServiceProviders) {
+                    for (ServiceProvider provider : mRegistry.getProviders()) {
                         for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
                             provider.dumpHal(props.sensorId, fd,
                                     Arrays.copyOfRange(args, 1, args.length, args.getClass()));
                         }
                     }
                 } else {
-                    for (ServiceProvider provider : mServiceProviders) {
+                    for (ServiceProvider provider : mRegistry.getProviders()) {
                         for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
                             pw.println("Dumping for sensorId: " + props.sensorId
                                     + ", provider: " + provider.getClass().getSimpleName());
@@ -504,10 +446,9 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public boolean isHardwareDetected(int sensorId, String opPackageName) {
-
             final long token = Binder.clearCallingIdentity();
             try {
-                final ServiceProvider provider = getProviderForSensor(sensorId);
+                final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
                 if (provider == null) {
                     Slog.w(TAG, "Null provider for isHardwareDetected, caller: " + opPackageName);
                     return false;
@@ -521,12 +462,11 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public List<Face> getEnrolledFaces(int sensorId, int userId, String opPackageName) {
-
             if (userId != UserHandle.getCallingUserId()) {
                 Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
             }
 
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getEnrolledFaces, caller: " + opPackageName);
                 return Collections.emptyList();
@@ -538,12 +478,11 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName) {
-
             if (userId != UserHandle.getCallingUserId()) {
                 Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
             }
 
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for hasEnrolledFaces, caller: " + opPackageName);
                 return false;
@@ -555,8 +494,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getLockoutModeForUser");
                 return LockoutTracker.LOCKOUT_NONE;
@@ -569,8 +507,7 @@
         @Override
         public void invalidateAuthenticatorId(int sensorId, int userId,
                 IInvalidationCallback callback) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for invalidateAuthenticatorId");
                 return;
@@ -582,7 +519,7 @@
         @Override // Binder call
         public long getAuthenticatorId(int sensorId, int userId) {
 
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getAuthenticatorId");
                 return 0;
@@ -595,8 +532,7 @@
         @Override // Binder call
         public void resetLockout(IBinder token, int sensorId, int userId, byte[] hardwareAuthToken,
                 String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName);
                 return;
@@ -610,8 +546,7 @@
         public void setFeature(final IBinder token, int userId, int feature, boolean enabled,
                 final byte[] hardwareAuthToken, IFaceServiceReceiver receiver,
                 final String opPackageName) {
-
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for setFeature");
                 return;
@@ -625,8 +560,7 @@
         @Override
         public void getFeature(final IBinder token, int userId, int feature,
                 IFaceServiceReceiver receiver, final String opPackageName) {
-
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getFeature");
                 return;
@@ -636,18 +570,14 @@
                     new ClientMonitorCallbackConverter(receiver), opPackageName);
         }
 
-        private void addHidlProviders(@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
-            for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
-                mServiceProviders.add(
-                        Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher));
-            }
-        }
+        private List<ServiceProvider> getAidlProviders() {
+            final List<ServiceProvider> providers = new ArrayList<>();
 
-        private void addAidlProviders() {
             final String[] instances = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
             if (instances == null || instances.length == 0) {
-                return;
+                return providers;
             }
+
             for (String instance : instances) {
                 final String fqName = IFace.DESCRIPTOR + "/" + instance;
                 final IFace face = IFace.Stub.asInterface(
@@ -660,53 +590,41 @@
                     final SensorProps[] props = face.getSensorProps();
                     final FaceProvider provider = new FaceProvider(getContext(), props, instance,
                             mLockoutResetDispatcher, BiometricContext.getInstance(getContext()));
-                    mServiceProviders.add(provider);
+                    providers.add(provider);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
                 }
             }
+
+            return providers;
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public void registerAuthenticators(
                 @NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
-
-            // Some HAL might not be started before the system service and will cause the code below
-            // to wait, and some of the operations below might take a significant amount of time to
-            // complete (calls to the HALs). To avoid blocking the rest of system server we put
-            // this on a background thread.
-            final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
-                    true /* allowIo */);
-            thread.start();
-            final Handler handler = new Handler(thread.getLooper());
-
-            handler.post(() -> {
-                addHidlProviders(hidlSensors);
-                addAidlProviders();
-
-                final IBiometricService biometricService = IBiometricService.Stub.asInterface(
-                        ServiceManager.getService(Context.BIOMETRIC_SERVICE));
-
-                // Register each sensor individually with BiometricService
-                for (ServiceProvider provider : mServiceProviders) {
-                    final List<FaceSensorPropertiesInternal> props = provider.getSensorProperties();
-                    for (FaceSensorPropertiesInternal prop : props) {
-                        final int sensorId = prop.sensorId;
-                        final @BiometricManager.Authenticators.Types int strength =
-                                Utils.propertyStrengthToAuthenticatorStrength(prop.sensorStrength);
-                        final FaceAuthenticator authenticator = new FaceAuthenticator(
-                                mServiceWrapper, sensorId);
-                        try {
-                            biometricService.registerAuthenticator(sensorId, TYPE_FACE, strength,
-                                    authenticator);
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "Remote exception when registering sensorId: " + sensorId);
-                        }
-                    }
+            mRegistry.registerAll(() -> {
+                final List<ServiceProvider> providers = new ArrayList<>();
+                for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
+                    providers.add(
+                            Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher));
                 }
+                providers.addAll(getAidlProviders());
+                return providers;
             });
         }
+
+        @Override
+        public void addAuthenticatorsRegisteredCallback(
+                IFaceAuthenticatorsRegisteredCallback callback) {
+            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+            mRegistry.addAllRegisteredCallback(callback);
+        }
+
+        @Override
+        public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
+            mBiometricStateCallback.registerBiometricStateListener(listener);
+        }
     }
 
     public FaceService(Context context) {
@@ -714,7 +632,16 @@
         mServiceWrapper = new FaceServiceWrapper();
         mLockoutResetDispatcher = new LockoutResetDispatcher(context);
         mLockPatternUtils = new LockPatternUtils(context);
-        mServiceProviders = new ArrayList<>();
+        mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
+        mRegistry = new FaceServiceRegistry(mServiceWrapper,
+                () -> IBiometricService.Stub.asInterface(
+                        ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+        mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
+            @Override
+            public void onAllAuthenticatorsRegistered(List<FaceSensorPropertiesInternal> sensors) {
+                mBiometricStateCallback.start(mRegistry.getProviders());
+            }
+        });
     }
 
     @Override
@@ -752,7 +679,7 @@
         if (Utils.isVirtualEnabled(getContext())) {
             Slog.i(TAG, "Sync virtual enrollments");
             final int userId = ActivityManager.getCurrentUser();
-            for (ServiceProvider provider : mServiceProviders) {
+            for (ServiceProvider provider : mRegistry.getProviders()) {
                 for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
                     provider.scheduleInternalCleanup(props.sensorId, userId, null /* callback */,
                             true /* favorHalEnrollments */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java
new file mode 100644
index 0000000..0f0a81d
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
+import android.hardware.face.IFaceService;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.BiometricServiceRegistry;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+/** Registry for {@link IFaceService} providers. */
+public class FaceServiceRegistry extends BiometricServiceRegistry<ServiceProvider,
+        FaceSensorPropertiesInternal, IFaceAuthenticatorsRegisteredCallback> {
+
+    private static final String TAG = "FaceServiceRegistry";
+
+    @NonNull
+    private final IFaceService mService;
+
+    /** Creates a new registry tied to the given service. */
+    public FaceServiceRegistry(@NonNull IFaceService service,
+            @Nullable Supplier<IBiometricService> biometricSupplier) {
+        super(biometricSupplier);
+        mService = service;
+    }
+
+    @Override
+    protected void registerService(@NonNull IBiometricService service,
+            @NonNull FaceSensorPropertiesInternal props) {
+        @BiometricManager.Authenticators.Types final int strength =
+                Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
+        try {
+            service.registerAuthenticator(props.sensorId, TYPE_FACE, strength,
+                    new FaceAuthenticator(mService, props.sensorId));
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId);
+        }
+    }
+
+    @Override
+    protected void invokeRegisteredCallback(@NonNull IFaceAuthenticatorsRegisteredCallback callback,
+            @NonNull List<FaceSensorPropertiesInternal> allProps) throws RemoteException {
+        callback.onAllAuthenticatorsRegistered(allProps);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index 6f98365..4efaedb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -26,15 +26,13 @@
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.IBinder;
-import android.util.proto.ProtoOutputStream;
 import android.view.Surface;
 
+import com.android.server.biometrics.sensors.BiometricServiceProvider;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.LockoutTracker;
 
 import java.io.FileDescriptor;
-import java.io.PrintWriter;
 import java.util.List;
 
 /**
@@ -56,24 +54,11 @@
  * to check (e.g. via {@link FaceManager#getSensorPropertiesInternal()}) that the code path isn't
  * taken. ServiceProviders will provide a no-op for unsupported operations to fail safely.
  */
-public interface ServiceProvider {
-    /**
-     * Checks if the specified sensor is owned by this provider.
-     */
-    boolean containsSensor(int sensorId);
-
-    @NonNull
-    List<FaceSensorPropertiesInternal> getSensorProperties();
-
-    @NonNull
-    FaceSensorPropertiesInternal getSensorProperties(int sensorId);
+public interface ServiceProvider extends BiometricServiceProvider<FaceSensorPropertiesInternal> {
 
     @NonNull
     List<Face> getEnrolledFaces(int sensorId, int userId);
 
-    @LockoutTracker.LockoutMode
-    int getLockoutModeForUser(int sensorId, int userId);
-
     /**
      * Requests for the authenticatorId (whose source of truth is in the TEE or equivalent) to be
      * invalidated. See {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}
@@ -84,10 +69,6 @@
                 + " this method");
     }
 
-    long getAuthenticatorId(int sensorId, int userId);
-
-    boolean isHardwareDetected(int sensorId);
-
     void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
             @NonNull IFaceServiceReceiver receiver, String opPackageName);
 
@@ -142,13 +123,6 @@
     void scheduleInternalCleanup(int sensorId, int userId,
             @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments);
 
-    void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
-            boolean clearSchedulerBuffer);
-
-    void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd);
-
-    void dumpInternal(int sensorId, @NonNull PrintWriter pw);
-
     @NonNull
     ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
             @NonNull String opPackageName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 19d54c8..6bff179 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -285,6 +285,11 @@
     }
 
     @Override
+    public boolean hasEnrollments(int sensorId, int userId) {
+        return !getEnrolledFaces(sensorId, userId).isEmpty();
+    }
+
+    @Override
     public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
             @NonNull IInvalidationCallback callback) {
         mHandler.post(() -> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 6528912..c0a119f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -484,6 +484,11 @@
     }
 
     @Override
+    public boolean hasEnrollments(int sensorId, int userId) {
+        return !getEnrolledFaces(sensorId, userId).isEmpty();
+    }
+
+    @Override
     @LockoutTracker.LockoutMode
     public int getLockoutModeForUser(int sensorId, int userId) {
         return mLockoutTracker.getLockoutModeForUser(userId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 2ba449a..7e2742e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -17,14 +17,11 @@
 package com.android.server.biometrics.sensors.fingerprint;
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
-import static android.Manifest.permission.MANAGE_BIOMETRIC;
 import static android.Manifest.permission.MANAGE_FINGERPRINT;
-import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
 import static android.Manifest.permission.TEST_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.Manifest.permission.USE_FINGERPRINT;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR;
@@ -36,8 +33,6 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -65,7 +60,6 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Process;
-import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -79,11 +73,9 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.server.ServiceThread;
 import com.android.server.SystemService;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
@@ -115,74 +107,32 @@
 
     protected static final String TAG = "FingerprintService";
 
-    private final Object mLock = new Object();
     private final AppOpsManager mAppOps;
     private final LockoutResetDispatcher mLockoutResetDispatcher;
     private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
     private final LockPatternUtils mLockPatternUtils;
-    @NonNull private final List<ServiceProvider> mServiceProviders;
-    @NonNull private final BiometricStateCallback mBiometricStateCallback;
-    @NonNull private final Handler mHandler;
-    @NonNull private final BiometricContext mBiometricContext;
-    @NonNull private final Supplier<IBiometricService> mBiometricServiceSupplier;
-    @NonNull private final Function<String, IFingerprint> mIFingerprintProvider;
+    @NonNull
+    private final BiometricContext mBiometricContext;
+    @NonNull
+    private final Supplier<String[]> mAidlInstanceNameSupplier;
+    @NonNull
+    private final Function<String, IFingerprint> mIFingerprintProvider;
+    @NonNull
+    private final BiometricStateCallback<ServiceProvider, FingerprintSensorPropertiesInternal>
+            mBiometricStateCallback;
+    @NonNull
+    private final Handler mHandler;
+    @NonNull
+    private final FingerprintServiceRegistry mRegistry;
 
-    @GuardedBy("mLock")
-    @NonNull private final RemoteCallbackList<IFingerprintAuthenticatorsRegisteredCallback>
-            mAuthenticatorsRegisteredCallbacks;
-
-    @GuardedBy("mLock")
-    @NonNull private final List<FingerprintSensorPropertiesInternal> mSensorProps;
-
-    /**
-     * Registers BiometricStateListener in list stored by FingerprintService
-     * @param listener new BiometricStateListener being added
-     */
-    public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
-        mBiometricStateCallback.registerBiometricStateListener(listener);
-        broadcastCurrentEnrollmentState(listener);
-    }
-
-    /**
-     * @param listener if non-null, notifies only this listener. if null, notifies all listeners
-     *                 in {@link BiometricStateCallback}. This is slightly ugly, but reduces
-     *                 redundant code.
-     */
-    private void broadcastCurrentEnrollmentState(@Nullable IBiometricStateListener listener) {
-        final UserManager um = UserManager.get(getContext());
-        synchronized (mLock) {
-            // Update the new listener with current state of all sensors
-            for (FingerprintSensorPropertiesInternal prop : mSensorProps) {
-                final ServiceProvider provider = getProviderForSensor(prop.sensorId);
-                for (UserInfo userInfo : um.getAliveUsers()) {
-                    final boolean enrolled = !provider
-                            .getEnrolledFingerprints(prop.sensorId, userInfo.id).isEmpty();
-
-                    // Defer this work and allow the loop to release the lock sooner
-                    mHandler.post(() -> {
-                        if (listener != null) {
-                            mBiometricStateCallback.notifyEnrollmentStateChanged(
-                                    listener, userInfo.id, prop.sensorId, enrolled);
-                        } else {
-                            mBiometricStateCallback.notifyAllEnrollmentStateChanged(
-                                    userInfo.id, prop.sensorId, enrolled);
-                        }
-                    });
-                }
-            }
-        }
-    }
-
-    /**
-     * Receives the incoming binder calls from FingerprintManager.
-     */
-    private final IFingerprintService.Stub mServiceWrapper = new IFingerprintService.Stub() {
+    /** Receives the incoming binder calls from FingerprintManager. */
+    @VisibleForTesting
+    final IFingerprintService.Stub mServiceWrapper = new IFingerprintService.Stub() {
         @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
         @Override
         public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
                 @NonNull String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
 
             if (provider == null) {
                 Slog.w(TAG, "Null provider for createTestSession, sensorId: " + sensorId);
@@ -195,9 +145,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) {
-
             final ProtoOutputStream proto = new ProtoOutputStream();
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider != null) {
                 provider.dumpProtoState(sensorId, proto, clearSchedulerBuffer);
             }
@@ -212,16 +161,14 @@
                     != PackageManager.PERMISSION_GRANTED) {
                 Utils.checkPermission(getContext(), TEST_BIOMETRIC);
             }
-
-            return FingerprintService.this.getSensorProperties();
+            return mRegistry.getAllProperties();
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public FingerprintSensorPropertiesInternal getSensorProperties(int sensorId,
                 @NonNull String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId
                         + ", caller: " + opPackageName);
@@ -234,8 +181,7 @@
         @Override // Binder call
         public void generateChallenge(IBinder token, int sensorId, int userId,
                 IFingerprintServiceReceiver receiver, String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
                 return;
@@ -248,8 +194,7 @@
         @Override // Binder call
         public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName,
                 long challenge) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
                 return;
@@ -264,8 +209,7 @@
         public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
                 final int userId, final IFingerprintServiceReceiver receiver,
                 final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
-
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for enroll");
                 return -1;
@@ -278,8 +222,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
         @Override // Binder call
         public void cancelEnrollment(final IBinder token, long requestId) {
-
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelEnrollment");
                 return;
@@ -339,10 +282,10 @@
 
             final Pair<Integer, ServiceProvider> provider;
             if (sensorId == FingerprintManager.SENSOR_ID_ANY) {
-                provider = getSingleProvider();
+                provider = mRegistry.getSingleProvider();
             } else {
                 Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
-                provider = new Pair<>(sensorId, getProviderForSensor(sensorId));
+                provider = new Pair<>(sensorId, mRegistry.getProviderForSensor(sensorId));
             }
             if (provider == null) {
                 Slog.w(TAG, "Null provider for authenticate");
@@ -374,7 +317,6 @@
                 final IFingerprintServiceReceiver receiver,
                 final String opPackageName,
                 boolean ignoreEnrollmentState) throws PackageManager.NameNotFoundException {
-
             final Context context = getUiContext();
             final Context promptContext = context.createPackageContextAsUser(
                     opPackageName, 0 /* flags */, UserHandle.getUserHandleForUid(uId));
@@ -468,7 +410,7 @@
                 return -1;
             }
 
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for detectFingerprint");
                 return -1;
@@ -484,8 +426,7 @@
         public void prepareForAuthentication(int sensorId, IBinder token, long operationId,
                 int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
                 long requestId, int cookie, boolean allowBackgroundAuthentication) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for prepareForAuthentication");
                 return;
@@ -501,8 +442,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
         @Override // Binder call
         public void startPreparedClient(int sensorId, int cookie) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for startPreparedClient");
                 return;
@@ -532,7 +472,7 @@
                 return;
             }
 
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelAuthentication");
                 return;
@@ -553,7 +493,7 @@
 
             // For IBiometricsFingerprint2.1, cancelling fingerprint detect is the same as
             // cancelling authentication.
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelFingerprintDetect");
                 return;
@@ -566,11 +506,9 @@
         @Override // Binder call
         public void cancelAuthenticationFromService(final int sensorId, final IBinder token,
                 final String opPackageName, final long requestId) {
-
-
             Slog.d(TAG, "cancelAuthenticationFromService, sensorId: " + sensorId);
 
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for cancelAuthenticationFromService");
                 return;
@@ -583,8 +521,7 @@
         @Override // Binder call
         public void remove(final IBinder token, final int fingerId, final int userId,
                 final IFingerprintServiceReceiver receiver, final String opPackageName) {
-
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for remove");
                 return;
@@ -617,7 +554,7 @@
 
             // This effectively iterates through all sensors, but has to do so by finding all
             // sensors under each provider.
-            for (ServiceProvider provider : mServiceProviders) {
+            for (ServiceProvider provider : mRegistry.getProviders()) {
                 List<FingerprintSensorPropertiesInternal> props = provider.getSensorProperties();
                 for (FingerprintSensorPropertiesInternal prop : props) {
                     provider.scheduleRemoveAll(prop.sensorId, token, internalReceiver, userId,
@@ -652,7 +589,7 @@
             try {
                 if (args.length > 1 && "--proto".equals(args[0]) && "--state".equals(args[1])) {
                     final ProtoOutputStream proto = new ProtoOutputStream(fd);
-                    for (ServiceProvider provider : mServiceProviders) {
+                    for (ServiceProvider provider : mRegistry.getProviders()) {
                         for (FingerprintSensorPropertiesInternal props
                                 : provider.getSensorProperties()) {
                             provider.dumpProtoState(props.sensorId, proto, false);
@@ -660,14 +597,14 @@
                     }
                     proto.flush();
                 } else if (args.length > 0 && "--proto".equals(args[0])) {
-                    for (ServiceProvider provider : mServiceProviders) {
+                    for (ServiceProvider provider : mRegistry.getProviders()) {
                         for (FingerprintSensorPropertiesInternal props
                                 : provider.getSensorProperties()) {
                             provider.dumpProtoMetrics(props.sensorId, fd);
                         }
                     }
                 } else {
-                    for (ServiceProvider provider : mServiceProviders) {
+                    for (ServiceProvider provider : mRegistry.getProviders()) {
                         for (FingerprintSensorPropertiesInternal props
                                 : provider.getSensorProperties()) {
                             pw.println("Dumping for sensorId: " + props.sensorId
@@ -698,7 +635,7 @@
 
             final long token = Binder.clearCallingIdentity();
             try {
-                final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+                final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
                 if (provider == null) {
                     Slog.w(TAG, "Null provider for isHardwareDetectedDeprecated, caller: "
                             + opPackageName);
@@ -713,8 +650,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public boolean isHardwareDetected(int sensorId, String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for isHardwareDetected, caller: " + opPackageName);
                 return false;
@@ -730,7 +666,7 @@
                 return;
             }
 
-            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for rename");
                 return;
@@ -781,8 +717,7 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         public boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for hasEnrolledFingerprints, caller: " + opPackageName);
                 return false;
@@ -794,8 +729,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getLockoutModeForUser");
                 return LockoutTracker.LOCKOUT_NONE;
@@ -807,8 +741,7 @@
         @Override
         public void invalidateAuthenticatorId(int sensorId, int userId,
                 IInvalidationCallback callback) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for invalidateAuthenticatorId");
                 return;
@@ -819,8 +752,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
         public long getAuthenticatorId(int sensorId, int userId) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for getAuthenticatorId");
                 return 0;
@@ -832,8 +764,7 @@
         @Override // Binder call
         public void resetLockout(IBinder token, int sensorId, int userId,
                 @Nullable byte[] hardwareAuthToken, String opPackageName) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName);
                 return;
@@ -864,55 +795,38 @@
         @Override // Binder call
         public void registerAuthenticators(
                 @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
-
-            // Some HAL might not be started before the system service and will cause the code below
-            // to wait, and some of the operations below might take a significant amount of time to
-            // complete (calls to the HALs). To avoid blocking the rest of system server we put
-            // this on a background thread.
-            final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
-                    true /* allowIo */);
-            thread.start();
-            final Handler handler = new Handler(thread.getLooper());
-            handler.post(() -> {
+            mRegistry.registerAll(() -> {
+                final List<ServiceProvider> providers = new ArrayList<>();
+                providers.addAll(getHidlProviders(hidlSensors));
                 List<String> aidlSensors = new ArrayList<>();
-                final String[] instances =
-                        ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR);
+                final String[] instances = mAidlInstanceNameSupplier.get();
                 if (instances != null) {
                     aidlSensors.addAll(Lists.newArrayList(instances));
                 }
-                registerAuthenticatorsForService(aidlSensors, hidlSensors);
+                providers.addAll(getAidlProviders(
+                        Utils.filterAvailableHalInstances(getContext(), aidlSensors)));
+                return providers;
             });
-            thread.quitSafely();
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void addAuthenticatorsRegisteredCallback(
                 IFingerprintAuthenticatorsRegisteredCallback callback) {
-            if (callback == null) {
-                Slog.e(TAG, "addAuthenticatorsRegisteredCallback, callback is null");
-                return;
-            }
+            mRegistry.addAllRegisteredCallback(callback);
+        }
 
-            final boolean registered;
-            final boolean hasSensorProps;
-            synchronized (mLock) {
-                registered = mAuthenticatorsRegisteredCallbacks.register(callback);
-                hasSensorProps = !mSensorProps.isEmpty();
-            }
-            if (registered && hasSensorProps) {
-                broadcastAllAuthenticatorsRegistered();
-            } else if (!registered) {
-                Slog.e(TAG, "addAuthenticatorsRegisteredCallback failed to register callback");
-            }
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @Override
+        public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
+            mBiometricStateCallback.registerBiometricStateListener(listener);
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void onPointerDown(long requestId, int sensorId, int x, int y,
                 float minor, float major) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching provider for onFingerDown, sensorId: " + sensorId);
                 return;
@@ -923,8 +837,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void onPointerUp(long requestId, int sensorId) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching provider for onFingerUp, sensorId: " + sensorId);
                 return;
@@ -935,8 +848,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void onUiReady(long requestId, int sensorId) {
-
-            final ServiceProvider provider = getProviderForSensor(sensorId);
+            final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
             if (provider == null) {
                 Slog.w(TAG, "No matching provider for onUiReady, sensorId: " + sensorId);
                 return;
@@ -947,8 +859,7 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
-
-            for (ServiceProvider provider : mServiceProviders) {
+            for (ServiceProvider provider : mRegistry.getProviders()) {
                 provider.setUdfpsOverlayController(controller);
             }
         }
@@ -956,22 +867,15 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void setSidefpsController(@NonNull ISidefpsController controller) {
-
-            for (ServiceProvider provider : mServiceProviders) {
+            for (ServiceProvider provider : mRegistry.getProviders()) {
                 provider.setSidefpsController(controller);
             }
         }
 
-        @Override
-        public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
-            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
-            FingerprintService.this.registerBiometricStateListener(listener);
-        }
-
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void onPowerPressed() {
-            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
-            for (ServiceProvider provider : mServiceProviders) {
+            for (ServiceProvider provider : mRegistry.getProviders()) {
                 provider.onPowerPressed();
             }
         }
@@ -981,6 +885,7 @@
         this(context, BiometricContext.getInstance(context),
                 () -> IBiometricService.Stub.asInterface(
                         ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
+                () -> ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR),
                 (fqName) -> IFingerprint.Stub.asInterface(
                         Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName))));
     }
@@ -988,61 +893,34 @@
     @VisibleForTesting
     FingerprintService(Context context,
             BiometricContext biometricContext,
-            Supplier<IBiometricService> biometricServiceProvider,
+            Supplier<IBiometricService> biometricServiceSupplier,
+            Supplier<String[]> aidlInstanceNameSupplier,
             Function<String, IFingerprint> fingerprintProvider) {
         super(context);
         mBiometricContext = biometricContext;
-        mBiometricServiceSupplier = biometricServiceProvider;
+        mAidlInstanceNameSupplier = aidlInstanceNameSupplier;
         mIFingerprintProvider = fingerprintProvider;
         mAppOps = context.getSystemService(AppOpsManager.class);
         mGestureAvailabilityDispatcher = new GestureAvailabilityDispatcher();
         mLockoutResetDispatcher = new LockoutResetDispatcher(context);
         mLockPatternUtils = new LockPatternUtils(context);
-        mServiceProviders = new ArrayList<>();
-        mBiometricStateCallback = new BiometricStateCallback();
-        mAuthenticatorsRegisteredCallbacks = new RemoteCallbackList<>();
-        mSensorProps = new ArrayList<>();
+        mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
         mHandler = new Handler(Looper.getMainLooper());
+        mRegistry = new FingerprintServiceRegistry(mServiceWrapper, biometricServiceSupplier);
+        mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+            @Override
+            public void onAllAuthenticatorsRegistered(
+                    List<FingerprintSensorPropertiesInternal> sensors) {
+                mBiometricStateCallback.start(mRegistry.getProviders());
+            }
+        });
     }
 
-    @VisibleForTesting
-    void registerAuthenticatorsForService(@NonNull List<String> aidlInstanceNames,
+    @NonNull
+    private List<ServiceProvider> getHidlProviders(
             @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
-        addHidlProviders(hidlSensors);
-        addAidlProviders(Utils.filterAvailableHalInstances(getContext(), aidlInstanceNames));
+        final List<ServiceProvider> providers = new ArrayList<>();
 
-        final IBiometricService biometricService = mBiometricServiceSupplier.get();
-
-        // Register each sensor individually with BiometricService
-        for (ServiceProvider provider : mServiceProviders) {
-            final List<FingerprintSensorPropertiesInternal> props =
-                    provider.getSensorProperties();
-            for (FingerprintSensorPropertiesInternal prop : props) {
-                final int sensorId = prop.sensorId;
-                @BiometricManager.Authenticators.Types final int strength =
-                        Utils.propertyStrengthToAuthenticatorStrength(prop.sensorStrength);
-                final FingerprintAuthenticator authenticator = new FingerprintAuthenticator(
-                        mServiceWrapper, sensorId);
-                try {
-                    biometricService.registerAuthenticator(sensorId, TYPE_FINGERPRINT,
-                            strength, authenticator);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Remote exception when registering sensorId: " + sensorId);
-                }
-            }
-        }
-
-        synchronized (mLock) {
-            for (ServiceProvider provider : mServiceProviders) {
-                mSensorProps.addAll(provider.getSensorProperties());
-            }
-        }
-
-        broadcastCurrentEnrollmentState(null); // broadcasts to all listeners
-        broadcastAllAuthenticatorsRegistered();
-    }
-
-    private void addHidlProviders(List<FingerprintSensorPropertiesInternal> hidlSensors) {
         for (FingerprintSensorPropertiesInternal hidlSensor : hidlSensors) {
             final Fingerprint21 fingerprint21;
             if ((Build.IS_USERDEBUG || Build.IS_ENG)
@@ -1059,11 +937,16 @@
                         mBiometricStateCallback, hidlSensor, mHandler,
                         mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
             }
-            mServiceProviders.add(fingerprint21);
+            providers.add(fingerprint21);
         }
+
+        return providers;
     }
 
-    private void addAidlProviders(List<String> instances) {
+    @NonNull
+    private List<ServiceProvider> getAidlProviders(@NonNull List<String> instances) {
+        final List<ServiceProvider> providers = new ArrayList<>();
+
         for (String instance : instances) {
             final String fqName = IFingerprint.DESCRIPTOR + "/" + instance;
             final IFingerprint fp = mIFingerprintProvider.apply(fqName);
@@ -1075,7 +958,7 @@
                             mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
                             mBiometricContext);
                     Slog.i(TAG, "Adding AIDL provider: " + fqName);
-                    mServiceProviders.add(provider);
+                    providers.add(provider);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
                 }
@@ -1083,38 +966,8 @@
                 Slog.e(TAG, "Unable to get declared service: " + fqName);
             }
         }
-    }
 
-    // Notifies the callbacks that all of the authenticators have been registered and removes the
-    // invoked callbacks from the callback list.
-    private void broadcastAllAuthenticatorsRegistered() {
-        // Make a local copy of the data so it can be used outside of the synchronized block when
-        // making Binder calls.
-        final List<IFingerprintAuthenticatorsRegisteredCallback> callbacks = new ArrayList<>();
-        final List<FingerprintSensorPropertiesInternal> props;
-        synchronized (mLock) {
-            if (!mSensorProps.isEmpty()) {
-                props = new ArrayList<>(mSensorProps);
-            } else {
-                Slog.e(TAG, "mSensorProps is empty");
-                return;
-            }
-            final int n = mAuthenticatorsRegisteredCallbacks.beginBroadcast();
-            for (int i = 0; i < n; ++i) {
-                final IFingerprintAuthenticatorsRegisteredCallback cb =
-                        mAuthenticatorsRegisteredCallbacks.getBroadcastItem(i);
-                callbacks.add(cb);
-                mAuthenticatorsRegisteredCallbacks.unregister(cb);
-            }
-            mAuthenticatorsRegisteredCallbacks.finishBroadcast();
-        }
-        for (IFingerprintAuthenticatorsRegisteredCallback cb : callbacks) {
-            try {
-                cb.onAllAuthenticatorsRegistered(props);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception in onAllAuthenticatorsRegistered", e);
-            }
-        }
+        return providers;
     }
 
     @Override
@@ -1122,51 +975,9 @@
         publishBinderService(Context.FINGERPRINT_SERVICE, mServiceWrapper);
     }
 
-    @Nullable
-    private ServiceProvider getProviderForSensor(int sensorId) {
-        for (ServiceProvider provider : mServiceProviders) {
-            if (provider.containsSensor(sensorId)) {
-                return provider;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * For devices with only a single provider, returns that provider. If multiple providers,
-     * returns the first one. If no providers, returns null.
-     */
-    @Nullable
-    private Pair<Integer, ServiceProvider> getSingleProvider() {
-        final List<FingerprintSensorPropertiesInternal> properties = getSensorProperties();
-        if (properties.isEmpty()) {
-            Slog.e(TAG, "No providers found");
-            return null;
-        }
-
-        // Theoretically we can just return the first provider, but maybe this is easier to
-        // understand.
-        final int sensorId = properties.get(0).sensorId;
-        for (ServiceProvider provider : mServiceProviders) {
-            if (provider.containsSensor(sensorId)) {
-                return new Pair<>(sensorId, provider);
-            }
-        }
-
-        Slog.e(TAG, "Provider not found");
-        return null;
-    }
-
-    @NonNull
-    private List<FingerprintSensorPropertiesInternal> getSensorProperties() {
-        synchronized (mLock) {
-            return mSensorProps;
-        }
-    }
-
     @NonNull
     private List<Fingerprint> getEnrolledFingerprintsDeprecated(int userId, String opPackageName) {
-        final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+        final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
         if (provider == null) {
             Slog.w(TAG, "Null provider for getEnrolledFingerprintsDeprecated, caller: "
                     + opPackageName);
@@ -1229,7 +1040,7 @@
         if (Utils.isVirtualEnabled(getContext())) {
             Slog.i(TAG, "Sync virtual enrollments");
             final int userId = ActivityManager.getCurrentUser();
-            for (ServiceProvider provider : mServiceProviders) {
+            for (ServiceProvider provider : mRegistry.getProviders()) {
                 for (FingerprintSensorPropertiesInternal props : provider.getSensorProperties()) {
                     provider.scheduleInternalCleanup(props.sensorId, userId, null /* callback */,
                             true /* favorHalEnrollments */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java
new file mode 100644
index 0000000..33810b7
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.hardware.fingerprint.IFingerprintService;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.BiometricServiceRegistry;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+/** Registry for {@link IFingerprintService} providers. */
+public class FingerprintServiceRegistry extends BiometricServiceRegistry<ServiceProvider,
+        FingerprintSensorPropertiesInternal, IFingerprintAuthenticatorsRegisteredCallback> {
+
+    private static final String TAG = "FingerprintServiceRegistry";
+
+    @NonNull
+    private final IFingerprintService mService;
+
+    /** Creates a new registry tied to the given service. */
+    public FingerprintServiceRegistry(@NonNull IFingerprintService service,
+            @Nullable Supplier<IBiometricService> biometricSupplier) {
+        super(biometricSupplier);
+        mService = service;
+    }
+
+    @Override
+    protected void registerService(@NonNull IBiometricService service,
+            @NonNull FingerprintSensorPropertiesInternal props) {
+        @BiometricManager.Authenticators.Types final int strength =
+                Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
+        try {
+            service.registerAuthenticator(props.sensorId, TYPE_FINGERPRINT, strength,
+                    new FingerprintAuthenticator(mService, props.sensorId));
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId);
+        }
+    }
+
+    @Override
+    protected void invokeRegisteredCallback(
+            @NonNull IFingerprintAuthenticatorsRegisteredCallback callback,
+            @NonNull List<FingerprintSensorPropertiesInternal> allProps) throws RemoteException {
+        callback.onAllAuthenticatorsRegistered(allProps);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 275d7e4..9075e7e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -28,14 +28,11 @@
 import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
-import android.util.proto.ProtoOutputStream;
 
+import com.android.server.biometrics.sensors.BiometricServiceProvider;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.LockoutTracker;
 
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
 import java.util.List;
 
 /**
@@ -59,23 +56,8 @@
  * fail safely.
  */
 @SuppressWarnings("deprecation")
-public interface ServiceProvider {
-    /**
-     * Checks if the specified sensor is owned by this provider.
-     */
-    boolean containsSensor(int sensorId);
-
-    @NonNull
-    List<FingerprintSensorPropertiesInternal> getSensorProperties();
-
-    /**
-     * Returns the internal properties of the specified sensor, if owned by this provider.
-     *
-     * @param sensorId The ID of a fingerprint sensor, or -1 for any sensor.
-     * @return An object representing the internal properties of the specified sensor.
-     */
-    @Nullable
-    FingerprintSensorPropertiesInternal getSensorProperties(int sensorId);
+public interface ServiceProvider extends
+        BiometricServiceProvider<FingerprintSensorPropertiesInternal> {
 
     void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken);
 
@@ -126,16 +108,11 @@
     void scheduleInternalCleanup(int sensorId, int userId,
             @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments);
 
-    boolean isHardwareDetected(int sensorId);
-
     void rename(int sensorId, int fingerId, int userId, @NonNull String name);
 
     @NonNull
     List<Fingerprint> getEnrolledFingerprints(int sensorId, int userId);
 
-    @LockoutTracker.LockoutMode
-    int getLockoutModeForUser(int sensorId, int userId);
-
     /**
      * Requests for the authenticatorId (whose source of truth is in the TEE or equivalent) to
      * be invalidated. See {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}
@@ -143,7 +120,6 @@
     void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
             @NonNull IInvalidationCallback callback);
 
-    long getAuthenticatorId(int sensorId, int userId);
 
     void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major);
 
@@ -161,13 +137,6 @@
      */
     void setSidefpsController(@NonNull ISidefpsController controller);
 
-    void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
-            boolean clearSchedulerBuffer);
-
-    void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd);
-
-    void dumpInternal(int sensorId, @NonNull PrintWriter pw);
-
     @NonNull
     ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
             @NonNull String opPackageName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 2dc00520..3fe6332 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -565,6 +565,11 @@
     }
 
     @Override
+    public boolean hasEnrollments(int sensorId, int userId) {
+        return !getEnrolledFingerprints(sensorId, userId).isEmpty();
+    }
+
+    @Override
     public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
             @NonNull IInvalidationCallback callback) {
         mHandler.post(() -> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index ed482f0..0e6df8e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -789,6 +789,11 @@
     }
 
     @Override
+    public boolean hasEnrollments(int sensorId, int userId) {
+        return !getEnrolledFingerprints(sensorId, userId).isEmpty();
+    }
+
+    @Override
     @LockoutTracker.LockoutMode public int getLockoutModeForUser(int sensorId, int userId) {
         return mLockoutTracker.getLockoutModeForUser(userId);
     }
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java
new file mode 100644
index 0000000..48112c4
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.hal2;
+
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.Slog;
+
+final class RadioEventLogger {
+    private final String mTag;
+    private final LocalLog mEventLogger;
+
+    RadioEventLogger(String tag, int loggerQueueSize) {
+        mTag = tag;
+        mEventLogger = new LocalLog(loggerQueueSize);
+    }
+
+    void logRadioEvent(String logFormat, Object... args) {
+        String log = String.format(logFormat, args);
+        mEventLogger.log(log);
+        if (Log.isLoggable(mTag, Log.DEBUG)) {
+            Slog.d(mTag, log);
+        }
+    }
+
+    void dump(IndentingPrintWriter pw) {
+        mEventLogger.dump(pw);
+    }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index 852aa66..0a23e38 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -55,12 +55,14 @@
 
 class RadioModule {
     private static final String TAG = "BcRadio2Srv.module";
+    private static final int RADIO_EVENT_LOGGER_QUEUE_SIZE = 25;
 
     @NonNull private final IBroadcastRadio mService;
     @NonNull public final RadioManager.ModuleProperties mProperties;
 
     private final Object mLock;
     @NonNull private final Handler mHandler;
+    @NonNull private final RadioEventLogger mEventLogger;
 
     @GuardedBy("mLock")
     private ITunerSession mHalTunerSession;
@@ -138,6 +140,7 @@
         mService = Objects.requireNonNull(service);
         mLock = Objects.requireNonNull(lock);
         mHandler = new Handler(Looper.getMainLooper());
+        mEventLogger = new RadioEventLogger(TAG, RADIO_EVENT_LOGGER_QUEUE_SIZE);
     }
 
     public static @Nullable RadioModule tryLoadingModule(int idx, @NonNull String fqName,
@@ -176,13 +179,14 @@
 
     public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb)
             throws RemoteException {
-        Slog.i(TAG, "Open TunerSession");
+        mEventLogger.logRadioEvent("Open TunerSession");
         synchronized (mLock) {
             if (mHalTunerSession == null) {
                 Mutable<ITunerSession> hwSession = new Mutable<>();
                 mService.openSession(mHalTunerCallback, (result, session) -> {
                     Convert.throwOnError("openSession", result);
                     hwSession.value = session;
+                    mEventLogger.logRadioEvent("New HIDL 2.0 tuner session is opened");
                 });
                 mHalTunerSession = Objects.requireNonNull(hwSession.value);
             }
@@ -207,7 +211,7 @@
         // Copy the contents of mAidlTunerSessions into a local array because TunerSession.close()
         // must be called without mAidlTunerSessions locked because it can call
         // onTunerSessionClosed().
-        Slog.i(TAG, "Close TunerSessions");
+        mEventLogger.logRadioEvent("Close TunerSessions");
         TunerSession[] tunerSessions;
         synchronized (mLock) {
             tunerSessions = new TunerSession[mAidlTunerSessions.size()];
@@ -320,7 +324,7 @@
         }
         onTunerSessionProgramListFilterChanged(null);
         if (mAidlTunerSessions.isEmpty() && mHalTunerSession != null) {
-            Slog.i(TAG, "Closing HAL tuner session");
+            mEventLogger.logRadioEvent("Closing HAL tuner session");
             try {
                 mHalTunerSession.close();
             } catch (RemoteException ex) {
@@ -372,7 +376,7 @@
 
     public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
             @NonNull android.hardware.radio.IAnnouncementListener listener) throws RemoteException {
-        Slog.i(TAG, "Add AnnouncementListener");
+        mEventLogger.logRadioEvent("Add AnnouncementListener");
         ArrayList<Byte> enabledList = new ArrayList<>();
         for (int type : enabledTypes) {
             enabledList.add((byte)type);
@@ -409,7 +413,7 @@
     }
 
     Bitmap getImage(int id) {
-        Slog.i(TAG, "Get image for id " + id);
+        mEventLogger.logRadioEvent("Get image for id %d", id);
         if (id == 0) throw new IllegalArgumentException("Image ID is missing");
 
         byte[] rawImage;
@@ -449,6 +453,10 @@
             }
             pw.decreaseIndent();
         }
+        pw.printf("Radio module events:\n");
+        pw.increaseIndent();
+        mEventLogger.dump(pw);
+        pw.decreaseIndent();
         pw.decreaseIndent();
     }
 }
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
index 41f753c..918dc98 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -28,7 +28,6 @@
 import android.hardware.radio.RadioManager;
 import android.os.RemoteException;
 import android.util.IndentingPrintWriter;
-import android.util.Log;
 import android.util.MutableBoolean;
 import android.util.MutableInt;
 import android.util.Slog;
@@ -41,8 +40,10 @@
 class TunerSession extends ITuner.Stub {
     private static final String TAG = "BcRadio2Srv.session";
     private static final String kAudioDeviceName = "Radio tuner source";
+    private static final int TUNER_EVENT_LOGGER_QUEUE_SIZE = 25;
 
     private final Object mLock;
+    @NonNull private final RadioEventLogger mEventLogger;
 
     private final RadioModule mModule;
     private final ITunerSession mHwSession;
@@ -61,15 +62,12 @@
         mHwSession = Objects.requireNonNull(hwSession);
         mCallback = Objects.requireNonNull(callback);
         mLock = Objects.requireNonNull(lock);
-    }
-
-    private boolean isDebugEnabled() {
-        return Log.isLoggable(TAG, Log.DEBUG);
+        mEventLogger = new RadioEventLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE);
     }
 
     @Override
     public void close() {
-        if (isDebugEnabled()) Slog.d(TAG, "Close");
+        mEventLogger.logRadioEvent("Close");
         close(null);
     }
 
@@ -81,7 +79,7 @@
      * @param error Optional error to send to client before session is closed.
      */
     public void close(@Nullable Integer error) {
-        if (isDebugEnabled()) Slog.d(TAG, "Close on error " + error);
+        mEventLogger.logRadioEvent("Close on error %d", error);
         synchronized (mLock) {
             if (mIsClosed) return;
             if (error != null) {
@@ -145,10 +143,8 @@
 
     @Override
     public void step(boolean directionDown, boolean skipSubChannel) throws RemoteException {
-        if (isDebugEnabled()) {
-            Slog.d(TAG, "Step with directionDown " + directionDown
-                    + " skipSubChannel " + skipSubChannel);
-        }
+        mEventLogger.logRadioEvent("Step with direction %s, skipSubChannel?  %s",
+                directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
         synchronized (mLock) {
             checkNotClosedLocked();
             int halResult = mHwSession.step(!directionDown);
@@ -158,10 +154,8 @@
 
     @Override
     public void scan(boolean directionDown, boolean skipSubChannel) throws RemoteException {
-        if (isDebugEnabled()) {
-            Slog.d(TAG, "Scan with directionDown " + directionDown
-                    + " skipSubChannel " + skipSubChannel);
-        }
+        mEventLogger.logRadioEvent("Scan with direction %s, skipSubChannel? %s",
+                directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
         synchronized (mLock) {
             checkNotClosedLocked();
             int halResult = mHwSession.scan(!directionDown, skipSubChannel);
@@ -171,7 +165,7 @@
 
     @Override
     public void tune(ProgramSelector selector) throws RemoteException {
-        if (isDebugEnabled()) Slog.d(TAG, "Tune with selector " + selector);
+        mEventLogger.logRadioEvent("Tune with selector %s", selector);
         synchronized (mLock) {
             checkNotClosedLocked();
             int halResult = mHwSession.tune(Convert.programSelectorToHal(selector));
@@ -195,7 +189,7 @@
 
     @Override
     public Bitmap getImage(int id) {
-        if (isDebugEnabled()) Slog.d(TAG, "Get image for " + id);
+        mEventLogger.logRadioEvent("Get image for %d", id);
         return mModule.getImage(id);
     }
 
@@ -208,7 +202,7 @@
 
     @Override
     public void startProgramListUpdates(ProgramList.Filter filter) throws RemoteException {
-        if (isDebugEnabled()) Slog.d(TAG, "start programList updates " + filter);
+        mEventLogger.logRadioEvent("start programList updates %s", filter);
         // If the AIDL client provides a null filter, it wants all updates, so use the most broad
         // filter.
         if (filter == null) {
@@ -267,7 +261,7 @@
 
     @Override
     public void stopProgramListUpdates() throws RemoteException {
-        if (isDebugEnabled()) Slog.d(TAG, "Stop programList updates");
+        mEventLogger.logRadioEvent("Stop programList updates");
         synchronized (mLock) {
             checkNotClosedLocked();
             mProgramInfoCache = null;
@@ -291,7 +285,7 @@
 
     @Override
     public boolean isConfigFlagSet(int flag) {
-        if (isDebugEnabled()) Slog.d(TAG, "Is ConfigFlagSet for " + ConfigFlag.toString(flag));
+        mEventLogger.logRadioEvent("Is ConfigFlagSet for %s", ConfigFlag.toString(flag));
         synchronized (mLock) {
             checkNotClosedLocked();
 
@@ -313,9 +307,7 @@
 
     @Override
     public void setConfigFlag(int flag, boolean value) throws RemoteException {
-        if (isDebugEnabled()) {
-            Slog.d(TAG, "Set ConfigFlag " + ConfigFlag.toString(flag) + " = " + value);
-        }
+        mEventLogger.logRadioEvent("Set ConfigFlag  %s = %b", ConfigFlag.toString(flag), value);
         synchronized (mLock) {
             checkNotClosedLocked();
             int halResult = mHwSession.setConfigFlag(flag, value);
@@ -351,6 +343,10 @@
             pw.printf("ProgramInfoCache: %s\n", mProgramInfoCache);
             pw.printf("Config: %s\n", mDummyConfig);
         }
+        pw.printf("Tuner session events:\n");
+        pw.increaseIndent();
+        mEventLogger.dump(pw);
+        pw.decreaseIndent();
         pw.decreaseIndent();
     }
 }
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 66ef5e7..11eb782 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -582,13 +582,18 @@
         }
 
         @Override
-        public boolean isCameraDisabled() {
+        public boolean isCameraDisabled(int userId) {
             DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
             if (dpm == null) {
                 Slog.e(TAG, "Failed to get the device policy manager service");
                 return false;
             }
-            return dpm.getCameraDisabled(null);
+            try {
+                return dpm.getCameraDisabled(null, userId);
+            } catch (Exception e) {
+                e.printStackTrace();
+                return false;
+            }
         }
     };
 
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 931c692..b4e91b5 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1110,7 +1110,7 @@
         // Except for Settings and VpnDialogs, the caller should be matched one of oldPackage or
         // newPackage. Otherwise, non VPN owner might get the VPN always-on status of the VPN owner.
         // See b/191382886.
-        if (!hasControlVpnPermission()) {
+        if (mContext.checkCallingOrSelfPermission(CONTROL_VPN) != PERMISSION_GRANTED) {
             if (oldPackage != null) {
                 verifyCallingUidAndPackage(oldPackage);
             }
@@ -2073,10 +2073,6 @@
                 "Unauthorized Caller");
     }
 
-    private boolean hasControlVpnPermission() {
-        return mContext.checkCallingOrSelfPermission(CONTROL_VPN) == PERMISSION_GRANTED;
-    }
-
     private class Connection implements ServiceConnection {
         private IBinder mService;
 
@@ -3901,10 +3897,8 @@
             Binder.restoreCallingIdentity(token);
         }
 
-        // If package has CONTROL_VPN, grant the ACTIVATE_PLATFORM_VPN appop.
-        if (hasControlVpnPermission()) {
-            setPackageAuthorization(packageName, VpnManager.TYPE_VPN_PLATFORM);
-        }
+        // TODO: if package has CONTROL_VPN, grant the ACTIVATE_PLATFORM_VPN appop.
+        // This mirrors the prepareAndAuthorize that is used by VpnService.
 
         // Return whether the app is already pre-consented
         return isVpnProfilePreConsented(mContext, packageName);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2cbdad7..2476350 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -813,13 +813,7 @@
                 mDisplayStatsId = mUniqueDisplayId.hashCode();
                 mDisplayDeviceConfig = config;
                 loadFromDisplayDeviceConfig(token, info);
-                if (DEBUG) {
-                    Trace.beginAsyncSection("DisplayPowerController#updatePowerState", 0);
-                }
                 updatePowerState();
-                if (DEBUG) {
-                    Trace.endAsyncSection("DisplayPowerController#updatePowerState", 0);
-                }
             }
         });
     }
@@ -1147,6 +1141,16 @@
     }
 
     private void updatePowerState() {
+        if (DEBUG) {
+            Trace.beginSection("DisplayPowerController#updatePowerState");
+        }
+        updatePowerStateInternal();
+        if (DEBUG) {
+            Trace.endSection();
+        }
+    }
+
+    private void updatePowerStateInternal() {
         // Update the power state request.
         final boolean mustNotify;
         final int previousPolicy;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index ea54b30..e6c2e7c 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -544,6 +544,7 @@
                     || message.getOpcode() == Constants.MESSAGE_SET_STREAM_PATH
                     || message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) {
                 removeAction(ActiveSourceAction.class);
+                removeAction(OneTouchPlayAction.class);
                 return;
             }
         }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index fa8b5c1..3ee3503 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1051,13 +1051,13 @@
 
                             // Address allocation completed for all devices. Notify each device.
                             if (allocatingDevices.size() == ++finished[0]) {
-                                mAddressAllocated = true;
                                 if (initiatedBy != INITIATED_BY_HOTPLUG) {
                                     // In case of the hotplug we don't call
                                     // onInitializeCecComplete()
                                     // since we reallocate the logical address only.
                                     onInitializeCecComplete(initiatedBy);
                                 }
+                                mAddressAllocated = true;
                                 notifyAddressAllocated(allocatedDevices, initiatedBy);
                                 // Reinvoke the saved display status callback once the local
                                 // device is ready.
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index eb73234..3e39746 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -109,13 +109,11 @@
 
     @AnyThread
     void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privilegedOperations,
-            int configChanges, boolean stylusHandWritingSupported,
-            @InputMethodNavButtonFlags int navigationBarFlags) {
+            int configChanges, @InputMethodNavButtonFlags int navigationBarFlags) {
         final IInputMethod.InitParams params = new IInputMethod.InitParams();
         params.token = token;
         params.privilegedOperations = privilegedOperations;
         params.configChanges = configChanges;
-        params.stylusHandWritingSupported = stylusHandWritingSupported;
         params.navigationBarFlags = navigationBarFlags;
         try {
             mTarget.initializeInternal(params);
@@ -279,4 +277,13 @@
             logRemoteException(e);
         }
     }
+
+    @AnyThread
+    void removeStylusHandwritingWindow() {
+        try {
+            mTarget.removeStylusHandwritingWindow();
+        } catch (RemoteException e) {
+            logRemoteException(e);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index b63592c..23ea39a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -286,8 +286,7 @@
                     if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
                     final InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
                     mSupportsStylusHw = info.supportsStylusHandwriting();
-                    mService.initializeImeLocked(mCurMethod, mCurToken, info.getConfigChanges(),
-                            mSupportsStylusHw);
+                    mService.initializeImeLocked(mCurMethod, mCurToken, info.getConfigChanges());
                     mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
                     mService.reRequestCurrentClientSessionLocked();
                     mService.performOnCreateInlineSuggestionsRequestLocked();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 23f4373..7bd835c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -246,6 +246,7 @@
     private static final int MSG_RESET_HANDWRITING = 1090;
     private static final int MSG_START_HANDWRITING = 1100;
     private static final int MSG_FINISH_HANDWRITING = 1110;
+    private static final int MSG_REMOVE_HANDWRITING_WINDOW = 1120;
 
     private static final int MSG_SET_INTERACTIVE = 3030;
 
@@ -2719,13 +2720,13 @@
 
     @GuardedBy("ImfLock.class")
     void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token,
-            @android.content.pm.ActivityInfo.Config int configChanges, boolean supportStylusHw) {
+            @android.content.pm.ActivityInfo.Config int configChanges) {
         if (DEBUG) {
             Slog.v(TAG, "Sending attach of token: " + token + " for display: "
                     + mCurTokenDisplayId);
         }
         inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token),
-                configChanges, supportStylusHw, getInputMethodNavButtonFlagsLocked());
+                configChanges, getInputMethodNavButtonFlagsLocked());
     }
 
     @AnyThread
@@ -2734,6 +2735,11 @@
     }
 
     @AnyThread
+    void scheduleRemoveStylusHandwritingWindow() {
+        mHandler.obtainMessage(MSG_REMOVE_HANDWRITING_WINDOW).sendToTarget();
+    }
+
+    @AnyThread
     void scheduleNotifyImeUidToAudioService(int uid) {
         mHandler.removeMessages(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE);
         mHandler.obtainMessage(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE, uid, 0 /* unused */)
@@ -4363,40 +4369,50 @@
 
             private void add(int deviceId) {
                 synchronized (ImfLock.class) {
-                    if (mStylusIds == null) {
-                        mStylusIds = new IntArray();
-                    } else if (mStylusIds.indexOf(deviceId) != -1) {
-                        return;
-                    }
-                    Slog.d(TAG, "New Stylus device " + deviceId + " added.");
-                    mStylusIds.add(deviceId);
-                    // a new Stylus is detected. If IME supports handwriting and we don't have
-                    // handwriting initialized, lets do it now.
-                    if (!mHwController.getCurrentRequestId().isPresent()
-                            && mMethodMap.get(getSelectedMethodIdLocked())
-                            .supportsStylusHandwriting()) {
-                        scheduleResetStylusHandwriting();
-                    }
+                    addStylusDeviceIdLocked(deviceId);
                 }
             }
 
             private void remove(int deviceId) {
                 synchronized (ImfLock.class) {
-                    if (mStylusIds == null || mStylusIds.size() == 0) {
-                        return;
-                    }
-                    if (mStylusIds.indexOf(deviceId) != -1) {
-                        mStylusIds.remove(deviceId);
-                        Slog.d(TAG, "Stylus device " + deviceId + " removed.");
-                    }
-                    if (mStylusIds.size() == 0) {
-                        mHwController.reset();
-                    }
+                    removeStylusDeviceIdLocked(deviceId);
                 }
             }
         }, mHandler);
     }
 
+    private void addStylusDeviceIdLocked(int deviceId) {
+        if (mStylusIds == null) {
+            mStylusIds = new IntArray();
+        } else if (mStylusIds.indexOf(deviceId) != -1) {
+            return;
+        }
+        Slog.d(TAG, "New Stylus deviceId" + deviceId + " added.");
+        mStylusIds.add(deviceId);
+        // a new Stylus is detected. If IME supports handwriting, and we don't have
+        // handwriting initialized, lets do it now.
+        if (!mHwController.getCurrentRequestId().isPresent()
+                && mBindingController.supportsStylusHandwriting()) {
+            scheduleResetStylusHandwriting();
+        }
+    }
+
+    private void removeStylusDeviceIdLocked(int deviceId) {
+        if (mStylusIds == null || mStylusIds.size() == 0) {
+            return;
+        }
+        int index;
+        if ((index = mStylusIds.indexOf(deviceId)) != -1) {
+            mStylusIds.remove(index);
+            Slog.d(TAG, "Stylus deviceId: " + deviceId + " removed.");
+        }
+        if (mStylusIds.size() == 0) {
+            // no more supported stylus(es) in system.
+            mHwController.reset();
+            scheduleRemoveStylusHandwritingWindow();
+        }
+    }
+
     private static boolean isStylusDevice(InputDevice inputDevice) {
         return inputDevice.supportsSource(InputDevice.SOURCE_STYLUS)
                 || inputDevice.supportsSource(InputDevice.SOURCE_BLUETOOTH_STYLUS);
@@ -4422,17 +4438,8 @@
             }
             final long ident = Binder.clearCallingIdentity();
             try {
-                if (!mBindingController.supportsStylusHandwriting()) {
-                    Slog.w(TAG, "Stylus HW unsupported by IME. Ignoring addVirtualStylusId()");
-                    return;
-                }
-
                 if (DEBUG) Slog.v(TAG, "Adding virtual stylus id for session");
-                if (mStylusIds == null) {
-                    mStylusIds = new IntArray();
-                }
-                mStylusIds.add(VIRTUAL_STYLUS_ID_FOR_TEST);
-                scheduleResetStylusHandwriting();
+                addStylusDeviceIdLocked(VIRTUAL_STYLUS_ID_FOR_TEST);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -4441,10 +4448,7 @@
 
     @GuardedBy("ImfLock.class")
     private void removeVirtualStylusIdForTestSessionLocked() {
-        int index;
-        if ((index = mStylusIds.indexOf(VIRTUAL_STYLUS_ID_FOR_TEST)) != -1) {
-            mStylusIds.remove(index);
-        }
+        removeStylusDeviceIdLocked(VIRTUAL_STYLUS_ID_FOR_TEST);
     }
 
     private static IntArray getStylusInputDeviceIds(InputManager im) {
@@ -4922,6 +4926,14 @@
                     }
                 }
                 return true;
+            case MSG_REMOVE_HANDWRITING_WINDOW:
+                synchronized (ImfLock.class) {
+                    IInputMethodInvoker curMethod = getCurMethodLocked();
+                    if (curMethod != null) {
+                        curMethod.removeStylusHandwritingWindow();
+                    }
+                }
+                return true;
         }
         return false;
     }
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java b/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java
new file mode 100644
index 0000000..e46bb74
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.contexthub;
+
+import android.hardware.location.NanoAppMessage;
+
+/**
+ * A class to log events and useful metrics within the Context Hub service.
+ *
+ * The class holds a queue of the last NUM_EVENTS_TO_STORE events for each
+ * event category: nanoapp load, nanoapp unload, message from a nanoapp,
+ * message to a nanoapp, and context hub restarts. The dump() function
+ * will be called during debug dumps, giving access to the event information
+ * and aggregate data since the instantiation of this class.
+ *
+ * @hide
+ */
+public class ContextHubEventLogger {
+    private static final String TAG = "ContextHubEventLogger";
+
+    ContextHubEventLogger() {
+        throw new RuntimeException("Not implemented");
+    }
+
+    /**
+     * Logs a nanoapp load event
+     *
+     * @param contextHubId      the ID of the context hub
+     * @param nanoAppId         the ID of the nanoapp
+     * @param nanoAppVersion    the version of the nanoapp
+     * @param nanoAppSize       the size in bytes of the nanoapp
+     * @param success           whether the load was successful
+     */
+    public void logNanoAppLoad(int contextHubId, long nanoAppId, int nanoAppVersion,
+                               long nanoAppSize, boolean success) {
+        throw new RuntimeException("Not implemented");
+    }
+
+    /**
+     * Logs a nanoapp unload event
+     *
+     * @param contextHubId      the ID of the context hub
+     * @param nanoAppId         the ID of the nanoapp
+     * @param success           whether the unload was successful
+     */
+    public void logNanoAppUnload(int contextHubId, long nanoAppId, boolean success) {
+        throw new RuntimeException("Not implemented");
+    }
+
+    /**
+     * Logs the event where a nanoapp sends a message to a client
+     *
+     * @param contextHubId      the ID of the context hub
+     * @param message           the message that was sent
+     */
+    public void logMessageFromNanoApp(int contextHubId, NanoAppMessage message) {
+        throw new RuntimeException("Not implemented");
+    }
+
+    /**
+     * Logs the event where a client sends a message to a nanoapp
+     *
+     * @param contextHubId      the ID of the context hub
+     * @param message           the message that was sent
+     * @param success           whether the message was sent successfully
+     */
+    public void logMessageToNanoApp(int contextHubId, NanoAppMessage message, boolean success) {
+        throw new RuntimeException("Not implemented");
+    }
+
+    /**
+     * Logs a context hub restart event
+     *
+     * @param contextHubId      the ID of the context hub
+     */
+    public void logContextHubRestarts(int contextHubId) {
+        throw new RuntimeException("Not implemented");
+    }
+
+    /**
+     * Creates a string representation of the logged events
+     *
+     * @return the dumped events
+     */
+    public String dump() {
+        throw new RuntimeException("Not implemented");
+    }
+
+    @Override
+    public String toString() {
+        return dump();
+    }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b97aa5f..aa2a25b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6684,6 +6684,20 @@
             }
         }
 
+        // Ensure only allowed packages have a substitute app name
+        if (notification.extras.containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME)) {
+            int hasSubstituteAppNamePermission = mPackageManager.checkPermission(
+                    permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg, userId);
+            if (hasSubstituteAppNamePermission != PERMISSION_GRANTED) {
+                notification.extras.remove(Notification.EXTRA_SUBSTITUTE_APP_NAME);
+                if (DBG) {
+                    Slog.w(TAG, "warning: pkg " + pkg + " attempting to substitute app name"
+                            + " without holding perm "
+                            + Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME);
+                }
+            }
+        }
+
         // Remote views? Are they too big?
         checkRemoteViews(pkg, tag, id, notification);
     }
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 729c521..477b8da 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -86,7 +86,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -1328,17 +1327,16 @@
                 return null;
             }
             NotificationChannelGroup group = r.groups.get(groupId).clone();
-            ArrayList channels = new ArrayList();
+            group.setChannels(new ArrayList<>());
             int N = r.channels.size();
             for (int i = 0; i < N; i++) {
                 final NotificationChannel nc = r.channels.valueAt(i);
                 if (includeDeleted || !nc.isDeleted()) {
                     if (groupId.equals(nc.getGroup())) {
-                        channels.add(nc);
+                        group.addChannel(nc);
                     }
                 }
             }
-            group.setChannels(channels);
             return group;
         }
     }
@@ -1351,10 +1349,7 @@
             if (r == null) {
                 return null;
             }
-            if (r.groups.get(groupId) != null) {
-                 return r.groups.get(groupId).clone();
-            }
-            return null;
+            return r.groups.get(groupId);
         }
     }
 
@@ -1362,48 +1357,44 @@
     public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
             int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) {
         Objects.requireNonNull(pkg);
-        List<NotificationChannelGroup> groups = new ArrayList<>();
+        Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
         synchronized (mPackagePreferences) {
             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
             if (r == null) {
                 return ParceledListSlice.emptyList();
             }
-            Map<String, ArrayList<NotificationChannel>> groupedChannels = new HashMap();
+            NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
             int N = r.channels.size();
             for (int i = 0; i < N; i++) {
                 final NotificationChannel nc = r.channels.valueAt(i);
                 if (includeDeleted || !nc.isDeleted()) {
                     if (nc.getGroup() != null) {
                         if (r.groups.get(nc.getGroup()) != null) {
-                            ArrayList<NotificationChannel> channels = groupedChannels.getOrDefault(
-                                    nc.getGroup(), new ArrayList<>());
-                            channels.add(nc);
-                            groupedChannels.put(nc.getGroup(), channels);
+                            NotificationChannelGroup ncg = groups.get(nc.getGroup());
+                            if (ncg == null) {
+                                ncg = r.groups.get(nc.getGroup()).clone();
+                                ncg.setChannels(new ArrayList<>());
+                                groups.put(nc.getGroup(), ncg);
+
+                            }
+                            ncg.addChannel(nc);
                         }
                     } else {
-                        ArrayList<NotificationChannel> channels = groupedChannels.getOrDefault(
-                            null, new ArrayList<>());
-                        channels.add(nc);
-                        groupedChannels.put(null, channels);
+                        nonGrouped.addChannel(nc);
                     }
                 }
             }
-            for (NotificationChannelGroup group : r.groups.values()) {
-                ArrayList<NotificationChannel> channels =
-                        groupedChannels.getOrDefault(group.getId(), new ArrayList<>());
-                if (includeEmpty || !channels.isEmpty()) {
-                    NotificationChannelGroup clone = group.clone();
-                    clone.setChannels(channels);
-                    groups.add(clone);
+            if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
+                groups.put(null, nonGrouped);
+            }
+            if (includeEmpty) {
+                for (NotificationChannelGroup group : r.groups.values()) {
+                    if (!groups.containsKey(group.getId())) {
+                        groups.put(group.getId(), group);
+                    }
                 }
             }
-
-            if (includeNonGrouped && groupedChannels.containsKey(null)) {
-                NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
-                nonGrouped.setChannels(groupedChannels.get(null));
-                groups.add(nonGrouped);
-            }
-            return new ParceledListSlice<>(groups);
+            return new ParceledListSlice<>(new ArrayList<>(groups.values()));
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index bdc5711..5e0a180 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -131,6 +131,17 @@
         }
     }
 
+    // For tests: just do the setting of various local variables without actually doing work
+    @VisibleForTesting
+    protected void initForTests(Context context, NotificationUsageStats usageStats,
+            LruCache peopleCache) {
+        mUserToContextMap = new ArrayMap<>();
+        mBaseContext = context;
+        mUsageStats = usageStats;
+        mPeopleCache = peopleCache;
+        mEnabled = true;
+    }
+
     public RankingReconsideration process(NotificationRecord record) {
         if (!mEnabled) {
             if (VERBOSE) Slog.i(TAG, "disabled");
@@ -179,7 +190,7 @@
             return NONE;
         }
         final PeopleRankingReconsideration prr =
-                validatePeople(context, key, extras, null, affinityOut);
+                validatePeople(context, key, extras, null, affinityOut, null);
         float affinity = affinityOut[0];
 
         if (prr != null) {
@@ -224,15 +235,21 @@
         return context;
     }
 
-    private RankingReconsideration validatePeople(Context context,
+    @VisibleForTesting
+    protected RankingReconsideration validatePeople(Context context,
             final NotificationRecord record) {
         final String key = record.getKey();
         final Bundle extras = record.getNotification().extras;
         final float[] affinityOut = new float[1];
+        ArraySet<String> phoneNumbersOut = new ArraySet<>();
         final PeopleRankingReconsideration rr =
-                validatePeople(context, key, extras, record.getPeopleOverride(), affinityOut);
+                validatePeople(context, key, extras, record.getPeopleOverride(), affinityOut,
+                        phoneNumbersOut);
         final float affinity = affinityOut[0];
         record.setContactAffinity(affinity);
+        if (phoneNumbersOut.size() > 0) {
+            record.mergePhoneNumbers(phoneNumbersOut);
+        }
         if (rr == null) {
             mUsageStats.registerPeopleAffinity(record, affinity > NONE, affinity == STARRED_CONTACT,
                     true /* cached */);
@@ -243,7 +260,7 @@
     }
 
     private PeopleRankingReconsideration validatePeople(Context context, String key, Bundle extras,
-            List<String> peopleOverride, float[] affinityOut) {
+            List<String> peopleOverride, float[] affinityOut, ArraySet<String> phoneNumbersOut) {
         float affinity = NONE;
         if (extras == null) {
             return null;
@@ -270,6 +287,15 @@
                 }
                 if (lookupResult != null) {
                     affinity = Math.max(affinity, lookupResult.getAffinity());
+
+                    // add all phone numbers associated with this lookup result, if they exist
+                    // and if requested
+                    if (phoneNumbersOut != null) {
+                        ArraySet<String> phoneNumbers = lookupResult.getPhoneNumbers();
+                        if (phoneNumbers != null && phoneNumbers.size() > 0) {
+                            phoneNumbersOut.addAll(phoneNumbers);
+                        }
+                    }
                 }
             }
             if (++personIdx == MAX_PEOPLE) {
@@ -289,7 +315,8 @@
         return new PeopleRankingReconsideration(context, key, pendingLookups);
     }
 
-    private String getCacheKey(int userId, String handle) {
+    @VisibleForTesting
+    protected static String getCacheKey(int userId, String handle) {
         return Integer.toString(userId) + ":" + handle;
     }
 
@@ -485,7 +512,8 @@
         }
     }
 
-    private static class LookupResult {
+    @VisibleForTesting
+    protected static class LookupResult {
         private static final long CONTACT_REFRESH_MILLIS = 60 * 60 * 1000;  // 1hr
 
         private final long mExpireMillis;
@@ -574,7 +602,8 @@
             return mPhoneNumbers;
         }
 
-        private boolean isExpired() {
+        @VisibleForTesting
+        protected boolean isExpired() {
             return mExpireMillis < System.currentTimeMillis();
         }
 
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 51b36dd..7159673 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -39,7 +39,6 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
-import android.app.ActivityManagerInternal;
 import android.app.IActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -1417,18 +1416,16 @@
 
     private static void broadcastActionOverlayChanged(@NonNull final Set<String> targetPackages,
             final int userId) {
-        final PackageManagerInternal pmInternal =
-                LocalServices.getService(PackageManagerInternal.class);
-        final ActivityManagerInternal amInternal =
-                LocalServices.getService(ActivityManagerInternal.class);
         CollectionUtils.forEach(targetPackages, target -> {
             final Intent intent = new Intent(ACTION_OVERLAY_CHANGED,
                     Uri.fromParts("package", target, null));
             intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-            final int[] allowList = pmInternal.getVisibilityAllowList(target, userId);
-            amInternal.broadcastIntent(intent, null /* resultTo */, null /* requiredPermissions */,
-                    false /* serialized */, userId, allowList, null /* filterExtrasForReceiver */,
-                    null /* bOptions */);
+            try {
+                ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, null,
+                        null, null, android.app.AppOpsManager.OP_NONE, null, false, false, userId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "broadcastActionOverlayChanged remote exception", e);
+            }
         });
     }
 
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index da76738..bb918d5 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -257,6 +257,7 @@
         String name = "";
         String filename = null;
         String opt;
+        String configuration = null;
         while ((opt = getNextOption()) != null) {
             switch (opt) {
                 case "--user":
@@ -274,6 +275,9 @@
                 case "--file":
                     filename = getNextArgRequired();
                     break;
+                case "--config":
+                    configuration = getNextArgRequired();
+                    break;
                 default:
                     err.println("Error: Unknown option: " + opt);
                     return 1;
@@ -307,7 +311,7 @@
             final String resourceName = getNextArgRequired();
             final String typeStr = getNextArgRequired();
             final String strData = String.join(" ", peekRemainingArgs());
-            addOverlayValue(overlayBuilder, resourceName, typeStr, strData);
+            addOverlayValue(overlayBuilder, resourceName, typeStr, strData, configuration);
         }
 
         mInterface.commit(new OverlayManagerTransaction.Builder()
@@ -363,8 +367,9 @@
                             err.println("Error: value missing at line " + parser.getLineNumber());
                             return 1;
                         }
+                        String config = parser.getAttributeValue(null, "config");
                         addOverlayValue(overlayBuilder, targetPackage + ':' + target,
-                                overlayType, value);
+                                overlayType, value, config);
                     }
                 }
             }
@@ -379,7 +384,7 @@
     }
 
     private void addOverlayValue(FabricatedOverlay.Builder overlayBuilder,
-            String resourceName, String typeString, String valueString) {
+            String resourceName, String typeString, String valueString, String configuration) {
         final int type;
         typeString = typeString.toLowerCase(Locale.getDefault());
         if (TYPE_MAP.containsKey(typeString)) {
@@ -392,7 +397,7 @@
             }
         }
         if (type == TypedValue.TYPE_STRING) {
-            overlayBuilder.setResourceValue(resourceName, type, valueString);
+            overlayBuilder.setResourceValue(resourceName, type, valueString, configuration);
         } else {
             final int intData;
             if (valueString.startsWith("0x")) {
@@ -400,7 +405,7 @@
             } else {
                 intData = Integer.parseUnsignedInt(valueString);
             }
-            overlayBuilder.setResourceValue(resourceName, type, intData);
+            overlayBuilder.setResourceValue(resourceName, type, intData, configuration);
         }
     }
 
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index 45192b7..d3d1cc5 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -109,6 +109,13 @@
 
     private void handleTombstone(File path) {
         final String filename = path.getName();
+
+        // Clean up temporary files if they made it this far (e.g. if system server crashes).
+        if (filename.endsWith(".tmp")) {
+            path.delete();
+            return;
+        }
+
         if (!filename.startsWith("tombstone_")) {
             return;
         }
@@ -561,6 +568,10 @@
         @Override
         public void onEvent(int event, @Nullable String path) {
             mHandler.post(() -> {
+                // Ignore .tmp files.
+                if (path.endsWith(".tmp")) {
+                    return;
+                }
                 handleTombstone(new File(TOMBSTONE_DIR, path));
             });
         }
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index a878bfd..ab99860 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -113,6 +113,8 @@
             @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags,
             int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits);
     @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType,
+            long flags, int filterCallingUid, int userId);
+    @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType,
             long flags, int userId);
     @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent, String resolvedType,
             long flags, int userId, int callingUid, boolean includeInstantApps);
@@ -500,6 +502,13 @@
     boolean isComponentEffectivelyEnabled(@NonNull ComponentInfo componentInfo,
             @UserIdInt int userId);
 
+    /**
+     * @return true if the runtime app user enabled state and the install-time app manifest enabled
+     * state are both effectively enabled for the given app. Or if the app cannot be found,
+     * returns false.
+     */
+    boolean isApplicationEffectivelyEnabled(@NonNull String packageName, @UserIdInt int userId);
+
     @Nullable
     KeySet getKeySetByAlias(@NonNull String packageName, @NonNull String alias);
 
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 8ec3d2b..7c17778 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -608,6 +608,15 @@
                 resolveForStart, userId, intent);
     }
 
+    @NonNull
+    @Override
+    public final List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType,
+            @PackageManager.ResolveInfoFlagsBits long flags, int filterCallingUid, int userId) {
+        return queryIntentActivitiesInternal(
+                intent, resolvedType, flags, 0 /*privateResolveFlags*/, filterCallingUid,
+                userId, false /*resolveForStart*/, true /*allowDynamicSplits*/);
+    }
+
     public final @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
             String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
         return queryIntentActivitiesInternal(
@@ -5412,6 +5421,26 @@
         }
     }
 
+    @Override
+    public boolean isApplicationEffectivelyEnabled(@NonNull String packageName,
+            @UserIdInt int userId) {
+        try {
+            int appEnabledSetting = mSettings.getApplicationEnabledSetting(packageName, userId);
+            if (appEnabledSetting == COMPONENT_ENABLED_STATE_DEFAULT) {
+                final AndroidPackage pkg = getPackage(packageName);
+                if (pkg == null) {
+                    // Should not happen because getApplicationEnabledSetting would have thrown
+                    return false;
+                }
+                return pkg.isEnabled();
+            } else {
+                return appEnabledSetting == COMPONENT_ENABLED_STATE_ENABLED;
+            }
+        } catch (PackageManager.NameNotFoundException ignored) {
+            return false;
+        }
+    }
+
     @Nullable
     @Override
     public KeySet getKeySetByAlias(@NonNull String packageName, @NonNull String alias) {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 9db9837..61e2419 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -899,7 +899,7 @@
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages");
                     reconciledPackages = ReconcilePackageUtils.reconcilePackages(
                             reconcileRequest, mSharedLibraries,
-                            mPm.mSettings.getKeySetManagerService(), mPm.mSettings);
+                            mPm.mSettings.getKeySetManagerService(), mPm.mSettings, mContext);
                 } catch (ReconcileFailure e) {
                     for (InstallRequest request : requests) {
                         request.mInstallResult.setError("Reconciliation failed...", e);
@@ -3669,7 +3669,7 @@
                     final Map<String, ReconciledPackage> reconcileResult =
                             ReconcilePackageUtils.reconcilePackages(reconcileRequest,
                                     mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
-                                    mPm.mSettings);
+                                    mPm.mSettings, mContext);
                     if ((scanFlags & SCAN_AS_APEX) == 0) {
                         appIdCreated = optimisticallyRegisterAppId(scanResult);
                     } else {
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index c8db297..e969d93 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -309,7 +309,8 @@
     public final List<ResolveInfo> queryIntentActivities(
             Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
             int filterCallingUid, int userId) {
-        return snapshot().queryIntentActivitiesInternal(intent, resolvedType, flags, userId);
+        return snapshot().queryIntentActivitiesInternal(intent, resolvedType, flags,
+                filterCallingUid, userId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index d6a133e..3c47801 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
 import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY;
@@ -23,25 +24,31 @@
 import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
 import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
 
+import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SigningDetails;
 import android.os.SystemProperties;
+import android.permission.PermissionManager;
 import android.util.ArrayMap;
 import android.util.Log;
 
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.pkg.component.ParsedUsesPermission;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.utils.WatchedLongSparseArray;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
 final class ReconcilePackageUtils {
     public static Map<String, ReconciledPackage> reconcilePackages(
             final ReconcileRequest request, SharedLibrariesImpl sharedLibraries,
-            KeySetManagerService ksms, Settings settings)
+            KeySetManagerService ksms, Settings settings, Context context)
             throws ReconcileFailure {
         final Map<String, ScanResult> scannedPackages = request.mScannedPackages;
 
@@ -161,11 +168,43 @@
                     // over the latest parsed certs.
                     signingDetails = parsedPackage.getSigningDetails();
 
-                    // if this is is a sharedUser, check to see if the new package is signed by a
-                    // newer
-                    // signing certificate than the existing one, and if so, copy over the new
+                    // if this is a sharedUser, check to see if the new package is signed by a
+                    // newer signing certificate than the existing one, and if so, copy over the new
                     // details
                     if (sharedUserSetting != null) {
+                        if (prepareResult != null && !prepareResult.mPackageToScan.isTestOnly()
+                                && sharedUserSetting.isPrivileged()
+                                && !signatureCheckPs.isSystem()) {
+                            final List<ParsedUsesPermission> usesPermissions =
+                                    parsedPackage.getUsesPermissions();
+                            final List<String> usesPrivilegedPermissions = new ArrayList<>();
+                            final PermissionManager permissionManager = context.getSystemService(
+                                    PermissionManager.class);
+                            // Check if the app requests any privileged permissions because that
+                            // violates the privapp-permissions allowlist check during boot.
+                            if (permissionManager != null) {
+                                for (int i = 0; i < usesPermissions.size(); i++) {
+                                    final String permissionName = usesPermissions.get(i).getName();
+                                    final PermissionInfo permissionInfo =
+                                            permissionManager.getPermissionInfo(permissionName, 0);
+                                    if (permissionInfo != null
+                                            && (permissionInfo.getProtectionFlags()
+                                            & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0) {
+                                        usesPrivilegedPermissions.add(permissionName);
+                                    }
+                                }
+                            }
+
+                            if (!usesPrivilegedPermissions.isEmpty()) {
+                                throw new ReconcileFailure(INSTALL_FAILED_INVALID_APK,
+                                        "Non-system package: " + parsedPackage.getPackageName()
+                                                + " shares signature and sharedUserId with"
+                                                + " a privileged package but requests"
+                                                + " privileged permissions that are not"
+                                                + " allowed: " + Arrays.toString(
+                                                        usesPrivilegedPermissions.toArray()));
+                            }
+                        }
                         // Attempt to merge the existing lineage for the shared SigningDetails with
                         // the lineage of the new package; if the shared SigningDetails are not
                         // returned this indicates the new package added new signers to the lineage
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 5731af6..297439f 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -315,4 +315,10 @@
 
     /** TODO(b/239982558): add javadoc / mention invalid_id is used to unassing */
     public abstract void assignUserToDisplay(@UserIdInt int userId, int displayId);
+
+    /**
+     * Returns {@code true} if the user is visible (as defined by
+     * {@link UserManager#isUserVisible()} in the given display.
+     */
+    public abstract boolean isUserVisible(@UserIdInt int userId, int displayId);
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 025e973..672c13c 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -18,6 +18,7 @@
 
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.os.UserManager.DISALLOW_USER_SWITCH;
 
 import android.Manifest;
 import android.accounts.Account;
@@ -910,7 +911,7 @@
     private @NonNull List<UserInfo> getUsersInternal(boolean excludePartial, boolean excludeDying,
             boolean excludePreCreated) {
         synchronized (mUsersLock) {
-            ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
+            ArrayList<UserInfo> users = new ArrayList<>(mUsers.size());
             final int userSize = mUsers.size();
             for (int i = 0; i < userSize; i++) {
                 UserInfo ui = mUsers.valueAt(i).info;
@@ -1766,6 +1767,32 @@
     }
 
     @Override
+    public List<UserHandle> getVisibleUsers() {
+        if (!hasManageUsersOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)) {
+            throw new SecurityException("Caller needs MANAGE_USERS or INTERACT_ACROSS_USERS "
+                    + "permission to get list of visible users");
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            // TODO(b/2399825580): refactor into UserDisplayAssigner
+            synchronized (mUsersLock) {
+                int usersSize = mUsers.size();
+                ArrayList<UserHandle> visibleUsers = new ArrayList<>(usersSize);
+                for (int i = 0; i < usersSize; i++) {
+                    UserInfo ui = mUsers.valueAt(i).info;
+                    if (!ui.partial && !ui.preCreated && !mRemovingUserIds.get(ui.id)
+                            && isUserVisibleUnchecked(ui.id)) {
+                        visibleUsers.add(UserHandle.of(ui.id));
+                    }
+                }
+                return visibleUsers;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
     public @NonNull String getUserName() {
         final int callingUid = Binder.getCallingUid();
         if (!hasQueryOrCreateUsersPermission()
@@ -1885,6 +1912,19 @@
     }
 
     @Override
+    public boolean isUserSwitcherEnabled(@UserIdInt int mUserId) {
+        boolean multiUserSettingOn = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.USER_SWITCHER_ENABLED,
+                Resources.getSystem().getBoolean(com.android.internal
+                        .R.bool.config_showUserSwitcherByDefault) ? 1 : 0) != 0;
+
+        return UserManager.supportsMultipleUsers()
+                && !hasUserRestriction(DISALLOW_USER_SWITCH, mUserId)
+                && !UserManager.isDeviceInDemoMode(mContext)
+                && multiUserSettingOn;
+    }
+
+    @Override
     public boolean isRestricted(@UserIdInt int userId) {
         if (userId != UserHandle.getCallingUserId()) {
             checkCreateUsersPermission("query isRestricted for user " + userId);
@@ -2277,7 +2317,6 @@
                     originatingUserId, local);
             localChanged = updateLocalRestrictionsForTargetUsersLR(originatingUserId, local,
                     updatedLocalTargetUserIds);
-
             if (isDeviceOwner) {
                 // Remember the global restriction owner userId to be able to make a distinction
                 // in getUserRestrictionSource on who set local policies.
@@ -4804,41 +4843,59 @@
                 null, // use default PullAtomMetadata values
                 BackgroundThread.getExecutor(),
                 this::onPullAtom);
+        statsManager.setPullAtomCallback(
+                FrameworkStatsLog.MULTI_USER_INFO,
+                null, // use default PullAtomMetadata values
+                BackgroundThread.getExecutor(),
+                this::onPullAtom);
     }
 
     /** Writes a UserInfo pulled atom for each user on the device. */
     private int onPullAtom(int atomTag, List<StatsEvent> data) {
-        if (atomTag != FrameworkStatsLog.USER_INFO) {
-            Slogf.e(LOG_TAG, "Unexpected atom tag: %d", atomTag);
-            return android.app.StatsManager.PULL_SKIP;
-        }
-        final List<UserInfo> users = getUsersInternal(true, true, true);
-        final int size = users.size();
-        if (size > 1) {
-            for (int idx = 0; idx < size; idx++) {
-                final UserInfo user = users.get(idx);
-                final int userTypeStandard = UserManager.getUserTypeForStatsd(user.userType);
-                final String userTypeCustom = (userTypeStandard == FrameworkStatsLog
-                        .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN)
-                        ?
-                        user.userType : null;
+        if (atomTag == FrameworkStatsLog.USER_INFO) {
+            final List<UserInfo> users = getUsersInternal(true, true, true);
+            final int size = users.size();
+            if (size > 1) {
+                for (int idx = 0; idx < size; idx++) {
+                    final UserInfo user = users.get(idx);
+                    final int userTypeStandard = UserManager.getUserTypeForStatsd(user.userType);
+                    final String userTypeCustom = (userTypeStandard == FrameworkStatsLog
+                            .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN)
+                            ?
+                            user.userType : null;
 
-                boolean isUserRunningUnlocked;
-                synchronized (mUserStates) {
-                    isUserRunningUnlocked =
-                            mUserStates.get(user.id, -1) == UserState.STATE_RUNNING_UNLOCKED;
+                    boolean isUserRunningUnlocked;
+                    synchronized (mUserStates) {
+                        isUserRunningUnlocked =
+                                mUserStates.get(user.id, -1) == UserState.STATE_RUNNING_UNLOCKED;
+                    }
+
+                    data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.USER_INFO,
+                            user.id,
+                            userTypeStandard,
+                            userTypeCustom,
+                            user.flags,
+                            user.creationTime,
+                            user.lastLoggedInTime,
+                            isUserRunningUnlocked
+                    ));
+                }
+            }
+        } else if (atomTag == FrameworkStatsLog.MULTI_USER_INFO) {
+            if (UserManager.getMaxSupportedUsers() > 1) {
+                int deviceOwnerUserId = UserHandle.USER_NULL;
+
+                synchronized (mRestrictionsLock) {
+                    deviceOwnerUserId = mDeviceOwnerUserId;
                 }
 
-                data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.USER_INFO,
-                        user.id,
-                        userTypeStandard,
-                        userTypeCustom,
-                        user.flags,
-                        user.creationTime,
-                        user.lastLoggedInTime,
-                        isUserRunningUnlocked
-                ));
+                data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.MULTI_USER_INFO,
+                        UserManager.getMaxSupportedUsers(),
+                        isUserSwitcherEnabled(deviceOwnerUserId)));
             }
+        } else {
+            Slogf.e(LOG_TAG, "Unexpected atom tag: %d", atomTag);
+            return android.app.StatsManager.PULL_SKIP;
         }
         return android.app.StatsManager.PULL_SUCCESS;
     }
@@ -6974,7 +7031,12 @@
                 mUsersOnSecondaryDisplays.put(userId, displayId);
             }
         }
-    }
+
+        @Override
+        public boolean isUserVisible(int userId, int displayId) {
+            return isUserVisibleOnDisplay(userId, displayId);
+        }
+    } // class LocalService
 
     /**
      * Check if user has restrictions
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index 0a39e64..47a3705 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -25,6 +25,7 @@
 import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
 import static android.os.PowerWhitelistManager.REASON_PACKAGE_VERIFIER;
 import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 
 import static com.android.server.pm.PackageManagerService.CHECK_PENDING_INTEGRITY_VERIFICATION;
@@ -77,6 +78,7 @@
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 final class VerifyingSession {
@@ -353,7 +355,7 @@
         }
         final int verifierUserId = verifierUser.getIdentifier();
 
-        String[] requiredVerifierPackages = mPm.mRequiredVerifierPackages;
+        List<String> requiredVerifierPackages = Arrays.asList(mPm.mRequiredVerifierPackages);
         boolean requiredVerifierPackagesOverridden = false;
 
         // Allow verifier override for ADB installations which could already be unverified using
@@ -377,8 +379,7 @@
                     // are not adding a new way to disable verifications.
                     if (!isAdbVerificationEnabled(pkgLite, verifierUserId,
                             requestedDisableVerification)) {
-                        requiredVerifierPackages = adbVerifierOverridePackages.toArray(
-                                new String[adbVerifierOverridePackages.size()]);
+                        requiredVerifierPackages = adbVerifierOverridePackages;
                         requiredVerifierPackagesOverridden = true;
                     }
                 }
@@ -397,6 +398,16 @@
          */
         final Computer snapshot = mPm.snapshotComputer();
 
+        final int numRequiredVerifierPackages = requiredVerifierPackages.size();
+        for (int i = numRequiredVerifierPackages - 1; i >= 0; i--) {
+            if (!snapshot.isApplicationEffectivelyEnabled(requiredVerifierPackages.get(i),
+                    SYSTEM_UID)) {
+                Slog.w(TAG,
+                        "Required verifier: " + requiredVerifierPackages.get(i) + " is disabled");
+                requiredVerifierPackages.remove(i);
+            }
+        }
+
         for (String requiredVerifierPackage : requiredVerifierPackages) {
             final int requiredUid = snapshot.getPackageUid(requiredVerifierPackage,
                     MATCH_DEBUG_TRIAGED_MISSING, verifierUserId);
@@ -514,7 +525,7 @@
             }
         }
 
-        if (requiredVerifierPackages.length == 0) {
+        if (requiredVerifierPackages.size() == 0) {
             Slog.e(TAG, "No required verifiers");
             return;
         }
@@ -532,7 +543,7 @@
 
             final Intent requiredIntent;
             final String receiverPermission;
-            if (!requiredVerifierPackagesOverridden || requiredVerifierPackages.length == 1) {
+            if (!requiredVerifierPackagesOverridden || requiredVerifierPackages.size() == 1) {
                 // Prod code OR test code+single verifier.
                 requiredIntent = new Intent(verification);
                 if (!requiredVerifierPackagesOverridden) {
@@ -657,7 +668,7 @@
      * @return true if verification should be performed
      */
     private boolean isVerificationEnabled(PackageInfoLite pkgInfoLite, int userId,
-            String[] requiredVerifierPackages) {
+            List<String> requiredVerifierPackages) {
         if (!DEFAULT_VERIFY_ENABLE) {
             return false;
         }
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 9c95769..7602d33 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -354,7 +354,7 @@
      * Get op that controls the access related to the permission.
      *
      * <p>Usually the permission-op relationship is 1:1 but some permissions (e.g. fine location)
-     * {@link AppOpsManager#sOpToSwitch share an op} to control the access.
+     * {@link AppOpsManager#opToSwitch(int)}  share an op} to control the access.
      *
      * @param permission The permission
      * @return The op that controls the access of the permission
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 51bf557..937a789 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2082,21 +2082,22 @@
 
         mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() {
             @Override
-            public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
+            public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
+                    boolean keyguardOccluding, long duration, long statusBarAnimationStartTime,
                     long statusBarAnimationDuration) {
-                return handleTransitionForKeyguardLw(false /* startKeyguardExitAnimation */,
-                        false /* notifyOccluded */);
+                // When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI
+                // receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't
+                // need to call IKeyguardService#keyguardGoingAway here.
+                return handleStartTransitionForKeyguardLw(keyguardGoingAway
+                        && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation,
+                        keyguardOccluding, duration);
             }
 
             @Override
-            public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
-                    boolean keyguardOccludedCancelled) {
-                // When app KEYGUARD_GOING_AWAY or (UN)OCCLUDE app transition is canceled, we need
-                // to trigger relevant IKeyguardService calls to sync keyguard status in
-                // WindowManagerService and SysUI.
-                handleTransitionForKeyguardLw(
-                        keyguardGoingAwayCancelled /* startKeyguardExitAnimation */,
-                        keyguardOccludedCancelled /* notifyOccluded */);
+            public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
+                handleStartTransitionForKeyguardLw(
+                        keyguardGoingAway, false /* keyguardOccludingStarted */,
+                        0 /* duration */);
             }
         });
 
@@ -3257,44 +3258,35 @@
 
     @Override
     public void onKeyguardOccludedChangedLw(boolean occluded) {
-        if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()
-                && !WindowManagerService.sEnableShellTransitions) {
+        if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()) {
             mPendingKeyguardOccluded = occluded;
             mKeyguardOccludedChanged = true;
         } else {
-            setKeyguardOccludedLw(occluded, true /* notify */);
+            setKeyguardOccludedLw(occluded, false /* force */,
+                    false /* transitionStarted */);
         }
     }
 
     @Override
-    public int applyKeyguardOcclusionChange(boolean notify) {
+    public int applyKeyguardOcclusionChange(boolean transitionStarted) {
         if (mKeyguardOccludedChanged) {
             if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded="
                     + mPendingKeyguardOccluded);
-            if (setKeyguardOccludedLw(mPendingKeyguardOccluded, notify)) {
+            if (setKeyguardOccludedLw(mPendingKeyguardOccluded, false /* force */,
+                    transitionStarted)) {
                 return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_WALLPAPER;
             }
         }
         return 0;
     }
 
-    /**
-     * Called when keyguard related app transition starts, or cancelled.
-     *
-     * @param startKeyguardExitAnimation Trigger IKeyguardService#startKeyguardExitAnimation to
-     *                                  start keyguard exit animation.
-     * @param notifyOccluded Trigger IKeyguardService#setOccluded binder call to notify whether
-     *                      the top activity can occlude the keyguard or not.
-     *
-     * @return Whether the flags have changed and we have to redo the layout.
-     */
-    private int handleTransitionForKeyguardLw(boolean startKeyguardExitAnimation,
-            boolean notifyOccluded) {
-        final int redoLayout = applyKeyguardOcclusionChange(notifyOccluded);
+    private int handleStartTransitionForKeyguardLw(boolean keyguardGoingAway,
+            boolean keyguardOccluding, long duration) {
+        final int redoLayout = applyKeyguardOcclusionChange(keyguardOccluding);
         if (redoLayout != 0) return redoLayout;
-        if (startKeyguardExitAnimation) {
+        if (keyguardGoingAway) {
             if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation");
-            startKeyguardExitAnimation(SystemClock.uptimeMillis());
+            startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration);
         }
         return 0;
     }
@@ -3526,18 +3518,28 @@
      * Updates the occluded state of the Keyguard.
      *
      * @param isOccluded Whether the Keyguard is occluded by another window.
-     * @param notify Notify keyguard occlude status change immediately via
-     *       {@link com.android.internal.policy.IKeyguardService}.
+     * @param force notify the occluded status to KeyguardService and update flags even though
+     *             occlude status doesn't change.
+     * @param transitionStarted {@code true} if keyguard (un)occluded transition started.
      * @return Whether the flags have changed and we have to redo the layout.
      */
-    private boolean setKeyguardOccludedLw(boolean isOccluded, boolean notify) {
+    private boolean setKeyguardOccludedLw(boolean isOccluded, boolean force,
+            boolean transitionStarted) {
         if (DEBUG_KEYGUARD) Slog.d(TAG, "setKeyguardOccluded occluded=" + isOccluded);
         mKeyguardOccludedChanged = false;
-        if (isKeyguardOccluded() == isOccluded) {
+        if (isKeyguardOccluded() == isOccluded && !force) {
             return false;
         }
-        mKeyguardDelegate.setOccluded(isOccluded, notify);
-        return mKeyguardDelegate.isShowing();
+
+        final boolean showing = mKeyguardDelegate.isShowing();
+        final boolean animate = showing && !isOccluded;
+        // When remote animation is enabled for keyguard (un)occlude transition, KeyguardService
+        // uses remote animation start as a signal to update its occlusion status ,so we don't need
+        // to notify here.
+        final boolean notify = !WindowManagerService.sEnableRemoteKeyguardOccludeAnimation
+                || !transitionStarted;
+        mKeyguardDelegate.setOccluded(isOccluded, animate, notify);
+        return showing;
     }
 
     /** {@inheritDoc} */
@@ -4933,10 +4935,10 @@
     }
 
     @Override
-    public void startKeyguardExitAnimation(long startTime) {
+    public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
         if (mKeyguardDelegate != null) {
             if (DEBUG_KEYGUARD) Slog.d(TAG, "PWM.startKeyguardExitAnimation");
-            mKeyguardDelegate.startKeyguardExitAnimation(startTime);
+            mKeyguardDelegate.startKeyguardExitAnimation(startTime, fadeoutDuration);
         }
     }
 
diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
index 9ad15c8..f00edf3 100644
--- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
+++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
@@ -49,7 +49,7 @@
 
     // Key code of current key down event, reset when key up.
     private int mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN;
-    private volatile boolean mHandledByLongPress = false;
+    private boolean mHandledByLongPress = false;
     private final Handler mHandler;
     private long mLastDownTime = 0;
 
@@ -194,8 +194,8 @@
                 mHandledByLongPress = true;
                 mHandler.removeMessages(MSG_KEY_LONG_PRESS);
                 mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
-                final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, mActiveRule.mKeyCode,
-                        0, mActiveRule);
+                final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0,
+                        mActiveRule);
                 msg.setAsynchronous(true);
                 mHandler.sendMessage(msg);
             }
@@ -274,13 +274,26 @@
     }
 
     private boolean interceptKeyUp(KeyEvent event) {
-        mHandler.removeMessages(MSG_KEY_LONG_PRESS);
-        mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
         mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN;
         if (mActiveRule == null) {
             return false;
         }
 
+        if (!mHandledByLongPress) {
+            final long eventTime = event.getEventTime();
+            if (eventTime < mLastDownTime + mActiveRule.getLongPressTimeoutMs()) {
+                mHandler.removeMessages(MSG_KEY_LONG_PRESS);
+            } else {
+                mHandledByLongPress = mActiveRule.supportLongPress();
+            }
+
+            if (eventTime < mLastDownTime + mActiveRule.getVeryLongPressTimeoutMs()) {
+                mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
+            } else {
+                mHandledByLongPress = mActiveRule.supportVeryLongPress();
+            }
+        }
+
         if (mHandledByLongPress) {
             mHandledByLongPress = false;
             mKeyPressCounter = 0;
@@ -376,7 +389,6 @@
                     if (DEBUG) {
                         Log.i(TAG, "Detect long press " + KeyEvent.keyCodeToString(keyCode));
                     }
-                    mHandledByLongPress = true;
                     rule.onLongPress(mLastDownTime);
                     break;
                 case MSG_KEY_VERY_LONG_PRESS:
@@ -384,7 +396,6 @@
                         Log.i(TAG, "Detect very long press "
                                 + KeyEvent.keyCodeToString(keyCode));
                     }
-                    mHandledByLongPress = true;
                     rule.onVeryLongPress(mLastDownTime);
                     break;
                 case MSG_KEY_DELAYED_PRESS:
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 2b04050..4f00992 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -171,10 +171,10 @@
     void onKeyguardOccludedChangedLw(boolean occluded);
 
     /**
-     * @param notify {@code true} if the status change should be immediately notified via
-     *        {@link com.android.internal.policy.IKeyguardService}
+     * Applies a keyguard occlusion change if one happened.
+     * @param transitionStarted Whether keyguard (un)occlude transition is starting or not.
      */
-    int applyKeyguardOcclusionChange(boolean notify);
+    int applyKeyguardOcclusionChange(boolean transitionStarted);
 
     /**
      * Interface to the Window Manager state associated with a particular
@@ -1129,10 +1129,11 @@
 
     /**
      * Notifies the keyguard to start fading out.
-     *  @param startTime the start time of the animation in uptime milliseconds
      *
+     * @param startTime the start time of the animation in uptime milliseconds
+     * @param fadeoutDuration the duration of the exit animation, in milliseconds
      */
-    void startKeyguardExitAnimation(long startTime);
+    void startKeyguardExitAnimation(long startTime, long fadeoutDuration);
 
     /**
      * Called when System UI has been started.
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 7737421..b79ac6f 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -249,10 +249,10 @@
         }
     }
 
-    public void setOccluded(boolean isOccluded, boolean notify) {
+    public void setOccluded(boolean isOccluded, boolean animate, boolean notify) {
         if (mKeyguardService != null && notify) {
-            if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ")");
-            mKeyguardService.setOccluded(isOccluded, false /* animate */);
+            if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ") animate=" + animate);
+            mKeyguardService.setOccluded(isOccluded, animate);
         }
         mKeyguardState.occluded = isOccluded;
     }
@@ -394,9 +394,9 @@
         }
     }
 
-    public void startKeyguardExitAnimation(long startTime) {
+    public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
         if (mKeyguardService != null) {
-            mKeyguardService.startKeyguardExitAnimation(startTime, 0);
+            mKeyguardService.startKeyguardExitAnimation(startTime, fadeoutDuration);
         }
     }
 
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index dad9584..5a2fb18 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -571,8 +571,7 @@
     /**
      * Called when there has been user activity.
      */
-    public void onUserActivity(int displayGroupId, @PowerManager.UserActivityEvent int event,
-            int uid) {
+    public void onUserActivity(int displayGroupId, int event, int uid) {
         if (DEBUG) {
             Slog.d(TAG, "onUserActivity: event=" + event + ", uid=" + uid);
         }
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 9fe53fb..fec61ac 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -74,8 +74,6 @@
     private long mLastPowerOnTime;
     private long mLastUserActivityTime;
     private long mLastUserActivityTimeNoChangeLights;
-    @PowerManager.UserActivityEvent
-    private int mLastUserActivityEvent;
     /** Timestamp (milliseconds since boot) of the last time the power group was awoken.*/
     private long mLastWakeTime;
     /** Timestamp (milliseconds since boot) of the last time the power group was put to sleep. */
@@ -246,7 +244,7 @@
         return true;
     }
 
-    boolean dozeLocked(long eventTime, int uid, @PowerManager.GoToSleepReason int reason) {
+    boolean dozeLocked(long eventTime, int uid, int reason) {
         if (eventTime < getLastWakeTimeLocked() || !isInteractive(mWakefulness)) {
             return false;
         }
@@ -255,14 +253,9 @@
         try {
             reason = Math.min(PowerManager.GO_TO_SLEEP_REASON_MAX,
                     Math.max(reason, PowerManager.GO_TO_SLEEP_REASON_MIN));
-            long millisSinceLastUserActivity = eventTime - Math.max(
-                    mLastUserActivityTimeNoChangeLights, mLastUserActivityTime);
             Slog.i(TAG, "Powering off display group due to "
-                    + PowerManager.sleepReasonToString(reason)
-                    + " (groupId= " + getGroupId() + ", uid= " + uid
-                    + ", millisSinceLastUserActivity=" + millisSinceLastUserActivity
-                    + ", lastUserActivityEvent=" + PowerManager.userActivityEventToString(
-                    mLastUserActivityEvent) + ")...");
+                    + PowerManager.sleepReasonToString(reason)  + " (groupId= " + getGroupId()
+                    + ", uid= " + uid + ")...");
 
             setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
             setWakefulnessLocked(WAKEFULNESS_DOZING, eventTime, uid, reason, /* opUid= */ 0,
@@ -273,16 +266,14 @@
         return true;
     }
 
-    boolean sleepLocked(long eventTime, int uid, @PowerManager.GoToSleepReason int reason) {
+    boolean sleepLocked(long eventTime, int uid, int reason) {
         if (eventTime < mLastWakeTime || getWakefulnessLocked() == WAKEFULNESS_ASLEEP) {
             return false;
         }
 
         Trace.traceBegin(Trace.TRACE_TAG_POWER, "sleepPowerGroup");
         try {
-            Slog.i(TAG,
-                    "Sleeping power group (groupId=" + getGroupId() + ", uid=" + uid + ", reason="
-                            + PowerManager.sleepReasonToString(reason) + ")...");
+            Slog.i(TAG, "Sleeping power group (groupId=" + getGroupId() + ", uid=" + uid + ")...");
             setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
             setWakefulnessLocked(WAKEFULNESS_ASLEEP, eventTime, uid, reason, /* opUid= */0,
                     /* opPackageName= */ null, /* details= */ null);
@@ -296,20 +287,16 @@
         return mLastUserActivityTime;
     }
 
-    void setLastUserActivityTimeLocked(long lastUserActivityTime,
-            @PowerManager.UserActivityEvent int event) {
+    void setLastUserActivityTimeLocked(long lastUserActivityTime) {
         mLastUserActivityTime = lastUserActivityTime;
-        mLastUserActivityEvent = event;
     }
 
     public long getLastUserActivityTimeNoChangeLightsLocked() {
         return mLastUserActivityTimeNoChangeLights;
     }
 
-    public void setLastUserActivityTimeNoChangeLightsLocked(long time,
-            @PowerManager.UserActivityEvent int event) {
+    public void setLastUserActivityTimeNoChangeLightsLocked(long time) {
         mLastUserActivityTimeNoChangeLights = time;
-        mLastUserActivityEvent = event;
     }
 
     public int getUserActivitySummaryLocked() {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index ca3599c..2a1748c 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1216,7 +1216,6 @@
                 return;
             }
 
-            Slog.i(TAG, "onFlip(): Face " + (isFaceDown ? "down." : "up."));
             mIsFaceDown = isFaceDown;
             if (isFaceDown) {
                 final long currentTime = mClock.uptimeMillis();
@@ -1938,13 +1937,12 @@
 
     // Called from native code.
     @SuppressWarnings("unused")
-    private void userActivityFromNative(long eventTime, @PowerManager.UserActivityEvent int event,
-            int displayId, int flags) {
+    private void userActivityFromNative(long eventTime, int event, int displayId, int flags) {
         userActivityInternal(displayId, eventTime, event, flags, Process.SYSTEM_UID);
     }
 
-    private void userActivityInternal(int displayId, long eventTime,
-            @PowerManager.UserActivityEvent int event, int flags, int uid) {
+    private void userActivityInternal(int displayId, long eventTime, int event, int flags,
+            int uid) {
         synchronized (mLock) {
             if (displayId == Display.INVALID_DISPLAY) {
                 if (userActivityNoUpdateLocked(eventTime, event, flags, uid)) {
@@ -1995,12 +1993,11 @@
 
     @GuardedBy("mLock")
     private boolean userActivityNoUpdateLocked(final PowerGroup powerGroup, long eventTime,
-            @PowerManager.UserActivityEvent int event, int flags, int uid) {
+            int event, int flags, int uid) {
         final int groupId = powerGroup.getGroupId();
         if (DEBUG_SPEW) {
             Slog.d(TAG, "userActivityNoUpdateLocked: groupId=" + groupId
-                    + ", eventTime=" + eventTime
-                    + ", event=" + PowerManager.userActivityEventToString(event)
+                    + ", eventTime=" + eventTime + ", event=" + event
                     + ", flags=0x" + Integer.toHexString(flags) + ", uid=" + uid);
         }
 
@@ -2035,7 +2032,7 @@
             if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) {
                 if (eventTime > powerGroup.getLastUserActivityTimeNoChangeLightsLocked()
                         && eventTime > powerGroup.getLastUserActivityTimeLocked()) {
-                    powerGroup.setLastUserActivityTimeNoChangeLightsLocked(eventTime, event);
+                    powerGroup.setLastUserActivityTimeNoChangeLightsLocked(eventTime);
                     mDirty |= DIRTY_USER_ACTIVITY;
                     if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
                         mDirty |= DIRTY_QUIESCENT;
@@ -2045,7 +2042,7 @@
                 }
             } else {
                 if (eventTime > powerGroup.getLastUserActivityTimeLocked()) {
-                    powerGroup.setLastUserActivityTimeLocked(eventTime, event);
+                    powerGroup.setLastUserActivityTimeLocked(eventTime);
                     mDirty |= DIRTY_USER_ACTIVITY;
                     if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
                         mDirty |= DIRTY_QUIESCENT;
@@ -2072,8 +2069,7 @@
             @WakeReason int reason, String details, int uid, String opPackageName, int opUid) {
         if (DEBUG_SPEW) {
             Slog.d(TAG, "wakePowerGroupLocked: eventTime=" + eventTime
-                    + ", groupId=" + powerGroup.getGroupId()
-                    + ", reason=" + PowerManager.wakeReasonToString(reason) + ", uid=" + uid);
+                    + ", groupId=" + powerGroup.getGroupId() + ", uid=" + uid);
         }
         if (mForceSuspendActive || !mSystemReady) {
             return;
@@ -2096,11 +2092,11 @@
 
     @GuardedBy("mLock")
     private boolean dozePowerGroupLocked(final PowerGroup powerGroup, long eventTime,
-            @GoToSleepReason int reason, int uid) {
+            int reason, int uid) {
         if (DEBUG_SPEW) {
             Slog.d(TAG, "dozePowerGroup: eventTime=" + eventTime
-                    + ", groupId=" + powerGroup.getGroupId()
-                    + ", reason=" + PowerManager.sleepReasonToString(reason) + ", uid=" + uid);
+                    + ", groupId=" + powerGroup.getGroupId() + ", reason=" + reason
+                    + ", uid=" + uid);
         }
 
         if (!mSystemReady || !mBootCompleted) {
@@ -2111,12 +2107,10 @@
     }
 
     @GuardedBy("mLock")
-    private boolean sleepPowerGroupLocked(final PowerGroup powerGroup, long eventTime,
-            @GoToSleepReason int reason, int uid) {
+    private boolean sleepPowerGroupLocked(final PowerGroup powerGroup, long eventTime, int reason,
+            int uid) {
         if (DEBUG_SPEW) {
-            Slog.d(TAG, "sleepPowerGroup: eventTime=" + eventTime
-                    + ", groupId=" + powerGroup.getGroupId()
-                    + ", reason=" + PowerManager.sleepReasonToString(reason) + ", uid=" + uid);
+            Slog.d(TAG, "sleepPowerGroup: eventTime=" + eventTime + ", uid=" + uid);
         }
         if (!mBootCompleted || !mSystemReady) {
             return false;
@@ -2178,11 +2172,7 @@
             case WAKEFULNESS_DOZING:
                 traceMethodName = "goToSleep";
                 Slog.i(TAG, "Going to sleep due to " + PowerManager.sleepReasonToString(reason)
-                        + " (uid " + uid + ", screenOffTimeout=" + mScreenOffTimeoutSetting
-                        + ", activityTimeoutWM=" + mUserActivityTimeoutOverrideFromWindowManager
-                        + ", maxDimRatio=" + mMaximumScreenDimRatioConfig
-                        + ", maxDimDur=" + mMaximumScreenDimDurationConfig + ")...");
-
+                        + " (uid " + uid + ")...");
                 mLastGlobalSleepTime = eventTime;
                 mLastGlobalSleepReason = reason;
                 mLastGlobalSleepTimeRealtime = mClock.elapsedRealtime();
@@ -4267,7 +4257,7 @@
     void onUserActivity() {
         synchronized (mLock) {
             mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP).setLastUserActivityTimeLocked(
-                    mClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER);
+                    mClock.uptimeMillis());
         }
     }
 
@@ -5655,8 +5645,7 @@
         }
 
         @Override // Binder call
-        public void userActivity(int displayId, long eventTime,
-                @PowerManager.UserActivityEvent int event, int flags) {
+        public void userActivity(int displayId, long eventTime, int event, int flags) {
             final long now = mClock.uptimeMillis();
             if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
                     != PackageManager.PERMISSION_GRANTED
diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
index df902c2..fe4aa53 100644
--- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
@@ -657,7 +657,7 @@
 
         // Now that we have finally received all the data, we can tell mStats about it.
         synchronized (mStats) {
-            mStats.recordHistoryEventLocked(
+            mStats.addHistoryEventLocked(
                     elapsedRealtime,
                     uptime,
                     BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS,
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 37643c3..0c9ada8 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -108,7 +108,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.BatteryStatsHistory;
-import com.android.internal.os.BatteryStatsHistory.HistoryStepDetailsCalculator;
 import com.android.internal.os.BatteryStatsHistoryIterator;
 import com.android.internal.os.BinderCallsStats;
 import com.android.internal.os.BinderTransactionNameResolver;
@@ -174,6 +173,7 @@
     private static final boolean DEBUG_ENERGY_CPU = DEBUG_ENERGY;
     private static final boolean DEBUG_BINDER_STATS = false;
     private static final boolean DEBUG_MEMORY = false;
+    private static final boolean DEBUG_HISTORY = false;
 
     // TODO: remove "tcp" from network methods, since we measure total stats.
 
@@ -322,11 +322,6 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     protected Queue<UidToRemove> mPendingRemovedUids = new LinkedList<>();
 
-    @NonNull
-    BatteryStatsHistory copyHistory() {
-        return mHistory.copy();
-    }
-
     @VisibleForTesting
     public final class UidToRemove {
         private final int mStartUid;
@@ -418,7 +413,7 @@
                 if (changed) {
                     final long uptimeMs = mClock.uptimeMillis();
                     final long elapsedRealtimeMs = mClock.elapsedRealtime();
-                    mHistory.writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+                    addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
                 }
             }
         }
@@ -673,16 +668,16 @@
     /**
      * Mapping isolated uids to the actual owning app uid.
      */
-    private final SparseIntArray mIsolatedUids = new SparseIntArray();
+    final SparseIntArray mIsolatedUids = new SparseIntArray();
     /**
      * Internal reference count of isolated uids.
      */
-    private final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray();
+    final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray();
 
     /**
      * The statistics we have collected organized by uids.
      */
-    private final SparseArray<BatteryStatsImpl.Uid> mUidStats = new SparseArray<>();
+    final SparseArray<BatteryStatsImpl.Uid> mUidStats = new SparseArray<>();
 
     // A set of pools of currently active timers.  When a timer is queried, we will divide the
     // elapsed time by the number of active timers to arrive at that timer's share of the time.
@@ -690,21 +685,20 @@
     // changes.
     @VisibleForTesting
     protected ArrayList<StopwatchTimer> mPartialTimers = new ArrayList<>();
-    private final ArrayList<StopwatchTimer> mFullTimers = new ArrayList<>();
-    private final ArrayList<StopwatchTimer> mWindowTimers = new ArrayList<>();
-    private final ArrayList<StopwatchTimer> mDrawTimers = new ArrayList<>();
-    private final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers = new SparseArray<>();
-    private final ArrayList<StopwatchTimer> mWifiRunningTimers = new ArrayList<>();
-    private final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<>();
-    private final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<>();
-    private final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<>();
-    private final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers =
-            new SparseArray<>();
-    private final ArrayList<StopwatchTimer> mAudioTurnedOnTimers = new ArrayList<>();
-    private final ArrayList<StopwatchTimer> mVideoTurnedOnTimers = new ArrayList<>();
-    private final ArrayList<StopwatchTimer> mFlashlightTurnedOnTimers = new ArrayList<>();
-    private final ArrayList<StopwatchTimer> mCameraTurnedOnTimers = new ArrayList<>();
-    private final ArrayList<StopwatchTimer> mBluetoothScanOnTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mFullTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mWindowTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mDrawTimers = new ArrayList<>();
+    final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers = new SparseArray<>();
+    final ArrayList<StopwatchTimer> mWifiRunningTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<>();
+    final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers = new SparseArray<>();
+    final ArrayList<StopwatchTimer> mAudioTurnedOnTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mVideoTurnedOnTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mFlashlightTurnedOnTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mCameraTurnedOnTimers = new ArrayList<>();
+    final ArrayList<StopwatchTimer> mBluetoothScanOnTimers = new ArrayList<>();
 
     // Last partial timers we use for distributing CPU usage.
     @VisibleForTesting
@@ -719,24 +713,69 @@
     protected final TimeBase mOnBatteryScreenOffTimeBase = new TimeBase(true);
 
     private boolean mSystemReady;
-    private boolean mShuttingDown;
+    boolean mShuttingDown;
 
-    private final HistoryEventTracker mActiveEvents = new HistoryEventTracker();
-    private final HistoryStepDetailsCalculatorImpl mStepDetailsCalculator =
-            new HistoryStepDetailsCalculatorImpl();
+    final HistoryEventTracker mActiveEvents = new HistoryEventTracker();
 
-    private boolean mHaveBatteryLevel = false;
-    private boolean mBatteryPluggedIn;
-    private int mBatteryStatus;
-    private int mBatteryLevel;
-    private int mBatteryPlugType;
-    private int mBatteryChargeUah;
-    private int mBatteryHealth;
-    private int mBatteryTemperature;
-    private int mBatteryVoltageMv = -1;
+    long mHistoryBaseTimeMs;
+    protected boolean mHaveBatteryLevel = false;
+    protected boolean mRecordingHistory = false;
+    int mNumHistoryItems;
+
+    private static final int HISTORY_TAG_INDEX_LIMIT = 0x7ffe;
+    private static final int MAX_HISTORY_TAG_STRING_LENGTH = 1024;
+
+    final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<>();
+    private SparseArray<HistoryTag> mHistoryTags;
+    final Parcel mHistoryBuffer = Parcel.obtain();
+    final HistoryItem mHistoryLastWritten = new HistoryItem();
+    final HistoryItem mHistoryLastLastWritten = new HistoryItem();
+    final HistoryItem mHistoryAddTmp = new HistoryItem();
+    int mNextHistoryTagIdx = 0;
+    int mNumHistoryTagChars = 0;
+    int mHistoryBufferLastPos = -1;
+    int mActiveHistoryStates = 0xffffffff;
+    int mActiveHistoryStates2 = 0xffffffff;
+    long mLastHistoryElapsedRealtimeMs = 0;
+    long mTrackRunningHistoryElapsedRealtimeMs = 0;
+    long mTrackRunningHistoryUptimeMs = 0;
 
     @NonNull
-    private final BatteryStatsHistory mHistory;
+    final BatteryStatsHistory mBatteryStatsHistory;
+
+    final HistoryItem mHistoryCur = new HistoryItem();
+
+    // Used by computeHistoryStepDetails
+    HistoryStepDetails mLastHistoryStepDetails = null;
+    byte mLastHistoryStepLevel = 0;
+    final HistoryStepDetails mCurHistoryStepDetails = new HistoryStepDetails();
+    final HistoryStepDetails mTmpHistoryStepDetails = new HistoryStepDetails();
+
+    /**
+     * Total time (in milliseconds) spent executing in user code.
+     */
+    long mLastStepCpuUserTimeMs;
+    long mCurStepCpuUserTimeMs;
+    /**
+     * Total time (in milliseconds) spent executing in kernel code.
+     */
+    long mLastStepCpuSystemTimeMs;
+    long mCurStepCpuSystemTimeMs;
+    /**
+     * Times from /proc/stat (but measured in milliseconds).
+     */
+    long mLastStepStatUserTimeMs;
+    long mLastStepStatSystemTimeMs;
+    long mLastStepStatIOWaitTimeMs;
+    long mLastStepStatIrqTimeMs;
+    long mLastStepStatSoftIrqTimeMs;
+    long mLastStepStatIdleTimeMs;
+    long mCurStepStatUserTimeMs;
+    long mCurStepStatSystemTimeMs;
+    long mCurStepStatIOWaitTimeMs;
+    long mCurStepStatIrqTimeMs;
+    long mCurStepStatSoftIrqTimeMs;
+    long mCurStepStatIdleTimeMs;
 
     private BatteryStatsHistoryIterator mBatteryStatsHistoryIterator;
 
@@ -1352,6 +1391,7 @@
     int mDischargeUnplugLevel;
     int mDischargePlugLevel;
     int mDischargeCurrentLevel;
+    int mCurrentBatteryLevel;
     int mLowDischargeAmountSinceCharge;
     int mHighDischargeAmountSinceCharge;
     int mDischargeScreenOnUnplugLevel;
@@ -1403,6 +1443,7 @@
 
     private int mNumConnectivityChange;
 
+    private int mBatteryVoltageMv = -1;
     private int mEstimatedBatteryCapacityMah = -1;
 
     private int mLastLearnedBatteryCapacityUah = -1;
@@ -1586,27 +1627,28 @@
     }
 
     public BatteryStatsImpl(Clock clock) {
-        this(clock, null);
+        this(clock, (File) null);
     }
 
     public BatteryStatsImpl(Clock clock, File historyDirectory) {
         init(clock);
-        mHandler = null;
-        mConstants = new Constants(mHandler);
         mStartClockTimeMs = clock.currentTimeMillis();
         mCheckinFile = null;
         mDailyFile = null;
         if (historyDirectory == null) {
             mStatsFile = null;
-            mHistory = new BatteryStatsHistory(mStepDetailsCalculator, mClock);
+            mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer);
         } else {
             mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin"));
-            mHistory = new BatteryStatsHistory(historyDirectory, mConstants.MAX_HISTORY_FILES,
-                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+            mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer, historyDirectory,
+                    this::getMaxHistoryFiles);
         }
+        mHandler = null;
         mPlatformIdleStateCallback = null;
         mMeasuredEnergyRetriever = null;
         mUserInfoProvider = null;
+        mConstants = new Constants(mHandler);
+        clearHistoryLocked();
     }
 
     private void init(Clock clock) {
@@ -3869,188 +3911,406 @@
         return kmt;
     }
 
-    private class HistoryStepDetailsCalculatorImpl implements HistoryStepDetailsCalculator {
-        private final HistoryStepDetails mDetails = new HistoryStepDetails();
+    /**
+     * Returns the index for the specified tag. If this is the first time the tag is encountered
+     * while writing the current history buffer, the method returns
+     * <code>(index | TAG_FIRST_OCCURRENCE_FLAG)</code>
+     */
+    private int writeHistoryTag(HistoryTag tag) {
+        if (tag.string == null) {
+            Slog.wtfStack(TAG, "writeHistoryTag called with null name");
+        }
 
-        private boolean mHasHistoryStepDetails;
+        final int stringLength = tag.string.length();
+        if (stringLength > MAX_HISTORY_TAG_STRING_LENGTH) {
+            Slog.e(TAG, "Long battery history tag: " + tag.string);
+            tag.string = tag.string.substring(0, MAX_HISTORY_TAG_STRING_LENGTH);
+        }
 
-        private int mLastHistoryStepLevel;
-
-        /**
-         * Total time (in milliseconds) spent executing in user code.
-         */
-        private long mLastStepCpuUserTimeMs;
-        private long mCurStepCpuUserTimeMs;
-        /**
-         * Total time (in milliseconds) spent executing in kernel code.
-         */
-        private long mLastStepCpuSystemTimeMs;
-        private long mCurStepCpuSystemTimeMs;
-        /**
-         * Times from /proc/stat (but measured in milliseconds).
-         */
-        private long mLastStepStatUserTimeMs;
-        private long mLastStepStatSystemTimeMs;
-        private long mLastStepStatIOWaitTimeMs;
-        private long mLastStepStatIrqTimeMs;
-        private long mLastStepStatSoftIrqTimeMs;
-        private long mLastStepStatIdleTimeMs;
-        private long mCurStepStatUserTimeMs;
-        private long mCurStepStatSystemTimeMs;
-        private long mCurStepStatIOWaitTimeMs;
-        private long mCurStepStatIrqTimeMs;
-        private long mCurStepStatSoftIrqTimeMs;
-        private long mCurStepStatIdleTimeMs;
-
-        @Override
-        public HistoryStepDetails getHistoryStepDetails() {
-            if (mBatteryLevel >= mLastHistoryStepLevel && mHasHistoryStepDetails) {
-                mLastHistoryStepLevel = mBatteryLevel;
-                return null;
+        Integer idxObj = mHistoryTagPool.get(tag);
+        int idx;
+        if (idxObj != null) {
+            idx = idxObj;
+            if ((idx & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+                mHistoryTagPool.put(tag, idx & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
             }
+            return idx;
+        } else if (mNextHistoryTagIdx < HISTORY_TAG_INDEX_LIMIT) {
+            idx = mNextHistoryTagIdx;
+            HistoryTag key = new HistoryTag();
+            key.setTo(tag);
+            tag.poolIdx = idx;
+            mHistoryTagPool.put(key, idx);
+            mNextHistoryTagIdx++;
 
-            // Perform a CPU update right after we do this collection, so we have started
-            // collecting good data for the next step.
-            requestImmediateCpuUpdate();
+            mNumHistoryTagChars += stringLength + 1;
+            if (mHistoryTags != null) {
+                mHistoryTags.put(idx, key);
+            }
+            return idx | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
+        } else {
+            // Tag pool overflow: include the tag itself in the parcel
+            return HISTORY_TAG_INDEX_LIMIT | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
+        }
+    }
 
+    /*
+        The history delta format uses flags to denote further data in subsequent ints in the parcel.
+
+        There is always the first token, which may contain the delta time, or an indicator of
+        the length of the time (int or long) following this token.
+
+        First token: always present,
+        31              23              15               7             0
+        █M|L|K|J|I|H|G|F█E|D|C|B|A|T|T|T█T|T|T|T|T|T|T|T█T|T|T|T|T|T|T|T█
+
+        T: the delta time if it is <= 0x7fffd. Otherwise 0x7fffe indicates an int immediately
+           follows containing the time, and 0x7ffff indicates a long immediately follows with the
+           delta time.
+        A: battery level changed and an int follows with battery data.
+        B: state changed and an int follows with state change data.
+        C: state2 has changed and an int follows with state2 change data.
+        D: wakelock/wakereason has changed and an wakelock/wakereason struct follows.
+        E: event data has changed and an event struct follows.
+        F: battery charge in coulombs has changed and an int with the charge follows.
+        G: state flag denoting that the mobile radio was active.
+        H: state flag denoting that the wifi radio was active.
+        I: state flag denoting that a wifi scan occurred.
+        J: state flag denoting that a wifi full lock was held.
+        K: state flag denoting that the gps was on.
+        L: state flag denoting that a wakelock was held.
+        M: state flag denoting that the cpu was running.
+
+        Time int/long: if T in the first token is 0x7ffff or 0x7fffe, then an int or long follows
+        with the time delta.
+
+        Battery level int: if A in the first token is set,
+        31              23              15               7             0
+        █L|L|L|L|L|L|L|T█T|T|T|T|T|T|T|T█T|V|V|V|V|V|V|V█V|V|V|V|V|V|V|D█
+
+        D: indicates that extra history details follow.
+        V: the battery voltage.
+        T: the battery temperature.
+        L: the battery level (out of 100).
+
+        State change int: if B in the first token is set,
+        31              23              15               7             0
+        █S|S|S|H|H|H|P|P█F|E|D|C|B| | |A█ | | | | | | | █ | | | | | | | █
+
+        A: wifi multicast was on.
+        B: battery was plugged in.
+        C: screen was on.
+        D: phone was scanning for signal.
+        E: audio was on.
+        F: a sensor was active.
+
+        State2 change int: if C in the first token is set,
+        31              23              15               7             0
+        █M|L|K|J|I|H|H|G█F|E|D|C| | | | █ | | | | | | | █ |B|B|B|A|A|A|A█
+
+        A: 4 bits indicating the wifi supplicant state: {@link BatteryStats#WIFI_SUPPL_STATE_NAMES}.
+        B: 3 bits indicating the wifi signal strength: 0, 1, 2, 3, 4.
+        C: a bluetooth scan was active.
+        D: the camera was active.
+        E: bluetooth was on.
+        F: a phone call was active.
+        G: the device was charging.
+        H: 2 bits indicating the device-idle (doze) state: off, light, full
+        I: the flashlight was on.
+        J: wifi was on.
+        K: wifi was running.
+        L: video was playing.
+        M: power save mode was on.
+
+        Wakelock/wakereason struct: if D in the first token is set,
+        TODO(adamlesinski): describe wakelock/wakereason struct.
+
+        Event struct: if E in the first token is set,
+        TODO(adamlesinski): describe the event struct.
+
+        History step details struct: if D in the battery level int is set,
+        TODO(adamlesinski): describe the history step details struct.
+
+        Battery charge int: if F in the first token is set, an int representing the battery charge
+        in coulombs follows.
+     */
+
+    @GuardedBy("this")
+    public void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) {
+        if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) {
+            dest.writeInt(BatteryStatsHistory.DELTA_TIME_ABS);
+            cur.writeToParcel(dest, 0);
+            return;
+        }
+
+        final long deltaTime = cur.time - last.time;
+        final int lastBatteryLevelInt = buildBatteryLevelInt(last);
+        final int lastStateInt = buildStateInt(last);
+
+        int deltaTimeToken;
+        if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) {
+            deltaTimeToken = BatteryStatsHistory.DELTA_TIME_LONG;
+        } else if (deltaTime >= BatteryStatsHistory.DELTA_TIME_ABS) {
+            deltaTimeToken = BatteryStatsHistory.DELTA_TIME_INT;
+        } else {
+            deltaTimeToken = (int)deltaTime;
+        }
+        int firstToken = deltaTimeToken | (cur.states & BatteryStatsHistory.DELTA_STATE_MASK);
+        final int includeStepDetails = mLastHistoryStepLevel > cur.batteryLevel
+                ? BatteryStatsHistory.BATTERY_DELTA_LEVEL_FLAG : 0;
+        final boolean computeStepDetails = includeStepDetails != 0
+                || mLastHistoryStepDetails == null;
+        final int batteryLevelInt = buildBatteryLevelInt(cur) | includeStepDetails;
+        final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt;
+        if (batteryLevelIntChanged) {
+            firstToken |= BatteryStatsHistory.DELTA_BATTERY_LEVEL_FLAG;
+        }
+        final int stateInt = buildStateInt(cur);
+        final boolean stateIntChanged = stateInt != lastStateInt;
+        if (stateIntChanged) {
+            firstToken |= BatteryStatsHistory.DELTA_STATE_FLAG;
+        }
+        final boolean state2IntChanged = cur.states2 != last.states2;
+        if (state2IntChanged) {
+            firstToken |= BatteryStatsHistory.DELTA_STATE2_FLAG;
+        }
+        if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
+            firstToken |= BatteryStatsHistory.DELTA_WAKELOCK_FLAG;
+        }
+        if (cur.eventCode != HistoryItem.EVENT_NONE) {
+            firstToken |= BatteryStatsHistory.DELTA_EVENT_FLAG;
+        }
+
+        final boolean batteryChargeChanged = cur.batteryChargeUah != last.batteryChargeUah;
+        if (batteryChargeChanged) {
+            firstToken |= BatteryStatsHistory.DELTA_BATTERY_CHARGE_FLAG;
+        }
+        dest.writeInt(firstToken);
+        if (DEBUG) Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken)
+                + " deltaTime=" + deltaTime);
+
+        if (deltaTimeToken >= BatteryStatsHistory.DELTA_TIME_INT) {
+            if (deltaTimeToken == BatteryStatsHistory.DELTA_TIME_INT) {
+                if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int)deltaTime);
+                dest.writeInt((int)deltaTime);
+            } else {
+                if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime);
+                dest.writeLong(deltaTime);
+            }
+        }
+        if (batteryLevelIntChanged) {
+            dest.writeInt(batteryLevelInt);
+            if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryToken=0x"
+                    + Integer.toHexString(batteryLevelInt)
+                    + " batteryLevel=" + cur.batteryLevel
+                    + " batteryTemp=" + cur.batteryTemperature
+                    + " batteryVolt=" + (int)cur.batteryVoltage);
+        }
+        if (stateIntChanged) {
+            dest.writeInt(stateInt);
+            if (DEBUG) Slog.i(TAG, "WRITE DELTA: stateToken=0x"
+                    + Integer.toHexString(stateInt)
+                    + " batteryStatus=" + cur.batteryStatus
+                    + " batteryHealth=" + cur.batteryHealth
+                    + " batteryPlugType=" + cur.batteryPlugType
+                    + " states=0x" + Integer.toHexString(cur.states));
+        }
+        if (state2IntChanged) {
+            dest.writeInt(cur.states2);
+            if (DEBUG) Slog.i(TAG, "WRITE DELTA: states2=0x"
+                    + Integer.toHexString(cur.states2));
+        }
+        if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
+            int wakeLockIndex;
+            int wakeReasonIndex;
+            if (cur.wakelockTag != null) {
+                wakeLockIndex = writeHistoryTag(cur.wakelockTag);
+                if (DEBUG) Slog.i(TAG, "WRITE DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx
+                    + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string);
+            } else {
+                wakeLockIndex = 0xffff;
+            }
+            if (cur.wakeReasonTag != null) {
+                wakeReasonIndex = writeHistoryTag(cur.wakeReasonTag);
+                if (DEBUG) Slog.i(TAG, "WRITE DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx
+                    + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string);
+            } else {
+                wakeReasonIndex = 0xffff;
+            }
+            dest.writeInt((wakeReasonIndex<<16) | wakeLockIndex);
+            if (cur.wakelockTag != null
+                    && (wakeLockIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+                cur.wakelockTag.writeToParcel(dest, 0);
+                cur.tagsFirstOccurrence = true;
+            }
+            if (cur.wakeReasonTag != null
+                    && (wakeReasonIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+                cur.wakeReasonTag.writeToParcel(dest, 0);
+                cur.tagsFirstOccurrence = true;
+            }
+        }
+        if (cur.eventCode != HistoryItem.EVENT_NONE) {
+            final int index = writeHistoryTag(cur.eventTag);
+            final int codeAndIndex = (cur.eventCode & 0xffff) | (index << 16);
+            dest.writeInt(codeAndIndex);
+            if ((index & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+                cur.eventTag.writeToParcel(dest, 0);
+                cur.tagsFirstOccurrence = true;
+            }
+            if (DEBUG) Slog.i(TAG, "WRITE DELTA: event=" + cur.eventCode + " tag=#"
+                    + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":"
+                    + cur.eventTag.string);
+        }
+        if (computeStepDetails) {
             if (mPlatformIdleStateCallback != null) {
-                mDetails.statSubsystemPowerState =
+                mCurHistoryStepDetails.statSubsystemPowerState =
                         mPlatformIdleStateCallback.getSubsystemLowPowerStats();
                 if (DEBUG) Slog.i(TAG, "WRITE SubsystemPowerState:" +
-                        mDetails.statSubsystemPowerState);
-            }
+                        mCurHistoryStepDetails.statSubsystemPowerState);
 
-            if (!mHasHistoryStepDetails) {
-                // We are not generating a delta, so all we need to do is reset the stats
-                // we will later be doing a delta from.
-                final int uidCount = mUidStats.size();
-                for (int i = 0; i < uidCount; i++) {
-                    final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
-                    uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs;
-                    uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs;
-                }
-                mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs;
-                mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs;
-                mLastStepStatUserTimeMs = mCurStepStatUserTimeMs;
-                mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs;
-                mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs;
-                mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs;
-                mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs;
-                mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs;
-                mDetails.clear();
+            }
+            computeHistoryStepDetails(mCurHistoryStepDetails, mLastHistoryStepDetails);
+            if (includeStepDetails != 0) {
+                mCurHistoryStepDetails.writeToParcel(dest);
+            }
+            cur.stepDetails = mCurHistoryStepDetails;
+            mLastHistoryStepDetails = mCurHistoryStepDetails;
+        } else {
+            cur.stepDetails = null;
+        }
+        if (mLastHistoryStepLevel < cur.batteryLevel) {
+            mLastHistoryStepDetails = null;
+        }
+        mLastHistoryStepLevel = cur.batteryLevel;
+
+        if (batteryChargeChanged) {
+            if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryChargeUah=" + cur.batteryChargeUah);
+            dest.writeInt(cur.batteryChargeUah);
+        }
+        dest.writeDouble(cur.modemRailChargeMah);
+        dest.writeDouble(cur.wifiRailChargeMah);
+    }
+
+    private int buildBatteryLevelInt(HistoryItem h) {
+        return ((((int)h.batteryLevel)<<25)&0xfe000000)
+                | ((((int)h.batteryTemperature)<<15)&0x01ff8000)
+                | ((((int)h.batteryVoltage)<<1)&0x00007ffe);
+    }
+
+    private int buildStateInt(HistoryItem h) {
+        int plugType = 0;
+        if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_AC) != 0) {
+            plugType = 1;
+        } else if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_USB) != 0) {
+            plugType = 2;
+        } else if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0) {
+            plugType = 3;
+        }
+        return ((h.batteryStatus & BatteryStatsHistory.STATE_BATTERY_STATUS_MASK)
+                << BatteryStatsHistory.STATE_BATTERY_STATUS_SHIFT)
+                | ((h.batteryHealth & BatteryStatsHistory.STATE_BATTERY_HEALTH_MASK)
+                << BatteryStatsHistory.STATE_BATTERY_HEALTH_SHIFT)
+                | ((plugType & BatteryStatsHistory.STATE_BATTERY_PLUG_MASK)
+                << BatteryStatsHistory.STATE_BATTERY_PLUG_SHIFT)
+                | (h.states & (~BatteryStatsHistory.STATE_BATTERY_MASK));
+    }
+
+    private void computeHistoryStepDetails(final HistoryStepDetails out,
+            final HistoryStepDetails last) {
+        final HistoryStepDetails tmp = last != null ? mTmpHistoryStepDetails : out;
+
+        // Perform a CPU update right after we do this collection, so we have started
+        // collecting good data for the next step.
+        requestImmediateCpuUpdate();
+
+        if (last == null) {
+            // We are not generating a delta, so all we need to do is reset the stats
+            // we will later be doing a delta from.
+            final int NU = mUidStats.size();
+            for (int i=0; i<NU; i++) {
+                final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
+                uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs;
+                uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs;
+            }
+            mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs;
+            mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs;
+            mLastStepStatUserTimeMs = mCurStepStatUserTimeMs;
+            mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs;
+            mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs;
+            mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs;
+            mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs;
+            mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs;
+            tmp.clear();
+            return;
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "Step stats last: user=" + mLastStepCpuUserTimeMs + " sys="
+                    + mLastStepStatSystemTimeMs + " io=" + mLastStepStatIOWaitTimeMs
+                    + " irq=" + mLastStepStatIrqTimeMs + " sirq="
+                    + mLastStepStatSoftIrqTimeMs + " idle=" + mLastStepStatIdleTimeMs);
+            Slog.d(TAG, "Step stats cur: user=" + mCurStepCpuUserTimeMs + " sys="
+                    + mCurStepStatSystemTimeMs + " io=" + mCurStepStatIOWaitTimeMs
+                    + " irq=" + mCurStepStatIrqTimeMs + " sirq="
+                    + mCurStepStatSoftIrqTimeMs + " idle=" + mCurStepStatIdleTimeMs);
+        }
+        out.userTime = (int) (mCurStepCpuUserTimeMs - mLastStepCpuUserTimeMs);
+        out.systemTime = (int) (mCurStepCpuSystemTimeMs - mLastStepCpuSystemTimeMs);
+        out.statUserTime = (int) (mCurStepStatUserTimeMs - mLastStepStatUserTimeMs);
+        out.statSystemTime = (int) (mCurStepStatSystemTimeMs - mLastStepStatSystemTimeMs);
+        out.statIOWaitTime = (int) (mCurStepStatIOWaitTimeMs - mLastStepStatIOWaitTimeMs);
+        out.statIrqTime = (int) (mCurStepStatIrqTimeMs - mLastStepStatIrqTimeMs);
+        out.statSoftIrqTime = (int) (mCurStepStatSoftIrqTimeMs - mLastStepStatSoftIrqTimeMs);
+        out.statIdlTime = (int) (mCurStepStatIdleTimeMs - mLastStepStatIdleTimeMs);
+        out.appCpuUid1 = out.appCpuUid2 = out.appCpuUid3 = -1;
+        out.appCpuUTime1 = out.appCpuUTime2 = out.appCpuUTime3 = 0;
+        out.appCpuSTime1 = out.appCpuSTime2 = out.appCpuSTime3 = 0;
+        final int NU = mUidStats.size();
+        for (int i=0; i<NU; i++) {
+            final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
+            final int totalUTimeMs = (int) (uid.mCurStepUserTimeMs - uid.mLastStepUserTimeMs);
+            final int totalSTimeMs = (int) (uid.mCurStepSystemTimeMs - uid.mLastStepSystemTimeMs);
+            final int totalTimeMs = totalUTimeMs + totalSTimeMs;
+            uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs;
+            uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs;
+            if (totalTimeMs <= (out.appCpuUTime3 + out.appCpuSTime3)) {
+                continue;
+            }
+            if (totalTimeMs <= (out.appCpuUTime2 + out.appCpuSTime2)) {
+                out.appCpuUid3 = uid.mUid;
+                out.appCpuUTime3 = totalUTimeMs;
+                out.appCpuSTime3 = totalSTimeMs;
             } else {
-                if (DEBUG) {
-                    Slog.d(TAG, "Step stats last: user=" + mLastStepCpuUserTimeMs + " sys="
-                            + mLastStepStatSystemTimeMs + " io=" + mLastStepStatIOWaitTimeMs
-                            + " irq=" + mLastStepStatIrqTimeMs + " sirq="
-                            + mLastStepStatSoftIrqTimeMs + " idle=" + mLastStepStatIdleTimeMs);
-                    Slog.d(TAG, "Step stats cur: user=" + mCurStepCpuUserTimeMs + " sys="
-                            + mCurStepStatSystemTimeMs + " io=" + mCurStepStatIOWaitTimeMs
-                            + " irq=" + mCurStepStatIrqTimeMs + " sirq="
-                            + mCurStepStatSoftIrqTimeMs + " idle=" + mCurStepStatIdleTimeMs);
+                out.appCpuUid3 = out.appCpuUid2;
+                out.appCpuUTime3 = out.appCpuUTime2;
+                out.appCpuSTime3 = out.appCpuSTime2;
+                if (totalTimeMs <= (out.appCpuUTime1 + out.appCpuSTime1)) {
+                    out.appCpuUid2 = uid.mUid;
+                    out.appCpuUTime2 = totalUTimeMs;
+                    out.appCpuSTime2 = totalSTimeMs;
+                } else {
+                    out.appCpuUid2 = out.appCpuUid1;
+                    out.appCpuUTime2 = out.appCpuUTime1;
+                    out.appCpuSTime2 = out.appCpuSTime1;
+                    out.appCpuUid1 = uid.mUid;
+                    out.appCpuUTime1 = totalUTimeMs;
+                    out.appCpuSTime1 = totalSTimeMs;
                 }
-                mDetails.userTime = (int) (mCurStepCpuUserTimeMs - mLastStepCpuUserTimeMs);
-                mDetails.systemTime = (int) (mCurStepCpuSystemTimeMs - mLastStepCpuSystemTimeMs);
-                mDetails.statUserTime = (int) (mCurStepStatUserTimeMs - mLastStepStatUserTimeMs);
-                mDetails.statSystemTime =
-                        (int) (mCurStepStatSystemTimeMs - mLastStepStatSystemTimeMs);
-                mDetails.statIOWaitTime =
-                        (int) (mCurStepStatIOWaitTimeMs - mLastStepStatIOWaitTimeMs);
-                mDetails.statIrqTime = (int) (mCurStepStatIrqTimeMs - mLastStepStatIrqTimeMs);
-                mDetails.statSoftIrqTime =
-                        (int) (mCurStepStatSoftIrqTimeMs - mLastStepStatSoftIrqTimeMs);
-                mDetails.statIdlTime = (int) (mCurStepStatIdleTimeMs - mLastStepStatIdleTimeMs);
-                mDetails.appCpuUid1 = mDetails.appCpuUid2 = mDetails.appCpuUid3 = -1;
-                mDetails.appCpuUTime1 = mDetails.appCpuUTime2 = mDetails.appCpuUTime3 = 0;
-                mDetails.appCpuSTime1 = mDetails.appCpuSTime2 = mDetails.appCpuSTime3 = 0;
-                final int uidCount = mUidStats.size();
-                for (int i = 0; i < uidCount; i++) {
-                    final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
-                    final int totalUTimeMs =
-                            (int) (uid.mCurStepUserTimeMs - uid.mLastStepUserTimeMs);
-                    final int totalSTimeMs =
-                            (int) (uid.mCurStepSystemTimeMs - uid.mLastStepSystemTimeMs);
-                    final int totalTimeMs = totalUTimeMs + totalSTimeMs;
-                    uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs;
-                    uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs;
-                    if (totalTimeMs <= (mDetails.appCpuUTime3 + mDetails.appCpuSTime3)) {
-                        continue;
-                    }
-                    if (totalTimeMs <= (mDetails.appCpuUTime2 + mDetails.appCpuSTime2)) {
-                        mDetails.appCpuUid3 = uid.mUid;
-                        mDetails.appCpuUTime3 = totalUTimeMs;
-                        mDetails.appCpuSTime3 = totalSTimeMs;
-                    } else {
-                        mDetails.appCpuUid3 = mDetails.appCpuUid2;
-                        mDetails.appCpuUTime3 = mDetails.appCpuUTime2;
-                        mDetails.appCpuSTime3 = mDetails.appCpuSTime2;
-                        if (totalTimeMs <= (mDetails.appCpuUTime1 + mDetails.appCpuSTime1)) {
-                            mDetails.appCpuUid2 = uid.mUid;
-                            mDetails.appCpuUTime2 = totalUTimeMs;
-                            mDetails.appCpuSTime2 = totalSTimeMs;
-                        } else {
-                            mDetails.appCpuUid2 = mDetails.appCpuUid1;
-                            mDetails.appCpuUTime2 = mDetails.appCpuUTime1;
-                            mDetails.appCpuSTime2 = mDetails.appCpuSTime1;
-                            mDetails.appCpuUid1 = uid.mUid;
-                            mDetails.appCpuUTime1 = totalUTimeMs;
-                            mDetails.appCpuSTime1 = totalSTimeMs;
-                        }
-                    }
-                }
-                mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs;
-                mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs;
-                mLastStepStatUserTimeMs = mCurStepStatUserTimeMs;
-                mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs;
-                mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs;
-                mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs;
-                mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs;
-                mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs;
             }
-
-            mHasHistoryStepDetails = mBatteryLevel <= mLastHistoryStepLevel;
-            mLastHistoryStepLevel = mBatteryLevel;
-
-            return mDetails;
         }
-
-        public void addCpuStats(int totalUTimeMs, int totalSTimeMs, int statUserTimeMs,
-                int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs,
-                int statSoftIrqTimeMs, int statIdleTimeMs) {
-            if (DEBUG) {
-                Slog.d(TAG, "Adding cpu: tuser=" + totalUTimeMs + " tsys=" + totalSTimeMs
-                        + " user=" + statUserTimeMs + " sys=" + statSystemTimeMs
-                        + " io=" + statIOWaitTimeMs + " irq=" + statIrqTimeMs
-                        + " sirq=" + statSoftIrqTimeMs + " idle=" + statIdleTimeMs);
-            }
-            mCurStepCpuUserTimeMs += totalUTimeMs;
-            mCurStepCpuSystemTimeMs += totalSTimeMs;
-            mCurStepStatUserTimeMs += statUserTimeMs;
-            mCurStepStatSystemTimeMs += statSystemTimeMs;
-            mCurStepStatIOWaitTimeMs += statIOWaitTimeMs;
-            mCurStepStatIrqTimeMs += statIrqTimeMs;
-            mCurStepStatSoftIrqTimeMs += statSoftIrqTimeMs;
-            mCurStepStatIdleTimeMs += statIdleTimeMs;
-        }
-
-        @Override
-        public void clear() {
-            mHasHistoryStepDetails = false;
-            mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs = 0;
-            mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs = 0;
-            mLastStepStatUserTimeMs = mCurStepStatUserTimeMs = 0;
-            mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs = 0;
-            mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs = 0;
-            mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs = 0;
-            mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs = 0;
-            mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs = 0;
-        }
+        mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs;
+        mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs;
+        mLastStepStatUserTimeMs = mCurStepStatUserTimeMs;
+        mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs;
+        mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs;
+        mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs;
+        mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs;
+        mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs;
     }
 
     @GuardedBy("this")
     @Override
     public void commitCurrentHistoryBatchLocked() {
-        mHistory.commitCurrentHistoryBatchLocked();
+        mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
     }
 
     @GuardedBy("this")
@@ -4066,9 +4326,191 @@
     }
 
     @GuardedBy("this")
-    public void recordHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code,
+    void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
+        if (!mHaveBatteryLevel || !mRecordingHistory) {
+            return;
+        }
+
+        final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time;
+        final int diffStates = mHistoryLastWritten.states^(cur.states&mActiveHistoryStates);
+        final int diffStates2 = mHistoryLastWritten.states2^(cur.states2&mActiveHistoryStates2);
+        final int lastDiffStates = mHistoryLastWritten.states^mHistoryLastLastWritten.states;
+        final int lastDiffStates2 = mHistoryLastWritten.states2^mHistoryLastLastWritten.states2;
+        if (DEBUG) {
+            Slog.i(TAG, "ADD: tdelta=" + timeDiffMs + " diff="
+                    + Integer.toHexString(diffStates) + " lastDiff="
+                    + Integer.toHexString(lastDiffStates) + " diff2="
+                    + Integer.toHexString(diffStates2) + " lastDiff2="
+                    + Integer.toHexString(lastDiffStates2));
+        }
+        if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE
+                && timeDiffMs < 1000 && (diffStates & lastDiffStates) == 0
+                && (diffStates2&lastDiffStates2) == 0
+                && (!mHistoryLastWritten.tagsFirstOccurrence && !cur.tagsFirstOccurrence)
+                && (mHistoryLastWritten.wakelockTag == null || cur.wakelockTag == null)
+                && (mHistoryLastWritten.wakeReasonTag == null || cur.wakeReasonTag == null)
+                && mHistoryLastWritten.stepDetails == null
+                && (mHistoryLastWritten.eventCode == HistoryItem.EVENT_NONE
+                        || cur.eventCode == HistoryItem.EVENT_NONE)
+                && mHistoryLastWritten.batteryLevel == cur.batteryLevel
+                && mHistoryLastWritten.batteryStatus == cur.batteryStatus
+                && mHistoryLastWritten.batteryHealth == cur.batteryHealth
+                && mHistoryLastWritten.batteryPlugType == cur.batteryPlugType
+                && mHistoryLastWritten.batteryTemperature == cur.batteryTemperature
+                && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage) {
+            // We can merge this new change in with the last one.  Merging is
+            // allowed as long as only the states have changed, and within those states
+            // as long as no bit has changed both between now and the last entry, as
+            // well as the last entry and the one before it (so we capture any toggles).
+            if (DEBUG) Slog.i(TAG, "ADD: rewinding back to " + mHistoryBufferLastPos);
+            mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
+            mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
+            mHistoryBufferLastPos = -1;
+            elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTimeMs;
+            // If the last written history had a wakelock tag, we need to retain it.
+            // Note that the condition above made sure that we aren't in a case where
+            // both it and the current history item have a wakelock tag.
+            if (mHistoryLastWritten.wakelockTag != null) {
+                cur.wakelockTag = cur.localWakelockTag;
+                cur.wakelockTag.setTo(mHistoryLastWritten.wakelockTag);
+            }
+            // If the last written history had a wake reason tag, we need to retain it.
+            // Note that the condition above made sure that we aren't in a case where
+            // both it and the current history item have a wakelock tag.
+            if (mHistoryLastWritten.wakeReasonTag != null) {
+                cur.wakeReasonTag = cur.localWakeReasonTag;
+                cur.wakeReasonTag.setTo(mHistoryLastWritten.wakeReasonTag);
+            }
+            // If the last written history had an event, we need to retain it.
+            // Note that the condition above made sure that we aren't in a case where
+            // both it and the current history item have an event.
+            if (mHistoryLastWritten.eventCode != HistoryItem.EVENT_NONE) {
+                cur.eventCode = mHistoryLastWritten.eventCode;
+                cur.eventTag = cur.localEventTag;
+                cur.eventTag.setTo(mHistoryLastWritten.eventTag);
+            }
+            mHistoryLastWritten.setTo(mHistoryLastLastWritten);
+        }
+        final int dataSize = mHistoryBuffer.dataSize();
+
+        if (dataSize >= mConstants.MAX_HISTORY_BUFFER) {
+            //open a new history file.
+            final long start = SystemClock.uptimeMillis();
+            writeHistoryLocked();
+            if (DEBUG) {
+                Slog.d(TAG, "addHistoryBufferLocked writeHistoryLocked takes ms:"
+                        + (SystemClock.uptimeMillis() - start));
+            }
+            mBatteryStatsHistory.startNextFile();
+            mHistoryBuffer.setDataSize(0);
+            mHistoryBuffer.setDataPosition(0);
+            mHistoryBuffer.setDataCapacity(mConstants.MAX_HISTORY_BUFFER / 2);
+            mHistoryBufferLastPos = -1;
+            mHistoryLastWritten.clear();
+            mHistoryLastLastWritten.clear();
+
+            // Mark every entry in the pool with a flag indicating that the tag
+            // has not yet been encountered while writing the current history buffer.
+            for (Map.Entry<HistoryTag, Integer> entry: mHistoryTagPool.entrySet()) {
+                entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
+            }
+            // Make a copy of mHistoryCur.
+            HistoryItem copy = new HistoryItem();
+            copy.setTo(cur);
+            // startRecordingHistory will reset mHistoryCur.
+            startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
+            // Add the copy into history buffer.
+            addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, copy);
+            return;
+        }
+
+        if (dataSize == 0) {
+            // The history is currently empty; we need it to start with a time stamp.
+            cur.currentTime = mClock.currentTimeMillis();
+            addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_RESET, cur);
+        }
+        addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, cur);
+    }
+
+    @GuardedBy("this")
+    private void addHistoryBufferLocked(long elapsedRealtimeMs, byte cmd, HistoryItem cur) {
+        if (mBatteryStatsHistoryIterator != null) {
+            throw new IllegalStateException("Can't do this while iterating history!");
+        }
+        mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
+        mHistoryLastLastWritten.setTo(mHistoryLastWritten);
+        final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence;
+        mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur);
+        mHistoryLastWritten.tagsFirstOccurrence = hasTags;
+        mHistoryLastWritten.states &= mActiveHistoryStates;
+        mHistoryLastWritten.states2 &= mActiveHistoryStates2;
+        writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
+        mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
+        cur.wakelockTag = null;
+        cur.wakeReasonTag = null;
+        cur.eventCode = HistoryItem.EVENT_NONE;
+        cur.eventTag = null;
+        cur.tagsFirstOccurrence = false;
+        if (DEBUG_HISTORY) Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
+                + " now " + mHistoryBuffer.dataPosition()
+                + " size is now " + mHistoryBuffer.dataSize());
+    }
+
+    @GuardedBy("this")
+    void addHistoryRecordLocked(long elapsedRealtimeMs, long uptimeMs) {
+        if (mTrackRunningHistoryElapsedRealtimeMs != 0) {
+            final long diffElapsedMs = elapsedRealtimeMs - mTrackRunningHistoryElapsedRealtimeMs;
+            final long diffUptimeMs = uptimeMs - mTrackRunningHistoryUptimeMs;
+            if (diffUptimeMs < (diffElapsedMs - 20)) {
+                final long wakeElapsedTimeMs = elapsedRealtimeMs - (diffElapsedMs - diffUptimeMs);
+                mHistoryAddTmp.setTo(mHistoryLastWritten);
+                mHistoryAddTmp.wakelockTag = null;
+                mHistoryAddTmp.wakeReasonTag = null;
+                mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE;
+                mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG;
+                addHistoryRecordInnerLocked(wakeElapsedTimeMs, uptimeMs, mHistoryAddTmp);
+            }
+        }
+        mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG;
+        mTrackRunningHistoryElapsedRealtimeMs = elapsedRealtimeMs;
+        mTrackRunningHistoryUptimeMs = uptimeMs;
+        addHistoryRecordInnerLocked(elapsedRealtimeMs, uptimeMs, mHistoryCur);
+    }
+
+    @GuardedBy("this")
+    void addHistoryRecordInnerLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
+        addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, cur);
+    }
+
+    @GuardedBy("this")
+    public void addHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code,
             String name, int uid) {
-        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, code, name, uid);
+        mHistoryCur.eventCode = code;
+        mHistoryCur.eventTag = mHistoryCur.localEventTag;
+        mHistoryCur.eventTag.string = name;
+        mHistoryCur.eventTag.uid = uid;
+        addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+    }
+
+    @GuardedBy("this")
+    void clearHistoryLocked() {
+        if (DEBUG_HISTORY) Slog.i(TAG, "********** CLEARING HISTORY!");
+        mHistoryBaseTimeMs = 0;
+        mLastHistoryElapsedRealtimeMs = 0;
+        mTrackRunningHistoryElapsedRealtimeMs = 0;
+        mTrackRunningHistoryUptimeMs = 0;
+
+        mHistoryBuffer.setDataSize(0);
+        mHistoryBuffer.setDataPosition(0);
+        mHistoryBuffer.setDataCapacity(mConstants.MAX_HISTORY_BUFFER / 2);
+        mHistoryLastLastWritten.clear();
+        mHistoryLastWritten.clear();
+        mHistoryTagPool.clear();
+        mNextHistoryTagIdx = 0;
+        mNumHistoryTagChars = 0;
+        mHistoryBufferLastPos = -1;
+        mActiveHistoryStates = 0xffffffff;
+        mActiveHistoryStates2 = 0xffffffff;
     }
 
     @GuardedBy("this")
@@ -4221,13 +4663,13 @@
         if (!mActiveEvents.updateState(code, name, uid, 0)) {
             return;
         }
-        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, code, name, uid);
+        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, code, name, uid);
     }
 
     @GuardedBy("this")
     public void noteCurrentTimeChangedLocked(long currentTimeMs,
             long elapsedRealtimeMs, long uptimeMs) {
-        mHistory.recordCurrentTimeChange(elapsedRealtimeMs, uptimeMs, currentTimeMs);
+        recordCurrentTimeChangeLocked(currentTimeMs, elapsedRealtimeMs, uptimeMs);
     }
 
     @GuardedBy("this")
@@ -4244,7 +4686,7 @@
         if (!mRecordAllHistory) {
             return;
         }
-        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_START, name, uid);
+        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_START, name, uid);
     }
 
     @GuardedBy("this")
@@ -4302,7 +4744,8 @@
         if (!mRecordAllHistory) {
             return;
         }
-        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_FINISH, name, uid);
+        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_FINISH,
+                name, uid);
     }
 
     @GuardedBy("this")
@@ -4318,7 +4761,7 @@
         if (!mActiveEvents.updateState(HistoryItem.EVENT_SYNC_START, name, uid, 0)) {
             return;
         }
-        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_START, name, uid);
+        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_START, name, uid);
     }
 
     @GuardedBy("this")
@@ -4334,7 +4777,8 @@
         if (!mActiveEvents.updateState(HistoryItem.EVENT_SYNC_FINISH, name, uid, 0)) {
             return;
         }
-        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_FINISH, name, uid);
+        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_FINISH,
+                name, uid);
     }
 
     @GuardedBy("this")
@@ -4350,7 +4794,7 @@
         if (!mActiveEvents.updateState(HistoryItem.EVENT_JOB_START, name, uid, 0)) {
             return;
         }
-        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_START, name, uid);
+        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_START, name, uid);
     }
 
     @GuardedBy("this")
@@ -4368,7 +4812,7 @@
         if (!mActiveEvents.updateState(HistoryItem.EVENT_JOB_FINISH, name, uid, 0)) {
             return;
         }
-        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_FINISH, name, uid);
+        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_FINISH, name, uid);
     }
 
     @GuardedBy("this")
@@ -4416,7 +4860,7 @@
             for (int i = 0; i < workSource.size(); ++i) {
                 uid = mapUid(workSource.getUid(i));
                 if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
-                    mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
+                    addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
                 }
             }
 
@@ -4425,7 +4869,7 @@
                 for (int i = 0; i < workChains.size(); ++i) {
                     uid = mapUid(workChains.get(i).getAttributionUid());
                     if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
-                        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
+                        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
                     }
                 }
             }
@@ -4433,7 +4877,7 @@
             uid = mapUid(uid);
 
             if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
-                mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
+                addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
             }
         }
     }
@@ -4508,7 +4952,7 @@
                 for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
                     SparseIntArray uids = ent.getValue();
                     for (int j=0; j<uids.size(); j++) {
-                        mHistory.recordEvent(mSecRealtime, mSecUptime,
+                        addHistoryEventLocked(mSecRealtime, mSecUptime,
                                 HistoryItem.EVENT_PROC_FINISH, ent.getKey(), uids.keyAt(j));
                     }
                 }
@@ -4523,8 +4967,8 @@
                 for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
                     SparseIntArray uids = ent.getValue();
                     for (int j=0; j<uids.size(); j++) {
-                        mHistory.recordEvent(mSecRealtime, mSecUptime, HistoryItem.EVENT_PROC_START,
-                                ent.getKey(), uids.keyAt(j));
+                        addHistoryEventLocked(mSecRealtime, mSecUptime,
+                                HistoryItem.EVENT_PROC_START, ent.getKey(), uids.keyAt(j));
                     }
                 }
             }
@@ -4567,19 +5011,30 @@
             if (mRecordAllHistory) {
                 if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_START, historyName,
                         mappedUid, 0)) {
-                    mHistory.recordEvent(elapsedRealtimeMs, uptimeMs,
+                    addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
                             HistoryItem.EVENT_WAKE_LOCK_START, historyName, mappedUid);
                 }
             }
             if (mWakeLockNesting == 0) {
+                mHistoryCur.states |= HistoryItem.STATE_WAKE_LOCK_FLAG;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Start wake lock to: "
+                        + Integer.toHexString(mHistoryCur.states));
+                mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+                mHistoryCur.wakelockTag.string = historyName;
+                mHistoryCur.wakelockTag.uid = mappedUid;
                 mWakeLockImportant = !unimportantForLogging;
-                mHistory.recordWakelockStartEvent(elapsedRealtimeMs, uptimeMs, historyName,
-                        mappedUid);
-            } else if (!mWakeLockImportant && !unimportantForLogging) {
-                if (mHistory.maybeUpdateWakelockTag(elapsedRealtimeMs, uptimeMs, historyName,
-                        mappedUid)) {
-                    mWakeLockImportant = true;
+                addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+            } else if (!mWakeLockImportant && !unimportantForLogging
+                    && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE) {
+                if (mHistoryLastWritten.wakelockTag != null) {
+                    // We'll try to update the last tag.
+                    mHistoryLastWritten.wakelockTag = null;
+                    mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+                    mHistoryCur.wakelockTag.string = historyName;
+                    mHistoryCur.wakelockTag.uid = mappedUid;
+                    addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
                 }
+                mWakeLockImportant = true;
             }
             mWakeLockNesting++;
         }
@@ -4632,13 +5087,15 @@
                 }
                 if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName,
                         mappedUid, 0)) {
-                    mHistory.recordEvent(elapsedRealtimeMs, uptimeMs,
+                    addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
                             HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName, mappedUid);
                 }
             }
             if (mWakeLockNesting == 0) {
-                mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
-                        HistoryItem.STATE_WAKE_LOCK_FLAG);
+                mHistoryCur.states &= ~HistoryItem.STATE_WAKE_LOCK_FLAG;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Stop wake lock to: "
+                        + Integer.toHexString(mHistoryCur.states));
+                addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             }
         }
         if (mappedUid >= 0) {
@@ -4829,7 +5286,7 @@
                 mappedUid, 0)) {
             return;
         }
-        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_START,
+        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_START,
                 historyName, mappedUid);
         if (mappedUid != uid) {
             // Prevent the isolated uid mapping from being removed while the wakelock is
@@ -4882,7 +5339,7 @@
                 mappedUid, 0)) {
             return;
         }
-        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH,
+        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH,
                 historyName, mappedUid);
         if (mappedUid != uid) {
             // Decrement the ref count for the isolated uid and delete the mapping if uneeded.
@@ -4904,10 +5361,15 @@
 
     @GuardedBy("this")
     public void noteWakeupReasonLocked(String reason, long elapsedRealtimeMs, long uptimeMs) {
+        if (DEBUG_HISTORY) Slog.v(TAG, "Wakeup reason \"" + reason +"\": "
+                + Integer.toHexString(mHistoryCur.states));
         aggregateLastWakeupUptimeLocked(elapsedRealtimeMs, uptimeMs);
-        mHistory.recordWakeupEvent(elapsedRealtimeMs, uptimeMs, reason);
+        mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag;
+        mHistoryCur.wakeReasonTag.string = reason;
+        mHistoryCur.wakeReasonTag.uid = 0;
         mLastWakeupReason = reason;
         mLastWakeupUptimeMs = uptimeMs;
+        addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
     }
 
     @GuardedBy("this")
@@ -4918,11 +5380,22 @@
 
     @GuardedBy("this")
     public void finishAddingCpuLocked(int totalUTimeMs, int totalSTimeMs, int statUserTimeMs,
-            int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs,
-            int statSoftIrqTimeMs, int statIdleTimeMs) {
-        mStepDetailsCalculator.addCpuStats(totalUTimeMs, totalSTimeMs, statUserTimeMs,
-                statSystemTimeMs, statIOWaitTimeMs, statIrqTimeMs,
-                statSoftIrqTimeMs, statIdleTimeMs);
+                                      int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs,
+                                      int statSoftIrqTimeMs, int statIdleTimeMs) {
+        if (DEBUG) {
+            Slog.d(TAG, "Adding cpu: tuser=" + totalUTimeMs + " tsys=" + totalSTimeMs
+                    + " user=" + statUserTimeMs + " sys=" + statSystemTimeMs
+                    + " io=" + statIOWaitTimeMs + " irq=" + statIrqTimeMs
+                    + " sirq=" + statSoftIrqTimeMs + " idle=" + statIdleTimeMs);
+        }
+        mCurStepCpuUserTimeMs += totalUTimeMs;
+        mCurStepCpuSystemTimeMs += totalSTimeMs;
+        mCurStepStatUserTimeMs += statUserTimeMs;
+        mCurStepStatSystemTimeMs += statSystemTimeMs;
+        mCurStepStatIOWaitTimeMs += statIOWaitTimeMs;
+        mCurStepStatIrqTimeMs += statIrqTimeMs;
+        mCurStepStatSoftIrqTimeMs += statSoftIrqTimeMs;
+        mCurStepStatIdleTimeMs += statIdleTimeMs;
     }
 
     public void noteProcessDiedLocked(int uid, int pid) {
@@ -4952,8 +5425,10 @@
     public void noteStartSensorLocked(int uid, int sensor, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         if (mSensorNesting == 0) {
-            mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE_SENSOR_ON_FLAG);
+            mHistoryCur.states |= HistoryItem.STATE_SENSOR_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Start sensor to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
         }
         mSensorNesting++;
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -4970,8 +5445,10 @@
         uid = mapUid(uid);
         mSensorNesting--;
         if (mSensorNesting == 0) {
-            mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE_SENSOR_ON_FLAG);
+            mHistoryCur.states &= ~HistoryItem.STATE_SENSOR_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Stop sensor to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
                 .noteStopSensor(sensor, elapsedRealtimeMs);
@@ -5021,8 +5498,10 @@
         }
         final int mappedUid = mapUid(uid);
         if (mGpsNesting == 0) {
-            mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE_GPS_ON_FLAG);
+            mHistoryCur.states |= HistoryItem.STATE_GPS_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Start GPS to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
         }
         mGpsNesting++;
 
@@ -5047,8 +5526,10 @@
         final int mappedUid = mapUid(uid);
         mGpsNesting--;
         if (mGpsNesting == 0) {
-            mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE_GPS_ON_FLAG);
+            mHistoryCur.states &= ~HistoryItem.STATE_GPS_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Stop GPS to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             stopAllGpsSignalQualityTimersLocked(-1, elapsedRealtimeMs);
             mGpsSignalQualityBin = -1;
         }
@@ -5081,9 +5562,12 @@
             if(!mGpsSignalQualityTimer[signalLevel].isRunningLocked()) {
                 mGpsSignalQualityTimer[signalLevel].startRunningLocked(elapsedRealtimeMs);
             }
-            mHistory.recordGpsSignalQualityEvent(elapsedRealtimeMs, uptimeMs, signalLevel);
+            mHistoryCur.states2 = (mHistoryCur.states2&~HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
+                    | (signalLevel << HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT);
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mGpsSignalQualityBin = signalLevel;
         }
+        return;
     }
 
     @GuardedBy("this")
@@ -5256,33 +5740,41 @@
                 }
             }
 
-            int startStates = 0;
-            int stopStates = 0;
+            boolean updateHistory = false;
             if (Display.isDozeState(state) && !Display.isDozeState(oldState)) {
-                startStates |= HistoryItem.STATE_SCREEN_DOZE_FLAG;
+                mHistoryCur.states |= HistoryItem.STATE_SCREEN_DOZE_FLAG;
                 mScreenDozeTimer.startRunningLocked(elapsedRealtimeMs);
+                updateHistory = true;
             } else if (Display.isDozeState(oldState) && !Display.isDozeState(state)) {
-                stopStates |= HistoryItem.STATE_SCREEN_DOZE_FLAG;
+                mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_DOZE_FLAG;
                 mScreenDozeTimer.stopRunningLocked(elapsedRealtimeMs);
+                updateHistory = true;
             }
             if (Display.isOnState(state)) {
-                startStates |= HistoryItem.STATE_SCREEN_ON_FLAG;
+                mHistoryCur.states |= HistoryItem.STATE_SCREEN_ON_FLAG;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Screen on to: "
+                        + Integer.toHexString(mHistoryCur.states));
                 mScreenOnTimer.startRunningLocked(elapsedRealtimeMs);
                 if (mScreenBrightnessBin >= 0) {
                     mScreenBrightnessTimer[mScreenBrightnessBin]
                             .startRunningLocked(elapsedRealtimeMs);
                 }
+                updateHistory = true;
             } else if (Display.isOnState(oldState)) {
-                stopStates |= HistoryItem.STATE_SCREEN_ON_FLAG;
+                mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_ON_FLAG;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Screen off to: "
+                        + Integer.toHexString(mHistoryCur.states));
                 mScreenOnTimer.stopRunningLocked(elapsedRealtimeMs);
                 if (mScreenBrightnessBin >= 0) {
                     mScreenBrightnessTimer[mScreenBrightnessBin]
                             .stopRunningLocked(elapsedRealtimeMs);
                 }
+                updateHistory = true;
             }
-            if (startStates != 0 || stopStates != 0) {
-                mHistory.recordStateChangeEvent(elapsedRealtimeMs, uptimeMs, startStates,
-                        stopStates);
+            if (updateHistory) {
+                if (DEBUG_HISTORY) Slog.v(TAG, "Screen state to: "
+                        + Display.stateToString(state));
+                addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             }
 
             // Per screen state Cpu stats needed. Prepare to schedule an external sync.
@@ -5396,7 +5888,13 @@
             long uptimeMs) {
         if (mScreenBrightnessBin != overallBin) {
             if (overallBin >= 0) {
-                mHistory.recordScreenBrightnessEvent(elapsedRealtimeMs, uptimeMs, overallBin);
+                mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_BRIGHTNESS_MASK)
+                        | (overallBin << HistoryItem.STATE_BRIGHTNESS_SHIFT);
+                if (DEBUG_HISTORY) {
+                    Slog.v(TAG, "Screen brightness " + overallBin + " to: "
+                            + Integer.toHexString(mHistoryCur.states));
+                }
+                addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             }
             if (mScreenState == Display.STATE_ON) {
                 if (mScreenBrightnessBin >= 0) {
@@ -5413,8 +5911,7 @@
     }
 
     @GuardedBy("this")
-    public void noteUserActivityLocked(int uid, @PowerManager.UserActivityEvent int event,
-            long elapsedRealtimeMs, long uptimeMs) {
+    public void noteUserActivityLocked(int uid, int event, long elapsedRealtimeMs, long uptimeMs) {
         if (mOnBatteryInternal) {
             uid = mapUid(uid);
             getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs).noteUserActivityLocked(event);
@@ -5424,8 +5921,8 @@
     @GuardedBy("this")
     public void noteWakeUpLocked(String reason, int reasonUid,
             long elapsedRealtimeMs, long uptimeMs) {
-        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SCREEN_WAKE_UP, reason,
-                reasonUid);
+        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SCREEN_WAKE_UP,
+                reason, reasonUid);
     }
 
     @GuardedBy("this")
@@ -5444,7 +5941,7 @@
     @GuardedBy("this")
     public void noteConnectivityChangedLocked(int type, String extra,
             long elapsedRealtimeMs, long uptimeMs) {
-        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_CONNECTIVITY_CHANGED,
+        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_CONNECTIVITY_CHANGED,
                 extra, type);
         mNumConnectivityChange++;
     }
@@ -5453,7 +5950,7 @@
     private void noteMobileRadioApWakeupLocked(final long elapsedRealtimeMillis,
             final long uptimeMillis, int uid) {
         uid = mapUid(uid);
-        mHistory.recordEvent(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "",
+        addHistoryEventLocked(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "",
                 uid);
         getUidStatsLocked(uid, elapsedRealtimeMillis, uptimeMillis).noteMobileRadioApWakeupLocked();
     }
@@ -5479,8 +5976,7 @@
                 }
 
                 mMobileRadioActiveStartTimeMs = realElapsedRealtimeMs = timestampNs / (1000 * 1000);
-                mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
-                        HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG);
+                mHistoryCur.states |= HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
             } else {
                 realElapsedRealtimeMs = timestampNs / (1000*1000);
                 long lastUpdateTimeMs = mMobileRadioActiveStartTimeMs;
@@ -5492,9 +5988,11 @@
                     mMobileRadioActiveAdjustedTime.addCountLocked(elapsedRealtimeMs
                             - realElapsedRealtimeMs);
                 }
-                mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
-                        HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG);
+                mHistoryCur.states &= ~HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
             }
+            if (DEBUG_HISTORY) Slog.v(TAG, "Mobile network active " + active + " to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mMobileRadioPowerState = powerState;
 
             // Inform current RatBatteryStats that the modem active state might have changed.
@@ -5544,14 +6042,17 @@
             mCurStepMode = (mCurStepMode&~STEP_LEVEL_MODE_POWER_SAVE) | stepState;
             mPowerSaveModeEnabled = enabled;
             if (enabled) {
-                mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
-                        HistoryItem.STATE2_POWER_SAVE_FLAG);
+                mHistoryCur.states2 |= HistoryItem.STATE2_POWER_SAVE_FLAG;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Power save mode enabled to: "
+                        + Integer.toHexString(mHistoryCur.states2));
                 mPowerSaveModeEnabledTimer.startRunningLocked(elapsedRealtimeMs);
             } else {
-                mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
-                        HistoryItem.STATE2_POWER_SAVE_FLAG);
+                mHistoryCur.states2 &= ~HistoryItem.STATE2_POWER_SAVE_FLAG;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Power save mode disabled to: "
+                        + Integer.toHexString(mHistoryCur.states2));
                 mPowerSaveModeEnabledTimer.stopRunningLocked(elapsedRealtimeMs);
             }
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_SAVER_MODE_STATE_CHANGED,
                     enabled
                         ? FrameworkStatsLog.BATTERY_SAVER_MODE_STATE_CHANGED__STATE__ON
@@ -5575,7 +6076,7 @@
             nowLightIdling = true;
         }
         if (activeReason != null && (mDeviceIdling || mDeviceLightIdling)) {
-            mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_ACTIVE,
+            addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_ACTIVE,
                     activeReason, activeUid);
         }
         if (mDeviceIdling != nowIdling || mDeviceLightIdling != nowLightIdling) {
@@ -5605,7 +6106,11 @@
             }
         }
         if (mDeviceIdleMode != mode) {
-            mHistory.recordDeviceIdleEvent(elapsedRealtimeMs, uptimeMs, mode);
+            mHistoryCur.states2 = (mHistoryCur.states2 & ~HistoryItem.STATE2_DEVICE_IDLE_MASK)
+                    | (mode << HistoryItem.STATE2_DEVICE_IDLE_SHIFT);
+            if (DEBUG_HISTORY) Slog.v(TAG, "Device idle mode changed to: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             long lastDuration = elapsedRealtimeMs - mLastIdleTimeStartMs;
             mLastIdleTimeStartMs = elapsedRealtimeMs;
             if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) {
@@ -5633,7 +6138,7 @@
     public void notePackageInstalledLocked(String pkgName, long versionCode,
             long elapsedRealtimeMs, long uptimeMs) {
         // XXX need to figure out what to do with long version codes.
-        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PACKAGE_INSTALLED,
+        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PACKAGE_INSTALLED,
                 pkgName, (int)versionCode);
         PackageChange pc = new PackageChange();
         pc.mPackageName = pkgName;
@@ -5645,8 +6150,8 @@
     @GuardedBy("this")
     public void notePackageUninstalledLocked(String pkgName,
             long elapsedRealtimeMs, long uptimeMs) {
-        mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PACKAGE_UNINSTALLED,
-                pkgName, 0);
+        addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
+                HistoryItem.EVENT_PACKAGE_UNINSTALLED, pkgName, 0);
         PackageChange pc = new PackageChange();
         pc.mPackageName = pkgName;
         pc.mUpdate = true;
@@ -5675,8 +6180,10 @@
     @GuardedBy("this")
     public void notePhoneOnLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (!mPhoneOn) {
-            mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_PHONE_IN_CALL_FLAG);
+            mHistoryCur.states2 |= HistoryItem.STATE2_PHONE_IN_CALL_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Phone on to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mPhoneOn = true;
             mPhoneOnTimer.startRunningLocked(elapsedRealtimeMs);
         }
@@ -5685,8 +6192,10 @@
     @GuardedBy("this")
     public void notePhoneOffLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mPhoneOn) {
-            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_PHONE_IN_CALL_FLAG);
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_PHONE_IN_CALL_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Phone off to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mPhoneOn = false;
             mPhoneOnTimer.stopRunningLocked(elapsedRealtimeMs);
         }
@@ -5724,12 +6233,11 @@
         if (mUsbDataState != newState) {
             mUsbDataState = newState;
             if (connected) {
-                mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
-                        HistoryItem.STATE2_USB_DATA_LINK_FLAG);
+                mHistoryCur.states2 |= HistoryItem.STATE2_USB_DATA_LINK_FLAG;
             } else {
-                mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
-                        HistoryItem.STATE2_USB_DATA_LINK_FLAG);
+                mHistoryCur.states2 &= ~HistoryItem.STATE2_USB_DATA_LINK_FLAG;
             }
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
         }
     }
 
@@ -5750,10 +6258,6 @@
             long elapsedRealtimeMs, long uptimeMs) {
         boolean scanning = false;
         boolean newHistory = false;
-        int addStateFlag = 0;
-        int removeStateFlag = 0;
-        int newState = -1;
-        int newSignalStrength = -1;
 
         mPhoneServiceStateRaw = state;
         mPhoneSimStateRaw = simState;
@@ -5782,8 +6286,10 @@
             scanning = true;
             strengthBin = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
             if (!mPhoneSignalScanningTimer.isRunningLocked()) {
-                addStateFlag = HistoryItem.STATE_PHONE_SCANNING_FLAG;
+                mHistoryCur.states |= HistoryItem.STATE_PHONE_SCANNING_FLAG;
                 newHistory = true;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Phone started scanning to: "
+                        + Integer.toHexString(mHistoryCur.states));
                 mPhoneSignalScanningTimer.startRunningLocked(elapsedRealtimeMs);
                 FrameworkStatsLog.write(FrameworkStatsLog.PHONE_SERVICE_STATE_CHANGED, state,
                         simState, strengthBin);
@@ -5793,7 +6299,9 @@
         if (!scanning) {
             // If we are no longer scanning, then stop the scanning timer.
             if (mPhoneSignalScanningTimer.isRunningLocked()) {
-                removeStateFlag = HistoryItem.STATE_PHONE_SCANNING_FLAG;
+                mHistoryCur.states &= ~HistoryItem.STATE_PHONE_SCANNING_FLAG;
+                if (DEBUG_HISTORY) Slog.v(TAG, "Phone stopped scanning to: "
+                        + Integer.toHexString(mHistoryCur.states));
                 newHistory = true;
                 mPhoneSignalScanningTimer.stopRunningLocked(elapsedRealtimeMs);
                 FrameworkStatsLog.write(FrameworkStatsLog.PHONE_SERVICE_STATE_CHANGED, state,
@@ -5802,7 +6310,10 @@
         }
 
         if (mPhoneServiceState != state) {
-            newState = state;
+            mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_PHONE_STATE_MASK)
+                    | (state << HistoryItem.STATE_PHONE_STATE_SHIFT);
+            if (DEBUG_HISTORY) Slog.v(TAG, "Phone state " + state + " to: "
+                    + Integer.toHexString(mHistoryCur.states));
             newHistory = true;
             mPhoneServiceState = state;
         }
@@ -5816,7 +6327,11 @@
                 if (!mPhoneSignalStrengthsTimer[strengthBin].isRunningLocked()) {
                     mPhoneSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtimeMs);
                 }
-                newSignalStrength = strengthBin;
+                mHistoryCur.states =
+                        (mHistoryCur.states & ~HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
+                        | (strengthBin << HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT);
+                if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + strengthBin + " to: "
+                        + Integer.toHexString(mHistoryCur.states));
                 newHistory = true;
                 FrameworkStatsLog.write(
                         FrameworkStatsLog.PHONE_SIGNAL_STRENGTH_CHANGED, strengthBin);
@@ -5827,8 +6342,7 @@
         }
 
         if (newHistory) {
-            mHistory.recordPhoneStateChangeEvent(elapsedRealtimeMs, uptimeMs,
-                    addStateFlag, removeStateFlag, newState, newSignalStrength);
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
         }
     }
 
@@ -5952,7 +6466,11 @@
 
         if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData);
         if (mPhoneDataConnectionType != bin) {
-            mHistory.recordDataConnectionTypeChangeEvent(elapsedRealtimeMs, uptimeMs, bin);
+            mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_DATA_CONNECTION_MASK)
+                    | (bin << HistoryItem.STATE_DATA_CONNECTION_SHIFT);
+            if (DEBUG_HISTORY) Slog.v(TAG, "Data connection " + bin + " to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             if (mPhoneDataConnectionType >= 0) {
                 mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked(
                         elapsedRealtimeMs);
@@ -6025,8 +6543,10 @@
     @GuardedBy("this")
     public void noteWifiOnLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (!mWifiOn) {
-            mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_WIFI_ON_FLAG);
+            mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI on to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mWifiOn = true;
             mWifiOnTimer.startRunningLocked(elapsedRealtimeMs);
             scheduleSyncExternalStatsLocked("wifi-off", ExternalStatsSync.UPDATE_WIFI);
@@ -6036,8 +6556,10 @@
     @GuardedBy("this")
     public void noteWifiOffLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mWifiOn) {
-            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_WIFI_ON_FLAG);
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_WIFI_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI off to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mWifiOn = false;
             mWifiOnTimer.stopRunningLocked(elapsedRealtimeMs);
             scheduleSyncExternalStatsLocked("wifi-on", ExternalStatsSync.UPDATE_WIFI);
@@ -6048,8 +6570,10 @@
     public void noteAudioOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         if (mAudioOnNesting == 0) {
-            mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE_AUDIO_ON_FLAG);
+            mHistoryCur.states |= HistoryItem.STATE_AUDIO_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Audio on to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mAudioOnTimer.startRunningLocked(elapsedRealtimeMs);
         }
         mAudioOnNesting++;
@@ -6064,8 +6588,10 @@
         }
         uid = mapUid(uid);
         if (--mAudioOnNesting == 0) {
-            mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE_AUDIO_ON_FLAG);
+            mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mAudioOnTimer.stopRunningLocked(elapsedRealtimeMs);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6076,8 +6602,10 @@
     public void noteVideoOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         if (mVideoOnNesting == 0) {
-            mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_VIDEO_ON_FLAG);
+            mHistoryCur.states2 |= HistoryItem.STATE2_VIDEO_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Video on to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mVideoOnTimer.startRunningLocked(elapsedRealtimeMs);
         }
         mVideoOnNesting++;
@@ -6092,8 +6620,10 @@
         }
         uid = mapUid(uid);
         if (--mVideoOnNesting == 0) {
-            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_VIDEO_ON_FLAG);
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_VIDEO_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Video off to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mVideoOnTimer.stopRunningLocked(elapsedRealtimeMs);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6104,8 +6634,10 @@
     public void noteResetAudioLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mAudioOnNesting > 0) {
             mAudioOnNesting = 0;
-            mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE_AUDIO_ON_FLAG);
+            mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mAudioOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
             for (int i=0; i<mUidStats.size(); i++) {
                 BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6118,8 +6650,10 @@
     public void noteResetVideoLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mVideoOnNesting > 0) {
             mVideoOnNesting = 0;
-            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_VIDEO_ON_FLAG);
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_VIDEO_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Video off to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mVideoOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
             for (int i=0; i<mUidStats.size(); i++) {
                 BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6171,8 +6705,10 @@
     public void noteFlashlightOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         if (mFlashlightOnNesting++ == 0) {
-            mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_FLASHLIGHT_FLAG);
+            mHistoryCur.states2 |= HistoryItem.STATE2_FLASHLIGHT_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Flashlight on to: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mFlashlightOnTimer.startRunningLocked(elapsedRealtimeMs);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6186,8 +6722,10 @@
         }
         uid = mapUid(uid);
         if (--mFlashlightOnNesting == 0) {
-            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_FLASHLIGHT_FLAG);
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_FLASHLIGHT_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Flashlight off to: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mFlashlightOnTimer.stopRunningLocked(elapsedRealtimeMs);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6198,8 +6736,10 @@
     public void noteCameraOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         if (mCameraOnNesting++ == 0) {
-            mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_CAMERA_FLAG);
+            mHistoryCur.states2 |= HistoryItem.STATE2_CAMERA_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Camera on to: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mCameraOnTimer.startRunningLocked(elapsedRealtimeMs);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6213,8 +6753,10 @@
         }
         uid = mapUid(uid);
         if (--mCameraOnNesting == 0) {
-            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_CAMERA_FLAG);
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_CAMERA_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Camera off to: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mCameraOnTimer.stopRunningLocked(elapsedRealtimeMs);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6225,8 +6767,10 @@
     public void noteResetCameraLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mCameraOnNesting > 0) {
             mCameraOnNesting = 0;
-            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_CAMERA_FLAG);
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_CAMERA_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Camera off to: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mCameraOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
             for (int i=0; i<mUidStats.size(); i++) {
                 BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6239,8 +6783,10 @@
     public void noteResetFlashlightLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mFlashlightOnNesting > 0) {
             mFlashlightOnNesting = 0;
-            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_FLASHLIGHT_FLAG);
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_FLASHLIGHT_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Flashlight off to: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mFlashlightOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
             for (int i=0; i<mUidStats.size(); i++) {
                 BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6257,8 +6803,10 @@
         }
         uid = mapUid(uid);
         if (mBluetoothScanNesting == 0) {
-            mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG);
+            mHistoryCur.states2 |= HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "BLE scan started for: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mBluetoothScanTimer.startRunningLocked(elapsedRealtimeMs);
         }
         mBluetoothScanNesting++;
@@ -6299,8 +6847,10 @@
         uid = mapUid(uid);
         mBluetoothScanNesting--;
         if (mBluetoothScanNesting == 0) {
-            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG);
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "BLE scan stopped for: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mBluetoothScanTimer.stopRunningLocked(elapsedRealtimeMs);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6335,8 +6885,10 @@
     public void noteResetBluetoothScanLocked(long elapsedRealtimeMs, long uptimeMs) {
         if (mBluetoothScanNesting > 0) {
             mBluetoothScanNesting = 0;
-            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG);
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "BLE can stopped for: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mBluetoothScanTimer.stopAllRunningLocked(elapsedRealtimeMs);
             for (int i=0; i<mUidStats.size(); i++) {
                 BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6376,7 +6928,7 @@
     private void noteWifiRadioApWakeupLocked(final long elapsedRealtimeMillis,
             final long uptimeMillis, int uid) {
         uid = mapUid(uid);
-        mHistory.recordEvent(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "",
+        addHistoryEventLocked(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "",
                 uid);
         getUidStatsLocked(uid, elapsedRealtimeMillis, uptimeMillis).noteWifiRadioApWakeupLocked();
     }
@@ -6392,14 +6944,15 @@
                 if (uid > 0) {
                     noteWifiRadioApWakeupLocked(elapsedRealtimeMs, uptimeMs, uid);
                 }
-                mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
-                        HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG);
+                mHistoryCur.states |= HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG;
                 mWifiActiveTimer.startRunningLocked(elapsedRealtimeMs);
             } else {
-                mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
-                        HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG);
+                mHistoryCur.states &= ~HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG;
                 mWifiActiveTimer.stopRunningLocked(timestampNs / (1000 * 1000));
             }
+            if (DEBUG_HISTORY) Slog.v(TAG, "Wifi network active " + active + " to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mWifiRadioPowerState = powerState;
         }
     }
@@ -6407,8 +6960,10 @@
     @GuardedBy("this")
     public void noteWifiRunningLocked(WorkSource ws, long elapsedRealtimeMs, long uptimeMs) {
         if (!mGlobalWifiRunning) {
-            mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_WIFI_RUNNING_FLAG);
+            mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_RUNNING_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI running to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mGlobalWifiRunning = true;
             mGlobalWifiRunningTimer.startRunningLocked(elapsedRealtimeMs);
             int N = ws.size();
@@ -6476,8 +7031,10 @@
     @GuardedBy("this")
     public void noteWifiStoppedLocked(WorkSource ws, long elapsedRealtimeMs, long uptimeMs) {
         if (mGlobalWifiRunning) {
-            mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_WIFI_RUNNING_FLAG);
+            mHistoryCur.states2 &= ~HistoryItem.STATE2_WIFI_RUNNING_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI stopped to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             mGlobalWifiRunning = false;
             mGlobalWifiRunningTimer.stopRunningLocked(elapsedRealtimeMs);
             int N = ws.size();
@@ -6525,7 +7082,12 @@
             }
             mWifiSupplState = supplState;
             mWifiSupplStateTimer[supplState].startRunningLocked(elapsedRealtimeMs);
-            mHistory.recordWifiSupplicantStateChangeEvent(elapsedRealtimeMs, uptimeMs, supplState);
+            mHistoryCur.states2 =
+                    (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
+                    | (supplState << HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
+            if (DEBUG_HISTORY) Slog.v(TAG, "Wifi suppl state " + supplState + " to: "
+                    + Integer.toHexString(mHistoryCur.states2));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
         }
     }
 
@@ -6554,8 +7116,12 @@
                 if (!mWifiSignalStrengthsTimer[strengthBin].isRunningLocked()) {
                     mWifiSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtimeMs);
                 }
-                mHistory.recordWifiSignalStrengthChangeEvent(elapsedRealtimeMs, uptimeMs,
-                        strengthBin);
+                mHistoryCur.states2 =
+                        (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK)
+                        | (strengthBin << HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT);
+                if (DEBUG_HISTORY) Slog.v(TAG, "Wifi signal strength " + strengthBin + " to: "
+                        + Integer.toHexString(mHistoryCur.states2));
+                addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             } else {
                 stopAllWifiSignalStrengthTimersLocked(-1, elapsedRealtimeMs);
             }
@@ -6568,8 +7134,10 @@
     @GuardedBy("this")
     public void noteFullWifiLockAcquiredLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         if (mWifiFullLockNesting == 0) {
-            mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE_WIFI_FULL_LOCK_FLAG);
+            mHistoryCur.states |= HistoryItem.STATE_WIFI_FULL_LOCK_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock on to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
         }
         mWifiFullLockNesting++;
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6580,8 +7148,10 @@
     public void noteFullWifiLockReleasedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         mWifiFullLockNesting--;
         if (mWifiFullLockNesting == 0) {
-            mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE_WIFI_FULL_LOCK_FLAG);
+            mHistoryCur.states &= ~HistoryItem.STATE_WIFI_FULL_LOCK_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock off to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
                 .noteFullWifiLockReleasedLocked(elapsedRealtimeMs);
@@ -6597,8 +7167,10 @@
     @GuardedBy("this")
     public void noteWifiScanStartedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         if (mWifiScanNesting == 0) {
-            mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE_WIFI_SCAN_FLAG);
+            mHistoryCur.states |= HistoryItem.STATE_WIFI_SCAN_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan started for: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
         }
         mWifiScanNesting++;
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6614,8 +7186,10 @@
     public void noteWifiScanStoppedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         mWifiScanNesting--;
         if (mWifiScanNesting == 0) {
-            mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE_WIFI_SCAN_FLAG);
+            mHistoryCur.states &= ~HistoryItem.STATE_WIFI_SCAN_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan stopped for: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
                 .noteWifiScanStoppedLocked(elapsedRealtimeMs);
@@ -6640,10 +7214,14 @@
     public void noteWifiMulticastEnabledLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
         uid = mapUid(uid);
         if (mWifiMulticastNesting == 0) {
-            mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG);
+            mHistoryCur.states |= HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+
             // Start Wifi Multicast overall timer
             if (!mWifiMulticastWakelockTimer.isRunningLocked()) {
+                if (DEBUG_HISTORY) Slog.v(TAG, "WiFi Multicast Overall Timer Started");
                 mWifiMulticastWakelockTimer.startRunningLocked(elapsedRealtimeMs);
             }
         }
@@ -6657,12 +7235,14 @@
         uid = mapUid(uid);
         mWifiMulticastNesting--;
         if (mWifiMulticastNesting == 0) {
-            mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG);
+            mHistoryCur.states &= ~HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
 
             // Stop Wifi Multicast overall timer
             if (mWifiMulticastWakelockTimer.isRunningLocked()) {
-                if (DEBUG) Slog.v(TAG, "Multicast Overall Timer Stopped");
+                if (DEBUG_HISTORY) Slog.v(TAG, "Multicast Overall Timer Stopped");
                 mWifiMulticastWakelockTimer.stopRunningLocked(elapsedRealtimeMs);
             }
         }
@@ -7414,9 +7994,8 @@
             // If the start clock time has changed by more than a year, then presumably
             // the previous time was completely bogus.  So we are going to figure out a
             // new time based on how much time has elapsed since we started counting.
-            mHistory.recordCurrentTimeChange(mClock.elapsedRealtime(), mClock.uptimeMillis(),
-                    currentTimeMs
-            );
+            recordCurrentTimeChangeLocked(currentTimeMs, mClock.elapsedRealtime(),
+                    mClock.uptimeMillis());
             return currentTimeMs - (mClock.elapsedRealtime() - (mRealtimeStartUs / 1000));
         }
         return mStartClockTimeMs;
@@ -8837,14 +9416,14 @@
         }
 
         @Override
-        public void noteUserActivityLocked(@PowerManager.UserActivityEvent int event) {
+        public void noteUserActivityLocked(int type) {
             if (mUserActivityCounters == null) {
                 initUserActivityLocked();
             }
-            if (event >= 0 && event < NUM_USER_ACTIVITY_TYPES) {
-                mUserActivityCounters[event].stepAtomic();
+            if (type >= 0 && type < NUM_USER_ACTIVITY_TYPES) {
+                mUserActivityCounters[type].stepAtomic();
             } else {
-                Slog.w(TAG, "Unknown user activity event " + event + " was specified.",
+                Slog.w(TAG, "Unknown user activity type " + type + " was specified.",
                         new Throwable());
             }
         }
@@ -10648,19 +11227,18 @@
             UserInfoProvider userInfoProvider) {
         init(clock);
 
-        mHandler = new MyHandler(handler.getLooper());
-        mConstants = new Constants(mHandler);
-
         if (systemDir == null) {
             mStatsFile = null;
-            mHistory = new BatteryStatsHistory(mStepDetailsCalculator, mClock);
+            mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer);
         } else {
             mStatsFile = new AtomicFile(new File(systemDir, "batterystats.bin"));
-            mHistory = new BatteryStatsHistory(systemDir, mConstants.MAX_HISTORY_FILES,
-                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+            mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer, systemDir,
+                    this::getMaxHistoryFiles);
         }
         mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
         mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
+        mHandler = new MyHandler(handler.getLooper());
+        mConstants = new Constants(mHandler);
         mStartCount++;
         initTimersAndCounters();
         mOnBattery = mOnBatteryInternal = false;
@@ -10669,6 +11247,7 @@
         initTimes(uptimeUs, realtimeUs);
         mStartPlatformVersion = mEndPlatformVersion = Build.ID;
         initDischarge(realtimeUs);
+        clearHistoryLocked();
         updateDailyDeadlineLocked();
         mPlatformIdleStateCallback = cb;
         mMeasuredEnergyRetriever = energyStatsCb;
@@ -10679,6 +11258,12 @@
         FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mDeviceIdleMode);
     }
 
+    private int getMaxHistoryFiles() {
+        synchronized (this) {
+            return mConstants.MAX_HISTORY_FILES;
+        }
+    }
+
     @VisibleForTesting
     protected void initTimersAndCounters() {
         mScreenOnTimer = new StopwatchTimer(mClock, null, -1, null, mOnBatteryTimeBase);
@@ -10760,7 +11345,7 @@
         mDischargeUnplugLevel = 0;
         mDischargePlugLevel = -1;
         mDischargeCurrentLevel = 0;
-        mBatteryLevel = 0;
+        mCurrentBatteryLevel = 0;
     }
 
     public void setPowerProfileLocked(PowerProfile profile) {
@@ -11147,7 +11732,7 @@
     }
 
     public int getHistoryUsedSize() {
-        return mHistory.getHistoryUsedSize();
+        return mBatteryStatsHistory.getHistoryUsedSize();
     }
 
     @Override
@@ -11161,27 +11746,43 @@
      */
     @VisibleForTesting
     public BatteryStatsHistoryIterator createBatteryStatsHistoryIterator() {
-        return mHistory.iterate();
+        return new BatteryStatsHistoryIterator(mBatteryStatsHistory);
     }
 
     @Override
     public int getHistoryStringPoolSize() {
-        return mHistory.getHistoryStringPoolSize();
+        return mHistoryTagPool.size();
     }
 
     @Override
     public int getHistoryStringPoolBytes() {
-        return mHistory.getHistoryStringPoolBytes();
+        return mNumHistoryTagChars;
     }
 
     @Override
     public String getHistoryTagPoolString(int index) {
-        return mHistory.getHistoryTagPoolString(index);
+        ensureHistoryTagArray();
+        HistoryTag historyTag = mHistoryTags.get(index);
+        return historyTag != null ? historyTag.string : null;
     }
 
     @Override
     public int getHistoryTagPoolUid(int index) {
-        return mHistory.getHistoryTagPoolUid(index);
+        ensureHistoryTagArray();
+        HistoryTag historyTag = mHistoryTags.get(index);
+        return historyTag != null ? historyTag.uid : Process.INVALID_UID;
+    }
+
+    private void ensureHistoryTagArray() {
+        if (mHistoryTags != null) {
+            return;
+        }
+
+        mHistoryTags = new SparseArray<>(mHistoryTagPool.size());
+        for (Map.Entry<HistoryTag, Integer> entry: mHistoryTagPool.entrySet()) {
+            mHistoryTags.put(entry.getValue() & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG,
+                    entry.getKey());
+        }
     }
 
     @Override
@@ -11191,11 +11792,15 @@
 
     @Override
     public void finishIteratingHistoryLocked() {
-        mBatteryStatsHistoryIterator.close();
         mBatteryStatsHistoryIterator = null;
     }
 
     @Override
+    public long getHistoryBaseTime() {
+        return mHistoryBaseTimeMs;
+    }
+
+    @Override
     public int getStartCount() {
         return mStartCount;
     }
@@ -11248,23 +11853,24 @@
         long realtimeUs = mSecRealtime * 1000;
         resetAllStatsLocked(mSecUptime, mSecRealtime, RESET_REASON_ADB_COMMAND);
         pullPendingStateUpdatesLocked();
-        mHistory.writeHistoryItem(mSecRealtime, mSecUptime);
-        mDischargeCurrentLevel = mDischargeUnplugLevel = mDischargePlugLevel = mBatteryLevel;
+        addHistoryRecordLocked(mSecRealtime, mSecUptime);
+        mDischargeCurrentLevel = mDischargeUnplugLevel = mDischargePlugLevel
+                = mCurrentBatteryLevel = mHistoryCur.batteryLevel;
         mOnBatteryTimeBase.reset(uptimeUs, realtimeUs);
         mOnBatteryScreenOffTimeBase.reset(uptimeUs, realtimeUs);
-        if (!mBatteryPluggedIn) {
+        if ((mHistoryCur.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) == 0) {
             if (Display.isOnState(mScreenState)) {
-                mDischargeScreenOnUnplugLevel = mBatteryLevel;
+                mDischargeScreenOnUnplugLevel = mHistoryCur.batteryLevel;
                 mDischargeScreenDozeUnplugLevel = 0;
                 mDischargeScreenOffUnplugLevel = 0;
             } else if (Display.isDozeState(mScreenState)) {
                 mDischargeScreenOnUnplugLevel = 0;
-                mDischargeScreenDozeUnplugLevel = mBatteryLevel;
+                mDischargeScreenDozeUnplugLevel = mHistoryCur.batteryLevel;
                 mDischargeScreenOffUnplugLevel = 0;
             } else {
                 mDischargeScreenOnUnplugLevel = 0;
                 mDischargeScreenDozeUnplugLevel = 0;
-                mDischargeScreenOffUnplugLevel = mBatteryLevel;
+                mDischargeScreenOffUnplugLevel = mHistoryCur.batteryLevel;
             }
             mDischargeAmountScreenOn = 0;
             mDischargeAmountScreenOff = 0;
@@ -11408,12 +12014,27 @@
 
         resetIfNotNull(mBinderThreadCpuTimesUs, false, elapsedRealtimeUs);
 
+        mLastHistoryStepDetails = null;
+        mLastStepCpuUserTimeMs = mLastStepCpuSystemTimeMs = 0;
+        mCurStepCpuUserTimeMs = mCurStepCpuSystemTimeMs = 0;
+        mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs = 0;
+        mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs = 0;
+        mLastStepStatUserTimeMs = mCurStepStatUserTimeMs = 0;
+        mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs = 0;
+        mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs = 0;
+        mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs = 0;
+        mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs = 0;
+        mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs = 0;
+
         mNumAllUidCpuTimeReads = 0;
         mNumUidsRemoved = 0;
 
         initDischarge(elapsedRealtimeUs);
 
-        mHistory.reset();
+        clearHistoryLocked();
+        if (mBatteryStatsHistory != null) {
+            mBatteryStatsHistory.resetAllFiles();
+        }
 
         // Flush external data, gathering snapshots, but don't process it since it is pre-reset data
         mIgnoreNextExternalStats = true;
@@ -11436,7 +12057,7 @@
             for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
                 SparseIntArray uids = ent.getValue();
                 for (int j=0; j<uids.size(); j++) {
-                    mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, i, ent.getKey(),
+                    addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, i, ent.getKey(),
                             uids.keyAt(j));
                 }
             }
@@ -11861,8 +12482,9 @@
                         (long) (mTmpRailStats.getWifiTotalEnergyUseduWs() / opVolt);
                 mWifiActivity.getMonitoredRailChargeConsumedMaMs().addCountLocked(
                         monitoredRailChargeConsumedMaMs);
-                mHistory.recordWifiConsumedCharge(elapsedRealtimeMs, uptimeMs,
-                        (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR));
+                mHistoryCur.wifiRailChargeMah +=
+                        (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR);
+                addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
                 mTmpRailStats.resetWifiTotalEnergyUsed();
 
                 if (uidEstimatedConsumptionMah != null) {
@@ -11975,8 +12597,9 @@
                             (long) (mTmpRailStats.getCellularTotalEnergyUseduWs() / opVolt);
                     mModemActivity.getMonitoredRailChargeConsumedMaMs().addCountLocked(
                             monitoredRailChargeConsumedMaMs);
-                    mHistory.recordWifiConsumedCharge(elapsedRealtimeMs, uptimeMs,
-                            (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR));
+                    mHistoryCur.modemRailChargeMah +=
+                            (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR);
+                    addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
                     mTmpRailStats.resetCellularTotalEnergyUsed();
                 }
 
@@ -12244,8 +12867,8 @@
             }
         }
         if (levelMaxTimeSpent == ModemActivityInfo.getNumTxPowerLevels() - 1) {
-            mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG);
+            mHistoryCur.states2 |= HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG;
+            addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
         }
     }
 
@@ -13678,7 +14301,11 @@
         mHandler.removeCallbacks(mDeferSetCharging);
         if (mCharging != charging) {
             mCharging = charging;
-            mHistory.setChargingState(charging);
+            if (charging) {
+                mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG;
+            } else {
+                mHistoryCur.states2 &= ~HistoryItem.STATE2_CHARGING_FLAG;
+            }
             mHandler.sendEmptyMessage(MSG_REPORT_CHARGING);
             return true;
         }
@@ -13692,15 +14319,6 @@
         mSystemReady = true;
     }
 
-    /**
-     * Force recording of all history events regardless of the "charging" state.
-     */
-    @VisibleForTesting
-    public void forceRecordAllHistory() {
-        mHistory.forceRecordAllHistory();
-        mRecordAllHistory = true;
-    }
-
     @GuardedBy("this")
     protected void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime,
             final boolean onBattery, final int oldStatus, final int level, final int chargeUah) {
@@ -13784,12 +14402,15 @@
             mInitStepMode = mCurStepMode;
             mModStepMode = 0;
             pullPendingStateUpdatesLocked();
+            mHistoryCur.batteryLevel = (byte)level;
+            mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Battery unplugged to: "
+                    + Integer.toHexString(mHistoryCur.states));
             if (reset) {
-                mHistory.startRecordingHistory(mSecRealtime, mSecUptime, reset);
-                initActiveHistoryEventsLocked(mSecRealtime, mSecUptime);
+                mRecordingHistory = true;
+                startRecordingHistory(mSecRealtime, mSecUptime, reset);
             }
-            mBatteryPluggedIn = false;
-            mHistory.recordBatteryState(mSecRealtime, mSecUptime, level, mBatteryPluggedIn);
+            addHistoryRecordLocked(mSecRealtime, mSecUptime);
             mDischargeCurrentLevel = mDischargeUnplugLevel = level;
             if (Display.isOnState(screenState)) {
                 mDischargeScreenOnUnplugLevel = level;
@@ -13811,8 +14432,11 @@
         } else {
             mOnBattery = mOnBatteryInternal = false;
             pullPendingStateUpdatesLocked();
-            mBatteryPluggedIn = true;
-            mHistory.recordBatteryState(mSecRealtime, mSecUptime, level, mBatteryPluggedIn);
+            mHistoryCur.batteryLevel = (byte)level;
+            mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+            if (DEBUG_HISTORY) Slog.v(TAG, "Battery plugged to: "
+                    + Integer.toHexString(mHistoryCur.states));
+            addHistoryRecordLocked(mSecRealtime, mSecUptime);
             mDischargeCurrentLevel = mDischargePlugLevel = level;
             if (level < mDischargeUnplugLevel) {
                 mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1;
@@ -13827,12 +14451,45 @@
             mModStepMode = 0;
         }
         if (doWrite || (mLastWriteTimeMs + (60 * 1000)) < mSecRealtime) {
-            if (mStatsFile != null && !mHistory.isReadOnly()) {
+            if (mStatsFile != null && mBatteryStatsHistory.getActiveFile() != null) {
                 writeAsyncLocked();
             }
         }
     }
 
+    @GuardedBy("this")
+    private void startRecordingHistory(final long elapsedRealtimeMs, final long uptimeMs,
+            boolean reset) {
+        mRecordingHistory = true;
+        mHistoryCur.currentTime = mClock.currentTimeMillis();
+        addHistoryBufferLocked(elapsedRealtimeMs,
+                reset ? HistoryItem.CMD_RESET : HistoryItem.CMD_CURRENT_TIME,
+                mHistoryCur);
+        mHistoryCur.currentTime = 0;
+        if (reset) {
+            initActiveHistoryEventsLocked(elapsedRealtimeMs, uptimeMs);
+        }
+    }
+
+    @GuardedBy("this")
+    private void recordCurrentTimeChangeLocked(final long currentTimeMs,
+            final long elapsedRealtimeMs, final long uptimeMs) {
+        if (mRecordingHistory) {
+            mHistoryCur.currentTime = currentTimeMs;
+            addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_CURRENT_TIME, mHistoryCur);
+            mHistoryCur.currentTime = 0;
+        }
+    }
+
+    @GuardedBy("this")
+    private void recordShutdownLocked(final long currentTimeMs, final long elapsedRealtimeMs) {
+        if (mRecordingHistory) {
+            mHistoryCur.currentTime = currentTimeMs;
+            addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_SHUTDOWN, mHistoryCur);
+            mHistoryCur.currentTime = 0;
+        }
+    }
+
     private void scheduleSyncExternalStatsLocked(String reason, int updateFlags) {
         if (mExternalSync != null) {
             mExternalSync.scheduleSync(reason, updateFlags);
@@ -13850,7 +14507,8 @@
         // Temperature is encoded without the signed bit, so clamp any negative temperatures to 0.
         temp = Math.max(0, temp);
 
-        reportChangesToStatsLog(status, plugType, level);
+        reportChangesToStatsLog(mHaveBatteryLevel ? mHistoryCur : null,
+                status, plugType, level);
 
         final boolean onBattery = isOnBattery(plugType, status);
         if (!mHaveBatteryLevel) {
@@ -13860,47 +14518,52 @@
             // plugged in, then twiddle our state to correctly reflect that
             // since we won't be going through the full setOnBattery().
             if (onBattery == mOnBattery) {
-                mHistory.setPluggedInState(!onBattery);
+                if (onBattery) {
+                    mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+                } else {
+                    mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+                }
             }
-            mBatteryStatus = status;
-            mBatteryLevel = level;
-            mBatteryChargeUah = chargeUah;
-
             // Always start out assuming charging, that will be updated later.
-            mHistory.setBatteryState(true /* charging */, status, level, chargeUah);
-
+            mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG;
+            mHistoryCur.batteryStatus = (byte)status;
+            mHistoryCur.batteryLevel = (byte)level;
+            mHistoryCur.batteryChargeUah = chargeUah;
             mMaxChargeStepLevel = mMinDischargeStepLevel =
                     mLastChargeStepLevel = mLastDischargeStepLevel = level;
-        } else if (mBatteryLevel != level || mOnBattery != onBattery) {
+        } else if (mCurrentBatteryLevel != level || mOnBattery != onBattery) {
             recordDailyStatsIfNeededLocked(level >= 100 && onBattery, currentTimeMs);
         }
-        int oldStatus = mBatteryStatus;
+        int oldStatus = mHistoryCur.batteryStatus;
         if (onBattery) {
             mDischargeCurrentLevel = level;
-            if (!mHistory.isRecordingHistory()) {
-                mHistory.startRecordingHistory(elapsedRealtimeMs, uptimeMs, true);
+            if (!mRecordingHistory) {
+                mRecordingHistory = true;
+                startRecordingHistory(elapsedRealtimeMs, uptimeMs, true);
             }
         } else if (level < 96 &&
                 status != BatteryManager.BATTERY_STATUS_UNKNOWN) {
-            if (!mHistory.isRecordingHistory()) {
-                mHistory.startRecordingHistory(elapsedRealtimeMs, uptimeMs, true);
+            if (!mRecordingHistory) {
+                mRecordingHistory = true;
+                startRecordingHistory(elapsedRealtimeMs, uptimeMs, true);
             }
         }
+        mBatteryVoltageMv = voltageMv;
+        mCurrentBatteryLevel = level;
         if (mDischargePlugLevel < 0) {
             mDischargePlugLevel = level;
         }
 
         if (onBattery != mOnBattery) {
-            mBatteryLevel = level;
-            mBatteryStatus = status;
-            mBatteryHealth = health;
-            mBatteryPlugType = plugType;
-            mBatteryTemperature = temp;
-            mBatteryVoltageMv = voltageMv;
-            mHistory.setBatteryState(status, level, health, plugType, temp, voltageMv, chargeUah);
-            if (chargeUah < mBatteryChargeUah) {
+            mHistoryCur.batteryLevel = (byte)level;
+            mHistoryCur.batteryStatus = (byte)status;
+            mHistoryCur.batteryHealth = (byte)health;
+            mHistoryCur.batteryPlugType = (byte)plugType;
+            mHistoryCur.batteryTemperature = (short)temp;
+            mHistoryCur.batteryVoltage = (char) voltageMv;
+            if (chargeUah < mHistoryCur.batteryChargeUah) {
                 // Only record discharges
-                final long chargeDiff = (long) mBatteryChargeUah - chargeUah;
+                final long chargeDiff = mHistoryCur.batteryChargeUah - chargeUah;
                 mDischargeCounter.addCountLocked(chargeDiff);
                 mDischargeScreenOffCounter.addCountLocked(chargeDiff);
                 if (Display.isDozeState(mScreenState)) {
@@ -13912,12 +14575,12 @@
                     mDischargeDeepDozeCounter.addCountLocked(chargeDiff);
                 }
             }
-            mBatteryChargeUah = chargeUah;
+            mHistoryCur.batteryChargeUah = chargeUah;
             setOnBatteryLocked(elapsedRealtimeMs, uptimeMs, onBattery, oldStatus, level, chargeUah);
         } else {
             boolean changed = false;
-            if (mBatteryLevel != level) {
-                mBatteryLevel = level;
+            if (mHistoryCur.batteryLevel != level) {
+                mHistoryCur.batteryLevel = (byte)level;
                 changed = true;
 
                 // TODO(adamlesinski): Schedule the creation of a HistoryStepDetails record
@@ -13925,33 +14588,33 @@
                 mExternalSync.scheduleSyncDueToBatteryLevelChange(
                         mConstants.BATTERY_LEVEL_COLLECTION_DELAY_MS);
             }
-            if (mBatteryStatus != status) {
-                mBatteryStatus = status;
+            if (mHistoryCur.batteryStatus != status) {
+                mHistoryCur.batteryStatus = (byte)status;
                 changed = true;
             }
-            if (mBatteryHealth != health) {
-                mBatteryHealth = health;
+            if (mHistoryCur.batteryHealth != health) {
+                mHistoryCur.batteryHealth = (byte)health;
                 changed = true;
             }
-            if (mBatteryPlugType != plugType) {
-                mBatteryPlugType = plugType;
+            if (mHistoryCur.batteryPlugType != plugType) {
+                mHistoryCur.batteryPlugType = (byte)plugType;
                 changed = true;
             }
-            if (temp >= (mBatteryTemperature + 10)
-                    || temp <= (mBatteryTemperature - 10)) {
-                mBatteryTemperature = temp;
+            if (temp >= (mHistoryCur.batteryTemperature+10)
+                    || temp <= (mHistoryCur.batteryTemperature-10)) {
+                mHistoryCur.batteryTemperature = (short)temp;
                 changed = true;
             }
-            if (voltageMv > (mBatteryVoltageMv + 20)
-                    || voltageMv < (mBatteryVoltageMv - 20)) {
-                mBatteryVoltageMv = voltageMv;
+            if (voltageMv > (mHistoryCur.batteryVoltage + 20)
+                    || voltageMv < (mHistoryCur.batteryVoltage - 20)) {
+                mHistoryCur.batteryVoltage = (char) voltageMv;
                 changed = true;
             }
-            if (chargeUah >= (mBatteryChargeUah + 10)
-                    || chargeUah <= (mBatteryChargeUah - 10)) {
-                if (chargeUah < mBatteryChargeUah) {
+            if (chargeUah >= (mHistoryCur.batteryChargeUah + 10)
+                    || chargeUah <= (mHistoryCur.batteryChargeUah - 10)) {
+                if (chargeUah < mHistoryCur.batteryChargeUah) {
                     // Only record discharges
-                    final long chargeDiff = (long) mBatteryChargeUah - chargeUah;
+                    final long chargeDiff = mHistoryCur.batteryChargeUah - chargeUah;
                     mDischargeCounter.addCountLocked(chargeDiff);
                     mDischargeScreenOffCounter.addCountLocked(chargeDiff);
                     if (Display.isDozeState(mScreenState)) {
@@ -13963,10 +14626,9 @@
                         mDischargeDeepDozeCounter.addCountLocked(chargeDiff);
                     }
                 }
-                mBatteryChargeUah = chargeUah;
+                mHistoryCur.batteryChargeUah = chargeUah;
                 changed = true;
             }
-
             long modeBits = (((long)mInitStepMode) << STEP_LEVEL_INITIAL_MODE_SHIFT)
                     | (((long)mModStepMode) << STEP_LEVEL_MODIFIED_MODE_SHIFT)
                     | (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT);
@@ -14024,10 +14686,7 @@
                 mLastChargeStepLevel = level;
             }
             if (changed) {
-                mHistory.setBatteryState(mBatteryStatus, mBatteryLevel, mBatteryHealth,
-                        mBatteryPlugType, mBatteryTemperature, mBatteryVoltageMv,
-                        mBatteryChargeUah);
-                mHistory.writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+                addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
             }
         }
         if (!onBattery &&
@@ -14036,7 +14695,7 @@
             // We don't record history while we are plugged in and fully charged
             // (or when battery is not present).  The next time we are
             // unplugged, history will be cleared.
-            mHistory.setHistoryRecordingEnabled(DEBUG);
+            mRecordingHistory = DEBUG;
         }
 
         mLastLearnedBatteryCapacityUah = chargeFullUah;
@@ -14055,18 +14714,17 @@
     }
 
     // Inform StatsLog of setBatteryState changes.
-    private void reportChangesToStatsLog(final int status, final int plugType, final int level) {
-        if (!mHaveBatteryLevel) {
-            return;
-        }
+    // If this is the first reporting, pass in recentPast == null.
+    private void reportChangesToStatsLog(HistoryItem recentPast,
+            final int status, final int plugType, final int level) {
 
-        if (mBatteryStatus != status) {
+        if (recentPast == null || recentPast.batteryStatus != status) {
             FrameworkStatsLog.write(FrameworkStatsLog.CHARGING_STATE_CHANGED, status);
         }
-        if (mBatteryPlugType != plugType) {
+        if (recentPast == null || recentPast.batteryPlugType != plugType) {
             FrameworkStatsLog.write(FrameworkStatsLog.PLUGGED_STATE_CHANGED, plugType);
         }
-        if (mBatteryLevel != level) {
+        if (recentPast == null || recentPast.batteryLevel != level) {
             FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_LEVEL_CHANGED, level);
         }
     }
@@ -14136,7 +14794,7 @@
         if (msPerLevel <= 0) {
             return -1;
         }
-        return (msPerLevel * mBatteryLevel) * 1000;
+        return (msPerLevel * mCurrentBatteryLevel) * 1000;
     }
 
     @Override
@@ -14166,7 +14824,7 @@
         if (msPerLevel <= 0) {
             return -1;
         }
-        return (msPerLevel * (100 - mBatteryLevel)) * 1000;
+        return (msPerLevel * (100 - mCurrentBatteryLevel)) * 1000;
     }
 
     /*@hide */
@@ -14597,8 +15255,7 @@
 
     @GuardedBy("this")
     public void shutdownLocked() {
-        mHistory.recordShutdownEvent(mClock.elapsedRealtime(), mClock.uptimeMillis(),
-                mClock.currentTimeMillis());
+        recordShutdownLocked(mClock.currentTimeMillis(), mClock.elapsedRealtime());
         writeSyncLocked();
         mShuttingDown = true;
     }
@@ -14806,6 +15463,7 @@
                 PROC_STATE_CHANGE_COLLECTION_DELAY_MS = mParser.getLong(
                         KEY_PROC_STATE_CHANGE_COLLECTION_DELAY_MS,
                         DEFAULT_PROC_STATE_CHANGE_COLLECTION_DELAY_MS);
+
                 MAX_HISTORY_FILES = mParser.getInt(KEY_MAX_HISTORY_FILES,
                         ActivityManager.isLowRamDeviceStatic() ?
                                 DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE
@@ -14816,20 +15474,9 @@
                                 : DEFAULT_MAX_HISTORY_BUFFER_KB)
                         * 1024;
                 updateBatteryChargedDelayMsLocked();
-
-                onChange();
             }
         }
 
-        /**
-         * Propagates changes in constant values.
-         */
-        @VisibleForTesting
-        public void onChange() {
-            mHistory.setMaxHistoryFiles(MAX_HISTORY_FILES);
-            mHistory.setMaxHistoryBufferSize(MAX_HISTORY_BUFFER);
-        }
-
         private void updateBatteryChargedDelayMsLocked() {
             // a negative value indicates that we should ignore this override
             final int delay = Settings.Global.getInt(mResolver,
@@ -15050,11 +15697,27 @@
     }
 
     private void writeHistoryLocked() {
+        if (mBatteryStatsHistory.getActiveFile() == null) {
+            Slog.w(TAG, "writeHistoryLocked: no history file associated with this instance");
+            return;
+        }
+
         if (mShuttingDown) {
             return;
         }
 
-        mHistory.writeHistory();
+        Parcel p = Parcel.obtain();
+        try {
+            final long start = SystemClock.uptimeMillis();
+            writeHistoryBuffer(p, true);
+            if (DEBUG) {
+                Slog.d(TAG, "writeHistoryBuffer duration ms:"
+                        + (SystemClock.uptimeMillis() - start) + " bytes:" + p.dataSize());
+            }
+            writeParcelToFileLocked(p, mBatteryStatsHistory.getActiveFile());
+        } finally {
+            p.recycle();
+        }
     }
 
     private final ReentrantLock mWriteLock = new ReentrantLock();
@@ -15093,6 +15756,13 @@
             return;
         }
 
+        final AtomicFile activeHistoryFile = mBatteryStatsHistory.getActiveFile();
+        if (activeHistoryFile == null) {
+            Slog.w(TAG,
+                    "readLocked: no history file associated with this instance");
+            return;
+        }
+
         mUidStats.clear();
 
         Parcel stats = Parcel.obtain();
@@ -15105,7 +15775,7 @@
                 readSummaryFromParcel(stats);
                 if (DEBUG) {
                     Slog.d(TAG, "readLocked stats file:" + mStatsFile.getBaseFile().getPath()
-                            + " bytes:" + raw.length + " took ms:" + (SystemClock.uptimeMillis()
+                            + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis()
                             - start));
                 }
             }
@@ -15117,19 +15787,126 @@
             stats.recycle();
         }
 
-        if (!mHistory.readSummary()) {
-            resetAllStatsLocked(SystemClock.uptimeMillis(), SystemClock.elapsedRealtime(),
-                    RESET_REASON_CORRUPT_FILE);
+        Parcel history = Parcel.obtain();
+        try {
+            final long start = SystemClock.uptimeMillis();
+            if (activeHistoryFile.exists()) {
+                byte[] raw = activeHistoryFile.readFully();
+                if (raw.length > 0) {
+                    history.unmarshall(raw, 0, raw.length);
+                    history.setDataPosition(0);
+                    readHistoryBuffer(history);
+                }
+                if (DEBUG) {
+                    Slog.d(TAG, "readLocked history file::"
+                            + activeHistoryFile.getBaseFile().getPath()
+                            + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis()
+                            - start));
+                }
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, "Error reading battery history", e);
+            clearHistoryLocked();
+            mBatteryStatsHistory.resetAllFiles();
+        } finally {
+            history.recycle();
         }
 
         mEndPlatformVersion = Build.ID;
 
-        mHistory.continueRecordingHistory();
+        if (mHistoryBuffer.dataPosition() > 0
+                || mBatteryStatsHistory.getFilesNumbers().size() > 1) {
+            mRecordingHistory = true;
+            final long elapsedRealtimeMs = mClock.elapsedRealtime();
+            final long uptimeMs = mClock.uptimeMillis();
+            addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_START, mHistoryCur);
+            startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
+        }
 
         recordDailyStatsIfNeededLocked(false, mClock.currentTimeMillis());
     }
 
     @GuardedBy("this")
+    void  readHistoryBuffer(Parcel in) throws ParcelFormatException {
+        final int version = in.readInt();
+        if (version != BatteryStatsHistory.VERSION) {
+            Slog.w("BatteryStats", "readHistoryBuffer: version got " + version
+                    + ", expected " + BatteryStatsHistory.VERSION + "; erasing old stats");
+            return;
+        }
+
+        final long historyBaseTime = in.readLong();
+
+        mHistoryBuffer.setDataSize(0);
+        mHistoryBuffer.setDataPosition(0);
+
+        int bufSize = in.readInt();
+        int curPos = in.dataPosition();
+        if (bufSize >= (mConstants.MAX_HISTORY_BUFFER*100)) {
+            throw new ParcelFormatException("File corrupt: history data buffer too large " +
+                    bufSize);
+        } else if ((bufSize&~3) != bufSize) {
+            throw new ParcelFormatException("File corrupt: history data buffer not aligned " +
+                    bufSize);
+        } else {
+            if (DEBUG_HISTORY) Slog.i(TAG, "***************** READING NEW HISTORY: " + bufSize
+                    + " bytes at " + curPos);
+            mHistoryBuffer.appendFrom(in, curPos, bufSize);
+            in.setDataPosition(curPos + bufSize);
+        }
+
+        if (DEBUG_HISTORY) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("****************** OLD mHistoryBaseTimeMs: ");
+            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
+            Slog.i(TAG, sb.toString());
+        }
+        mHistoryBaseTimeMs = historyBaseTime;
+        if (DEBUG_HISTORY) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("****************** NEW mHistoryBaseTimeMs: ");
+            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
+            Slog.i(TAG, sb.toString());
+        }
+
+        // We are just arbitrarily going to insert 1 minute from the sample of
+        // the last run until samples in this run.
+        if (mHistoryBaseTimeMs > 0) {
+            long oldnow = mClock.elapsedRealtime();
+            mHistoryBaseTimeMs = mHistoryBaseTimeMs - oldnow + 1;
+            if (DEBUG_HISTORY) {
+                StringBuilder sb = new StringBuilder(128);
+                sb.append("****************** ADJUSTED mHistoryBaseTimeMs: ");
+                TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
+                Slog.i(TAG, sb.toString());
+            }
+        }
+    }
+
+    void writeHistoryBuffer(Parcel out, boolean inclData) {
+        if (DEBUG_HISTORY) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("****************** WRITING mHistoryBaseTimeMs: ");
+            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
+            sb.append(" mLastHistoryElapsedRealtimeMs: ");
+            TimeUtils.formatDuration(mLastHistoryElapsedRealtimeMs, sb);
+            Slog.i(TAG, sb.toString());
+        }
+        out.writeInt(BatteryStatsHistory.VERSION);
+        out.writeLong(mHistoryBaseTimeMs + mLastHistoryElapsedRealtimeMs);
+        if (!inclData) {
+            out.writeInt(0);
+            out.writeInt(0);
+            return;
+        }
+
+        out.writeInt(mHistoryBuffer.dataSize());
+        if (DEBUG_HISTORY) Slog.i(TAG, "***************** WRITING HISTORY: "
+                + mHistoryBuffer.dataSize() + " bytes at " + out.dataPosition());
+        out.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
+    }
+
+    @GuardedBy("this")
     public void readSummaryFromParcel(Parcel in) throws ParcelFormatException {
         final int version = in.readInt();
 
@@ -15139,7 +15916,31 @@
             return;
         }
 
-        mHistory.readSummaryFromParcel(in);
+        boolean inclHistory = in.readBoolean();
+        if (inclHistory) {
+            readHistoryBuffer(in);
+            mBatteryStatsHistory.readFromParcel(in);
+        }
+
+        mHistoryTagPool.clear();
+        mNextHistoryTagIdx = 0;
+        mNumHistoryTagChars = 0;
+
+        int numTags = in.readInt();
+        for (int i=0; i<numTags; i++) {
+            int idx = in.readInt();
+            String str = in.readString();
+            int uid = in.readInt();
+            HistoryTag tag = new HistoryTag();
+            tag.string = str;
+            tag.uid = uid;
+            tag.poolIdx = idx;
+            mHistoryTagPool.put(tag, idx);
+            if (idx >= mNextHistoryTagIdx) {
+                mNextHistoryTagIdx = idx+1;
+            }
+            mNumHistoryTagChars += tag.string.length() + 1;
+        }
 
         mStartCount = in.readInt();
         mUptimeUs = in.readLong();
@@ -15152,7 +15953,7 @@
         mDischargeUnplugLevel = in.readInt();
         mDischargePlugLevel = in.readInt();
         mDischargeCurrentLevel = in.readInt();
-        mBatteryLevel = in.readInt();
+        mCurrentBatteryLevel = in.readInt();
         mEstimatedBatteryCapacityMah = in.readInt();
         mLastLearnedBatteryCapacityUah = in.readInt();
         mMinLearnedBatteryCapacityUah = in.readInt();
@@ -15655,7 +16456,19 @@
 
         out.writeInt(VERSION);
 
-        mHistory.writeSummaryToParcel(out, inclHistory);
+        out.writeBoolean(inclHistory);
+        if (inclHistory) {
+            writeHistoryBuffer(out, true);
+            mBatteryStatsHistory.writeToParcel(out);
+        }
+
+        out.writeInt(mHistoryTagPool.size());
+        for (HashMap.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) {
+            HistoryTag tag = ent.getKey();
+            out.writeInt(ent.getValue());
+            out.writeString(tag.string);
+            out.writeInt(tag.uid);
+        }
 
         out.writeInt(mStartCount);
         out.writeLong(computeUptime(nowUptime, STATS_SINCE_CHARGED));
@@ -15668,7 +16481,7 @@
         out.writeInt(mDischargeUnplugLevel);
         out.writeInt(mDischargePlugLevel);
         out.writeInt(mDischargeCurrentLevel);
-        out.writeInt(mBatteryLevel);
+        out.writeInt(mCurrentBatteryLevel);
         out.writeInt(mEstimatedBatteryCapacityMah);
         out.writeInt(mLastLearnedBatteryCapacityUah);
         out.writeInt(mMinLearnedBatteryCapacityUah);
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index c36d950..0cdd4d1 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -22,6 +22,7 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
+import android.os.Parcel;
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.UidBatteryConsumer;
@@ -31,8 +32,10 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BatteryStatsHistory;
 import com.android.internal.os.PowerProfile;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -217,7 +220,18 @@
             }
 
             BatteryStatsImpl batteryStatsImpl = (BatteryStatsImpl) mStats;
-            batteryUsageStatsBuilder.setBatteryHistory(batteryStatsImpl.copyHistory());
+
+            // Make a copy of battery history to avoid concurrent modification.
+            Parcel historyBuffer = Parcel.obtain();
+            historyBuffer.appendFrom(batteryStatsImpl.mHistoryBuffer, 0,
+                    batteryStatsImpl.mHistoryBuffer.dataSize());
+
+            final File systemDir =
+                    batteryStatsImpl.mBatteryStatsHistory.getHistoryDirectory().getParentFile();
+            final BatteryStatsHistory batteryStatsHistory =
+                    new BatteryStatsHistory(historyBuffer, systemDir, null);
+
+            batteryUsageStatsBuilder.setBatteryHistory(batteryStatsHistory);
         }
 
         BatteryUsageStats stats = batteryUsageStatsBuilder.build();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 55175c5..f8f94f6 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -312,7 +312,6 @@
 import android.util.TypedXmlSerializer;
 import android.util.proto.ProtoOutputStream;
 import android.view.AppTransitionAnimationSpec;
-import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.InputApplicationHandle;
@@ -357,6 +356,7 @@
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.WindowManagerService.H;
 import com.android.server.wm.utils.InsetUtils;
+import com.android.server.wm.utils.WmDisplayCutout;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -659,6 +659,9 @@
 
     boolean mUseTransferredAnimation;
 
+    /** Whether we need to setup the animation to animate only within the letterbox. */
+    private boolean mNeedsLetterboxedAnimation;
+
     /**
      * @see #currentLaunchCanTurnScreenOn()
      */
@@ -1762,8 +1765,12 @@
         mLetterboxUiController.layoutLetterbox(winHint);
     }
 
-    boolean hasWallpaperBackgroudForLetterbox() {
-        return mLetterboxUiController.hasWallpaperBackgroudForLetterbox();
+    boolean hasWallpaperBackgroundForLetterbox() {
+        return mLetterboxUiController.hasWallpaperBackgroundForLetterbox();
+    }
+
+    void updateLetterboxSurface(WindowState winHint, Transaction t) {
+        mLetterboxUiController.updateLetterboxSurface(winHint, t);
     }
 
     void updateLetterboxSurface(WindowState winHint) {
@@ -3238,7 +3245,7 @@
         rootTask.moveToFront(reason, task);
         // Report top activity change to tracking services and WM
         if (mRootWindowContainer.getTopResumedActivity() == this) {
-            mAtmService.setResumedActivityUncheckLocked(this, reason);
+            mAtmService.setLastResumedActivityUncheckLocked(this, reason);
         }
         return true;
     }
@@ -5325,6 +5332,18 @@
         commitVisibility(visible, performLayout, false /* fromTransition */);
     }
 
+    void setNeedsLetterboxedAnimation(boolean needsLetterboxedAnimation) {
+        mNeedsLetterboxedAnimation = needsLetterboxedAnimation;
+    }
+
+    boolean isNeedsLetterboxedAnimation() {
+        return mNeedsLetterboxedAnimation;
+    }
+
+    boolean isInLetterboxAnimation() {
+        return mNeedsLetterboxedAnimation && isAnimating();
+    }
+
     /**
      * Post process after applying an app transition animation.
      *
@@ -5868,7 +5887,8 @@
             try {
                 mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
                         PauseActivityItem.obtain(finishing, false /* userLeaving */,
-                                configChangeFlags, false /* dontReport */));
+                                configChangeFlags, false /* dontReport */,
+                                false /* autoEnteringPip */));
             } catch (Exception e) {
                 Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
             }
@@ -7252,6 +7272,10 @@
                 .setParent(getAnimationLeashParent())
                 .setName(getSurfaceControl() + " - animation-bounds")
                 .setCallsite("ActivityRecord.createAnimationBoundsLayer");
+        if (mNeedsLetterboxedAnimation) {
+            // Needs to be an effect layer to support rounded corners
+            builder.setEffectLayer();
+        }
         final SurfaceControl boundsLayer = builder.build();
         t.show(boundsLayer);
         return boundsLayer;
@@ -7289,6 +7313,11 @@
             mAnimatingActivityRegistry.notifyStarting(this);
         }
 
+        if (mNeedsLetterboxedAnimation) {
+            updateLetterboxSurface(findMainWindow(), t);
+            mNeedsAnimationBoundsLayer = true;
+        }
+
         // If the animation needs to be cropped then an animation bounds layer is created as a
         // child of the root pinned task or animation layer. The leash is then reparented to this
         // new layer.
@@ -7311,6 +7340,17 @@
             t.setLayer(leash, 0);
             t.setLayer(mAnimationBoundsLayer, getLastLayer());
 
+            if (mNeedsLetterboxedAnimation) {
+                final int cornerRadius = mLetterboxUiController
+                        .getRoundedCornersRadius(findMainWindow());
+
+                final Rect letterboxInnerBounds = new Rect();
+                getLetterboxInnerBounds(letterboxInnerBounds);
+
+                t.setCornerRadius(mAnimationBoundsLayer, cornerRadius)
+                        .setCrop(mAnimationBoundsLayer, letterboxInnerBounds);
+            }
+
             // Reparent leash to animation bounds layer.
             t.reparent(leash, mAnimationBoundsLayer);
         }
@@ -7424,6 +7464,12 @@
             mAnimationBoundsLayer = null;
         }
 
+        mNeedsAnimationBoundsLayer = false;
+        if (mNeedsLetterboxedAnimation) {
+            mNeedsLetterboxedAnimation = false;
+            updateLetterboxSurface(findMainWindow(), t);
+        }
+
         if (mAnimatingActivityRegistry != null) {
             mAnimatingActivityRegistry.notifyFinished(this);
         }
@@ -7436,7 +7482,6 @@
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AR#onAnimationFinished");
         mTransit = TRANSIT_OLD_UNSET;
         mTransitFlags = 0;
-        mNeedsAnimationBoundsLayer = false;
 
         setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM | FINISH_LAYOUT_REDO_WALLPAPER,
                 "ActivityRecord");
@@ -9639,9 +9684,8 @@
                 final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
                 final int dw = rotated ? display.mBaseDisplayHeight : display.mBaseDisplayWidth;
                 final int dh = rotated ? display.mBaseDisplayWidth : display.mBaseDisplayHeight;
-                final DisplayCutout cutout = display.calculateDisplayCutoutForRotation(rotation)
-                        .getDisplayCutout();
-                policy.getNonDecorInsetsLw(rotation, cutout, mNonDecorInsets[rotation]);
+                final WmDisplayCutout cutout = display.calculateDisplayCutoutForRotation(rotation);
+                policy.getNonDecorInsetsLw(rotation, dw, dh, cutout, mNonDecorInsets[rotation]);
                 mStableInsets[rotation].set(mNonDecorInsets[rotation]);
                 policy.convertNonDecorInsetsToStableInsets(mStableInsets[rotation], rotation);
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 0a4bc60d..4003eeb 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1486,6 +1486,7 @@
         a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
         a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
         a.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+        a.configChanges = ActivityInfo.CONFIG_ORIENTATION;
 
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchActivityType(ACTIVITY_TYPE_DREAM);
@@ -3562,7 +3563,7 @@
                 // Continue the pausing process after entering pip.
                 if (r.isState(PAUSING)) {
                     r.getTask().schedulePauseActivity(r, false /* userLeaving */,
-                            false /* pauseImmediately */, "auto-pip");
+                            false /* pauseImmediately */, true /* autoEnteringPip */, "auto-pip");
                 }
             }
         };
@@ -4623,7 +4624,7 @@
     }
 
     /** Update AMS states when an activity is resumed. */
-    void setResumedActivityUncheckLocked(ActivityRecord r, String reason) {
+    void setLastResumedActivityUncheckLocked(ActivityRecord r, String reason) {
         final Task task = r.getTask();
         if (task.isActivityTypeStandard()) {
             if (mCurAppTimeTracker != r.appTimeTracker) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 5a1afc4..dc91c15 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -789,7 +789,7 @@
 
             // schedule launch ticks to collect information about slow apps.
             r.startLaunchTickingLocked();
-
+            r.lastLaunchTime = SystemClock.uptimeMillis();
             r.setProcess(proc);
 
             // Ensure activity is allowed to be resumed after process has set.
@@ -835,8 +835,6 @@
             final IActivityClientController activityClientController =
                     proc.hasEverLaunchedActivity() ? null : mService.mActivityClientController;
             r.launchCount++;
-            r.lastLaunchTime = SystemClock.uptimeMillis();
-            proc.setLastActivityLaunchTime(r.lastLaunchTime);
 
             if (DEBUG_ALL) Slog.v(TAG, "Launching: " + r);
 
@@ -2085,7 +2083,7 @@
      * activity releases the top state and reports back, message about acquiring top state will be
      * sent to the new top resumed activity.
      */
-    void updateTopResumedActivityIfNeeded() {
+    void updateTopResumedActivityIfNeeded(String reason) {
         final ActivityRecord prevTopActivity = mTopResumedActivity;
         final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
         if (topRootTask == null || topRootTask.getTopResumedActivity() == prevTopActivity) {
@@ -2121,6 +2119,12 @@
             }
             mService.updateOomAdj();
         }
+        // Update the last resumed activity and focused app when the top resumed activity changed
+        // because the new top resumed activity might be already resumed and thus won't have
+        // activity state change to update the records to AMS.
+        if (mTopResumedActivity != null) {
+            mService.setLastResumedActivityUncheckLocked(mTopResumedActivity, reason);
+        }
         scheduleTopResumedActivityStateIfNeeded();
 
         mService.updateTopApp(mTopResumedActivity);
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 95169db..55d6b2f 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -375,6 +375,9 @@
         final AnimationAdapter topOpeningAnim = wc != null ? wc.getAnimation() : null;
 
         int redoLayout = notifyAppTransitionStartingLocked(
+                AppTransition.isKeyguardGoingAwayTransitOld(transit),
+                AppTransition.isKeyguardOccludeTransitOld(transit),
+                topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,
                 topOpeningAnim != null
                         ? topOpeningAnim.getStatusBarTransitionsStartTime()
                         : SystemClock.uptimeMillis(),
@@ -413,11 +416,8 @@
     }
 
     void freeze() {
-        final boolean keyguardGoingAwayCancelled = mNextAppTransitionRequests.contains(
+        final boolean keyguardGoingAway = mNextAppTransitionRequests.contains(
                 TRANSIT_KEYGUARD_GOING_AWAY);
-        final boolean keyguardOccludedCancelled =
-                mNextAppTransitionRequests.contains(TRANSIT_KEYGUARD_OCCLUDE)
-                || mNextAppTransitionRequests.contains(TRANSIT_KEYGUARD_UNOCCLUDE);
 
         // The RemoteAnimationControl didn't register AppTransitionListener and
         // only initialized the finish and timeout callback when goodToGo().
@@ -429,7 +429,7 @@
         mNextAppTransitionRequests.clear();
         clear();
         setReady();
-        notifyAppTransitionCancelledLocked(keyguardGoingAwayCancelled, keyguardOccludedCancelled);
+        notifyAppTransitionCancelledLocked(keyguardGoingAway);
     }
 
     private void setAppTransitionState(int state) {
@@ -479,11 +479,9 @@
         }
     }
 
-    private void notifyAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
-            boolean keyguardOccludedCancelled) {
+    private void notifyAppTransitionCancelledLocked(boolean keyguardGoingAway) {
         for (int i = 0; i < mListeners.size(); i++) {
-            mListeners.get(i).onAppTransitionCancelledLocked(keyguardGoingAwayCancelled,
-                    keyguardOccludedCancelled);
+            mListeners.get(i).onAppTransitionCancelledLocked(keyguardGoingAway);
         }
     }
 
@@ -493,12 +491,14 @@
         }
     }
 
-    private int notifyAppTransitionStartingLocked(long statusBarAnimationStartTime,
+    private int notifyAppTransitionStartingLocked(boolean keyguardGoingAway,
+            boolean keyguardOcclude, long duration, long statusBarAnimationStartTime,
             long statusBarAnimationDuration) {
         int redoLayout = 0;
         for (int i = 0; i < mListeners.size(); i++) {
-            redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(
-                    statusBarAnimationStartTime, statusBarAnimationDuration);
+            redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway,
+                    keyguardOcclude, duration, statusBarAnimationStartTime,
+                    statusBarAnimationDuration);
         }
         return redoLayout;
     }
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index a645e89..44f388b 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -20,6 +20,10 @@
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
 import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
@@ -77,9 +81,11 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.Rect;
 import android.os.Trace;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Pair;
 import android.util.Slog;
 import android.view.Display;
 import android.view.RemoteAnimationAdapter;
@@ -89,6 +95,7 @@
 import android.view.WindowManager.TransitionFlags;
 import android.view.WindowManager.TransitionOldType;
 import android.view.WindowManager.TransitionType;
+import android.view.animation.Animation;
 import android.window.ITaskFragmentOrganizer;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -290,6 +297,7 @@
 
             final int flags = appTransition.getTransitFlags();
             layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
+            handleNonAppWindowsInTransition(transit, flags);
             appTransition.postAnimationCallback();
             appTransition.clear();
         } finally {
@@ -1031,6 +1039,32 @@
             return;
         }
 
+        if (AppTransition.isActivityTransitOld(transit)) {
+            final ArrayList<Pair<ActivityRecord, Rect>> closingLetterboxes = new ArrayList();
+            for (int i = 0; i < closingApps.size(); ++i) {
+                ActivityRecord closingApp = closingApps.valueAt(i);
+                if (closingApp.areBoundsLetterboxed()) {
+                    final Rect insets = closingApp.getLetterboxInsets();
+                    closingLetterboxes.add(new Pair(closingApp, insets));
+                }
+            }
+
+            for (int i = 0; i < openingApps.size(); ++i) {
+                ActivityRecord openingApp = openingApps.valueAt(i);
+                if (openingApp.areBoundsLetterboxed()) {
+                    final Rect openingInsets = openingApp.getLetterboxInsets();
+                    for (Pair<ActivityRecord, Rect> closingLetterbox : closingLetterboxes) {
+                        final Rect closingInsets = closingLetterbox.second;
+                        if (openingInsets.equals(closingInsets)) {
+                            ActivityRecord closingApp = closingLetterbox.first;
+                            openingApp.setNeedsLetterboxedAnimation(true);
+                            closingApp.setNeedsLetterboxedAnimation(true);
+                        }
+                    }
+                }
+            }
+        }
+
         final ArraySet<WindowContainer> openingWcs = getAnimationTargets(
                 openingApps, closingApps, true /* visible */);
         final ArraySet<WindowContainer> closingWcs = getAnimationTargets(
@@ -1137,6 +1171,30 @@
         }
     }
 
+    private void handleNonAppWindowsInTransition(@TransitionOldType int transit, int flags) {
+        if (transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
+                && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
+            if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
+                    && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0
+                    && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) == 0) {
+                Animation anim = mService.mPolicy.createKeyguardWallpaperExit(
+                        (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0);
+                if (anim != null) {
+                    anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
+                    mDisplayContent.mWallpaperController.startWallpaperAnimation(anim);
+                }
+            }
+        }
+        if ((transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
+                || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER)
+                && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
+            mDisplayContent.startKeyguardExitOnNonAppWindows(
+                    transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+                    (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0,
+                    (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0);
+        }
+    }
+
     private boolean transitionGoodToGo(ArraySet<? extends WindowContainer> apps,
             ArrayMap<WindowContainer, Integer> outReasons) {
         ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index 994f079..5a24099 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -17,8 +17,12 @@
 package com.android.server.wm;
 
 import android.annotation.UiThread;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.res.Configuration;
 import android.os.Build;
 import android.os.Handler;
@@ -305,21 +309,21 @@
     private void hideDialogsForPackageUiThread(String name) {
         // Hides the "unsupported display" dialog if necessary.
         if (mUnsupportedDisplaySizeDialog != null && (name == null || name.equals(
-                mUnsupportedDisplaySizeDialog.getPackageName()))) {
+                mUnsupportedDisplaySizeDialog.mPackageName))) {
             mUnsupportedDisplaySizeDialog.dismiss();
             mUnsupportedDisplaySizeDialog = null;
         }
 
         // Hides the "unsupported compile SDK" dialog if necessary.
         if (mUnsupportedCompileSdkDialog != null && (name == null || name.equals(
-                mUnsupportedCompileSdkDialog.getPackageName()))) {
+                mUnsupportedCompileSdkDialog.mPackageName))) {
             mUnsupportedCompileSdkDialog.dismiss();
             mUnsupportedCompileSdkDialog = null;
         }
 
         // Hides the "deprecated target sdk version" dialog if necessary.
         if (mDeprecatedTargetSdkVersionDialog != null && (name == null || name.equals(
-                mDeprecatedTargetSdkVersionDialog.getPackageName()))) {
+                mDeprecatedTargetSdkVersionDialog.mPackageName))) {
             mDeprecatedTargetSdkVersionDialog.dismiss();
             mDeprecatedTargetSdkVersionDialog = null;
         }
@@ -431,6 +435,49 @@
         }
     }
 
+    static class BaseDialog {
+        final AppWarnings mManager;
+        final String mPackageName;
+        AlertDialog mDialog;
+        private BroadcastReceiver mCloseReceiver;
+
+        BaseDialog(AppWarnings manager, String packageName) {
+            mManager = manager;
+            mPackageName = packageName;
+        }
+
+        @UiThread
+        void show() {
+            if (mDialog == null) return;
+            if (mCloseReceiver == null) {
+                mCloseReceiver = new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+                            mManager.mUiHandler.hideDialogsForPackage(mPackageName);
+                        }
+                    }
+                };
+                mManager.mUiContext.registerReceiver(mCloseReceiver,
+                        new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
+                        Context.RECEIVER_EXPORTED);
+            }
+            Slog.w(TAG, "Showing " + getClass().getSimpleName() + " for package " + mPackageName);
+            mDialog.show();
+        }
+
+        @UiThread
+        void dismiss() {
+            if (mDialog == null) return;
+            if (mCloseReceiver != null) {
+                mManager.mUiContext.unregisterReceiver(mCloseReceiver);
+                mCloseReceiver = null;
+            }
+            mDialog.dismiss();
+            mDialog = null;
+        }
+    }
+
     /**
      * Handles messages on the ActivityTaskManagerService thread.
      */
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 9a94a4f..d345227 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -22,6 +22,7 @@
 import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Trace;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -63,6 +64,15 @@
 class BLASTSyncEngine {
     private static final String TAG = "BLASTSyncEngine";
 
+    /** No specific method. Used by override specifiers. */
+    public static final int METHOD_UNDEFINED = -1;
+
+    /** No sync method. Apps will draw/present internally and just report. */
+    public static final int METHOD_NONE = 0;
+
+    /** Sync with BLAST. Apps will draw and then send the buffer to be applied in sync. */
+    public static final int METHOD_BLAST = 1;
+
     interface TransactionReadyListener {
         void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction);
     }
@@ -85,6 +95,7 @@
      */
     class SyncGroup {
         final int mSyncId;
+        final int mSyncMethod;
         final TransactionReadyListener mListener;
         final Runnable mOnTimeout;
         boolean mReady = false;
@@ -92,8 +103,9 @@
         private SurfaceControl.Transaction mOrphanTransaction = null;
         private String mTraceName;
 
-        private SyncGroup(TransactionReadyListener listener, int id, String name) {
+        private SyncGroup(TransactionReadyListener listener, int id, String name, int method) {
             mSyncId = id;
+            mSyncMethod = method;
             mListener = listener;
             mOnTimeout = () -> {
                 Slog.w(TAG, "Sync group " + mSyncId + " timeout");
@@ -271,16 +283,13 @@
      * Prepares a {@link SyncGroup} that is not active yet. Caller must call {@link #startSyncSet}
      * before calling {@link #addToSyncSet(int, WindowContainer)} on any {@link WindowContainer}.
      */
-    SyncGroup prepareSyncSet(TransactionReadyListener listener, String name) {
-        return new SyncGroup(listener, mNextSyncId++, name);
+    SyncGroup prepareSyncSet(TransactionReadyListener listener, String name, int method) {
+        return new SyncGroup(listener, mNextSyncId++, name, method);
     }
 
-    int startSyncSet(TransactionReadyListener listener) {
-        return startSyncSet(listener, BLAST_TIMEOUT_DURATION, "");
-    }
-
-    int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name) {
-        final SyncGroup s = prepareSyncSet(listener, name);
+    int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name,
+            int method) {
+        final SyncGroup s = prepareSyncSet(listener, name, method);
         startSyncSet(s, timeoutMs);
         return s.mSyncId;
     }
@@ -302,6 +311,11 @@
         scheduleTimeout(s, timeoutMs);
     }
 
+    @Nullable
+    SyncGroup getSyncSet(int id) {
+        return mActiveSyncs.get(id);
+    }
+
     boolean hasActiveSync() {
         return mActiveSyncs.size() != 0;
     }
diff --git a/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java b/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java
index 37244bd..1a7a9b2 100644
--- a/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java
+++ b/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java
@@ -16,31 +16,23 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
-import android.util.Log;
 import android.view.Window;
 import android.view.WindowManager;
 
 import com.android.internal.R;
 import com.android.server.utils.AppInstallerUtil;
 
-public class DeprecatedTargetSdkVersionDialog {
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "DeprecatedTargetSdkVersionDialog" : TAG_ATM;
+class DeprecatedTargetSdkVersionDialog extends AppWarnings.BaseDialog {
 
-    private final AlertDialog mDialog;
-    private final String mPackageName;
-
-    public DeprecatedTargetSdkVersionDialog(final AppWarnings manager, Context context,
+    DeprecatedTargetSdkVersionDialog(final AppWarnings manager, Context context,
             ApplicationInfo appInfo) {
-        mPackageName = appInfo.packageName;
+        super(manager, appInfo.packageName);
 
         final PackageManager pm = context.getPackageManager();
         final CharSequence label = appInfo.loadSafeLabel(pm,
@@ -75,17 +67,4 @@
         // DO NOT MODIFY. Used by CTS to verify the dialog is displayed.
         window.getAttributes().setTitle("DeprecatedTargetSdkVersionDialog");
     }
-
-    public String getPackageName() {
-        return mPackageName;
-    }
-
-    public void show() {
-        Log.w(TAG, "Showing SDK deprecation warning for package " + mPackageName);
-        mDialog.show();
-    }
-
-    public void dismiss() {
-        mDialog.dismiss();
-    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index c6fa4c1..7773702 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -441,7 +441,7 @@
      */
     final DisplayMetrics mRealDisplayMetrics = new DisplayMetrics();
 
-    /** @see #computeCompatSmallestWidth(boolean, int, int, int) */
+    /** @see #computeCompatSmallestWidth(boolean, int, int) */
     private final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics();
 
     /**
@@ -2017,7 +2017,7 @@
         // the top of the method, the caller is obligated to call computeNewConfigurationLocked().
         // By updating the Display info here it will be available to
         // #computeScreenConfiguration() later.
-        updateDisplayAndOrientation(getConfiguration().uiMode, null /* outConfig */);
+        updateDisplayAndOrientation(null /* outConfig */);
 
         // NOTE: We disable the rotation in the emulator because
         //       it doesn't support hardware OpenGL emulation yet.
@@ -2067,7 +2067,7 @@
      * changed.
      * Do not call if {@link WindowManagerService#mDisplayReady} == false.
      */
-    private DisplayInfo updateDisplayAndOrientation(int uiMode, Configuration outConfig) {
+    private DisplayInfo updateDisplayAndOrientation(Configuration outConfig) {
         // Use the effective "visual" dimensions based on current rotation
         final int rotation = getRotation();
         final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
@@ -2079,18 +2079,16 @@
         final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();
         final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
 
-        final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
-                displayCutout);
-        final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dh, rotation,
-                displayCutout);
+        final Rect appFrame = mDisplayPolicy.getNonDecorDisplayFrame(dw, dh, rotation,
+                wmDisplayCutout);
         mDisplayInfo.rotation = rotation;
         mDisplayInfo.logicalWidth = dw;
         mDisplayInfo.logicalHeight = dh;
         mDisplayInfo.logicalDensityDpi = mBaseDisplayDensity;
         mDisplayInfo.physicalXDpi = mBaseDisplayPhysicalXDpi;
         mDisplayInfo.physicalYDpi = mBaseDisplayPhysicalYDpi;
-        mDisplayInfo.appWidth = appWidth;
-        mDisplayInfo.appHeight = appHeight;
+        mDisplayInfo.appWidth = appFrame.width();
+        mDisplayInfo.appHeight = appFrame.height();
         if (isDefaultDisplay) {
             mDisplayInfo.getLogicalMetrics(mRealDisplayMetrics,
                     CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
@@ -2104,7 +2102,7 @@
             mDisplayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;
         }
 
-        computeSizeRangesAndScreenLayout(mDisplayInfo, rotated, uiMode, dw, dh,
+        computeSizeRangesAndScreenLayout(mDisplayInfo, rotated, dw, dh,
                 mDisplayMetrics.density, outConfig);
 
         mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId,
@@ -2194,10 +2192,8 @@
         outConfig.windowConfiguration.setMaxBounds(0, 0, dw, dh);
         outConfig.windowConfiguration.setBounds(outConfig.windowConfiguration.getMaxBounds());
 
-        final int uiMode = getConfiguration().uiMode;
-        final DisplayCutout displayCutout =
-                calculateDisplayCutoutForRotation(rotation).getDisplayCutout();
-        computeScreenAppConfiguration(outConfig, dw, dh, rotation, uiMode, displayCutout);
+        final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
+        computeScreenAppConfiguration(outConfig, dw, dh, rotation, wmDisplayCutout);
 
         final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
         displayInfo.rotation = rotation;
@@ -2206,38 +2202,35 @@
         final Rect appBounds = outConfig.windowConfiguration.getAppBounds();
         displayInfo.appWidth = appBounds.width();
         displayInfo.appHeight = appBounds.height();
+        final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();
         displayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
-        computeSizeRangesAndScreenLayout(displayInfo, rotated, uiMode, dw, dh,
+        computeSizeRangesAndScreenLayout(displayInfo, rotated, dw, dh,
                 mDisplayMetrics.density, outConfig);
         return displayInfo;
     }
 
     /** Compute configuration related to application without changing current display. */
     private void computeScreenAppConfiguration(Configuration outConfig, int dw, int dh,
-            int rotation, int uiMode, DisplayCutout displayCutout) {
-        final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
-                displayCutout);
-        final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dh, rotation,
-                displayCutout);
-        mDisplayPolicy.getNonDecorInsetsLw(rotation, displayCutout, mTmpRect);
-        final int leftInset = mTmpRect.left;
-        final int topInset = mTmpRect.top;
+            int rotation, WmDisplayCutout wmDisplayCutout) {
+        final DisplayFrames displayFrames =
+                mDisplayPolicy.getSimulatedDisplayFrames(rotation, dw, dh, wmDisplayCutout);
+        final Rect appFrame =
+                mDisplayPolicy.getNonDecorDisplayFrameWithSimulatedFrame(displayFrames);
         // AppBounds at the root level should mirror the app screen size.
-        outConfig.windowConfiguration.setAppBounds(leftInset /* left */, topInset /* top */,
-                leftInset + appWidth /* right */, topInset + appHeight /* bottom */);
+        outConfig.windowConfiguration.setAppBounds(appFrame);
         outConfig.windowConfiguration.setRotation(rotation);
         outConfig.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
 
         final float density = mDisplayMetrics.density;
-        outConfig.screenWidthDp = (int) (mDisplayPolicy.getConfigDisplayWidth(dw, dh, rotation,
-                uiMode, displayCutout) / density + 0.5f);
-        outConfig.screenHeightDp = (int) (mDisplayPolicy.getConfigDisplayHeight(dw, dh, rotation,
-                uiMode, displayCutout) / density + 0.5f);
+        final Point configSize =
+                mDisplayPolicy.getConfigDisplaySizeWithSimulatedFrame(displayFrames);
+        outConfig.screenWidthDp = (int) (configSize.x / density + 0.5f);
+        outConfig.screenHeightDp = (int) (configSize.y / density + 0.5f);
         outConfig.compatScreenWidthDp = (int) (outConfig.screenWidthDp / mCompatibleScreenScale);
         outConfig.compatScreenHeightDp = (int) (outConfig.screenHeightDp / mCompatibleScreenScale);
 
         final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
-        outConfig.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, uiMode, dw, dh);
+        outConfig.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, dw, dh);
         outConfig.windowConfiguration.setDisplayRotation(rotation);
     }
 
@@ -2246,7 +2239,7 @@
      * Do not call if mDisplayReady == false.
      */
     void computeScreenConfiguration(Configuration config) {
-        final DisplayInfo displayInfo = updateDisplayAndOrientation(config.uiMode, config);
+        final DisplayInfo displayInfo = updateDisplayAndOrientation(config);
         final int dw = displayInfo.logicalWidth;
         final int dh = displayInfo.logicalHeight;
         mTmpRect.set(0, 0, dw, dh);
@@ -2255,8 +2248,8 @@
         config.windowConfiguration.setWindowingMode(getWindowingMode());
         config.windowConfiguration.setDisplayWindowingMode(getWindowingMode());
 
-        computeScreenAppConfiguration(config, dw, dh, displayInfo.rotation, config.uiMode,
-                displayInfo.displayCutout);
+        computeScreenAppConfiguration(config, dw, dh, displayInfo.rotation,
+                calculateDisplayCutoutForRotation(getRotation()));
 
         config.screenLayout = (config.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK)
                 | ((displayInfo.flags & Display.FLAG_ROUND) != 0
@@ -2345,7 +2338,7 @@
         mWmService.mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence);
     }
 
-    private int computeCompatSmallestWidth(boolean rotated, int uiMode, int dw, int dh) {
+    private int computeCompatSmallestWidth(boolean rotated, int dw, int dh) {
         mTmpDisplayMetrics.setTo(mDisplayMetrics);
         final DisplayMetrics tmpDm = mTmpDisplayMetrics;
         final int unrotDw, unrotDh;
@@ -2356,25 +2349,20 @@
             unrotDw = dw;
             unrotDh = dh;
         }
-        int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, uiMode, tmpDm, unrotDw,
-                unrotDh);
-        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, uiMode, tmpDm, unrotDh,
-                unrotDw);
-        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, uiMode, tmpDm, unrotDw,
-                unrotDh);
-        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, uiMode, tmpDm, unrotDh,
-                unrotDw);
+        int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, tmpDm, unrotDw, unrotDh);
+        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, tmpDm, unrotDh, unrotDw);
+        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, tmpDm, unrotDw, unrotDh);
+        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, tmpDm, unrotDh, unrotDw);
         return sw;
     }
 
-    private int reduceCompatConfigWidthSize(int curSize, int rotation, int uiMode,
+    private int reduceCompatConfigWidthSize(int curSize, int rotation,
             DisplayMetrics dm, int dw, int dh) {
-        final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(
-                rotation).getDisplayCutout();
-        dm.noncompatWidthPixels = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
-                displayCutout);
-        dm.noncompatHeightPixels = mDisplayPolicy.getNonDecorDisplayHeight(dh, rotation,
-                displayCutout);
+        final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
+        final Rect nonDecorSize = mDisplayPolicy.getNonDecorDisplayFrame(dw, dh, rotation,
+                wmDisplayCutout);
+        dm.noncompatWidthPixels = nonDecorSize.width();
+        dm.noncompatHeightPixels = nonDecorSize.height();
         float scale = CompatibilityInfo.computeCompatibleScaling(dm, null);
         int size = (int)(((dm.noncompatWidthPixels / scale) / dm.density) + .5f);
         if (curSize == 0 || size < curSize) {
@@ -2384,7 +2372,7 @@
     }
 
     private void computeSizeRangesAndScreenLayout(DisplayInfo displayInfo, boolean rotated,
-            int uiMode, int dw, int dh, float density, Configuration outConfig) {
+            int dw, int dh, float density, Configuration outConfig) {
 
         // We need to determine the smallest width that will occur under normal
         // operation.  To this, start with the base screen size and compute the
@@ -2402,37 +2390,34 @@
         displayInfo.smallestNominalAppHeight = 1<<30;
         displayInfo.largestNominalAppWidth = 0;
         displayInfo.largestNominalAppHeight = 0;
-        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, uiMode, unrotDw, unrotDh);
-        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, uiMode, unrotDh, unrotDw);
-        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, uiMode, unrotDw, unrotDh);
-        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, uiMode, unrotDh, unrotDw);
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, unrotDw, unrotDh);
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, unrotDh, unrotDw);
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, unrotDw, unrotDh);
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, unrotDh, unrotDw);
 
         if (outConfig == null) {
             return;
         }
         int sl = Configuration.resetScreenLayout(outConfig.screenLayout);
-        sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh, uiMode);
-        sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw, uiMode);
-        sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh, uiMode);
-        sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw, uiMode);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw);
         outConfig.smallestScreenWidthDp =
                 (int) (displayInfo.smallestNominalAppWidth / density + 0.5f);
         outConfig.screenLayout = sl;
     }
 
-    private int reduceConfigLayout(int curLayout, int rotation, float density, int dw, int dh,
-            int uiMode) {
+    private int reduceConfigLayout(int curLayout, int rotation, float density, int dw, int dh) {
         // Get the display cutout at this rotation.
-        final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(
-                rotation).getDisplayCutout();
+        final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
 
         // Get the app screen size at this rotation.
-        int w = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, displayCutout);
-        int h = mDisplayPolicy.getNonDecorDisplayHeight(dh, rotation, displayCutout);
+        final Rect size = mDisplayPolicy.getNonDecorDisplayFrame(dw, dh, rotation, wmDisplayCutout);
 
         // Compute the screen layout size class for this rotation.
-        int longSize = w;
-        int shortSize = h;
+        int longSize = size.width();
+        int shortSize = size.height();
         if (longSize < shortSize) {
             int tmp = longSize;
             longSize = shortSize;
@@ -2443,25 +2428,20 @@
         return Configuration.reduceScreenLayout(curLayout, longSize, shortSize);
     }
 
-    private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation,
-            int uiMode, int dw, int dh) {
-        final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(
-                rotation).getDisplayCutout();
-        final int width = mDisplayPolicy.getConfigDisplayWidth(dw, dh, rotation, uiMode,
-                displayCutout);
-        if (width < displayInfo.smallestNominalAppWidth) {
-            displayInfo.smallestNominalAppWidth = width;
+    private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, int dw, int dh) {
+        final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
+        final Point size = mDisplayPolicy.getConfigDisplaySize(dw, dh, rotation, wmDisplayCutout);
+        if (size.x < displayInfo.smallestNominalAppWidth) {
+            displayInfo.smallestNominalAppWidth = size.x;
         }
-        if (width > displayInfo.largestNominalAppWidth) {
-            displayInfo.largestNominalAppWidth = width;
+        if (size.x > displayInfo.largestNominalAppWidth) {
+            displayInfo.largestNominalAppWidth = size.x;
         }
-        final int height = mDisplayPolicy.getConfigDisplayHeight(dw, dh, rotation, uiMode,
-                displayCutout);
-        if (height < displayInfo.smallestNominalAppHeight) {
-            displayInfo.smallestNominalAppHeight = height;
+        if (size.y < displayInfo.smallestNominalAppHeight) {
+            displayInfo.smallestNominalAppHeight = size.y;
         }
-        if (height > displayInfo.largestNominalAppHeight) {
-            displayInfo.largestNominalAppHeight = height;
+        if (size.y > displayInfo.largestNominalAppHeight) {
+            displayInfo.largestNominalAppHeight = size.y;
         }
     }
 
@@ -3316,6 +3296,14 @@
                     mAsyncRotationController.keepAppearanceInPreviousRotation();
                 }
             } else if (isRotationChanging()) {
+                if (displayChange != null) {
+                    final boolean seamless = mDisplayRotation.shouldRotateSeamlessly(
+                            displayChange.getStartRotation(), displayChange.getEndRotation(),
+                            false /* forceUpdate */);
+                    if (seamless) {
+                        t.onSeamlessRotating(this);
+                    }
+                }
                 mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
                 controller.mTransitionMetricsReporter.associate(t,
                         startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
@@ -6598,8 +6586,7 @@
         }
 
         @Override
-        public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
-                boolean keyguardOccludedCancelled) {
+        public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
             // It is only needed when freezing display in legacy transition.
             if (mTransitionController.isShellTransitionsEnabled()) return;
             continueUpdateOrientationForDiffOrienLaunchingApp();
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 24cef31..8e06a81 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -19,15 +19,19 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.view.Display.TYPE_INTERNAL;
+import static android.view.InsetsState.ITYPE_BOTTOM_DISPLAY_CUTOUT;
 import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
 import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
 import static android.view.InsetsState.ITYPE_CAPTION_BAR;
 import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
+import static android.view.InsetsState.ITYPE_LEFT_DISPLAY_CUTOUT;
 import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT;
 import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT;
 import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
 import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
@@ -103,6 +107,7 @@
 import android.content.res.Resources;
 import android.graphics.Insets;
 import android.graphics.PixelFormat;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.gui.DropInputMode;
@@ -127,6 +132,8 @@
 import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
 import android.view.InsetsVisibilities;
+import android.view.PrivacyIndicatorBounds;
+import android.view.RoundedCorners;
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewDebug;
@@ -136,7 +143,6 @@
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManagerGlobal;
-import android.view.WindowManagerPolicyConstants;
 import android.view.accessibility.AccessibilityManager;
 import android.window.ClientWindowFrames;
 
@@ -160,6 +166,7 @@
 import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.wallpaper.WallpaperManagerInternal;
+import com.android.server.wm.utils.WmDisplayCutout;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -383,6 +390,16 @@
     private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
     private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
 
+    // TODO (b/235842600): Use public type once we can treat task bar as navigation bar.
+    private static final int[] STABLE_TYPES = new int[]{
+            ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT, ITYPE_BOTTOM_DISPLAY_CUTOUT,
+            ITYPE_LEFT_DISPLAY_CUTOUT, ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR, ITYPE_CLIMATE_BAR
+    };
+    private static final int[] NON_DECOR_TYPES = new int[]{
+            ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT, ITYPE_BOTTOM_DISPLAY_CUTOUT,
+            ITYPE_LEFT_DISPLAY_CUTOUT, ITYPE_NAVIGATION_BAR
+    };
+
     private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
 
     private final WindowManagerInternal.AppTransitionListener mAppTransitionListener;
@@ -602,8 +619,9 @@
             }
 
             @Override
-            public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
-                    long statusBarAnimationDuration) {
+            public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
+                    boolean keyguardOccluding, long duration,
+                    long statusBarAnimationStartTime, long statusBarAnimationDuration) {
                 mHandler.post(() -> {
                     StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
                     if (statusBar != null) {
@@ -615,8 +633,7 @@
             }
 
             @Override
-            public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
-                    boolean keyguardOccludedCancelled) {
+            public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
                 mHandler.post(mAppTransitionCancelled);
             }
 
@@ -794,11 +811,11 @@
     }
 
     public void setAwake(boolean awake) {
-        if (awake == mAwake) {
-            return;
-        }
-        mAwake = awake;
-        synchronized (mService.mGlobalLock) {
+        synchronized (mLock) {
+            if (awake == mAwake) {
+                return;
+            }
+            mAwake = awake;
             if (!mDisplayContent.isDefaultDisplay) {
                 return;
             }
@@ -2007,35 +2024,6 @@
         return mUiContext;
     }
 
-    private int getNavigationBarWidth(int rotation, int uiMode, int position) {
-        if (mNavigationBar == null) {
-            return 0;
-        }
-        LayoutParams lp = mNavigationBar.mAttrs;
-        if (lp.paramsForRotation != null
-                && lp.paramsForRotation.length == 4
-                && lp.paramsForRotation[rotation] != null) {
-            lp = lp.paramsForRotation[rotation];
-        }
-        Insets providedInsetsSize = null;
-        if (lp.providedInsets != null) {
-            for (InsetsFrameProvider provider : lp.providedInsets) {
-                if (provider.type != ITYPE_NAVIGATION_BAR) {
-                    continue;
-                }
-                providedInsetsSize = provider.insetsSize;
-            }
-        }
-        if (providedInsetsSize != null) {
-            if (position == NAV_BAR_LEFT) {
-                return providedInsetsSize.left;
-            } else if (position == NAV_BAR_RIGHT) {
-                return providedInsetsSize.right;
-            }
-        }
-        return lp.width;
-    }
-
     @VisibleForTesting
     void setCanSystemBarsBeShownByUser(boolean canBeShown) {
         mCanSystemBarsBeShownByUser = canBeShown;
@@ -2057,45 +2045,24 @@
     }
 
     /**
-     * Return the display width available after excluding any screen
-     * decorations that could never be removed in Honeycomb. That is, system bar or
-     * button bar.
+     * Return the display frame available after excluding any screen decorations that could never be
+     * removed in Honeycomb. That is, system bar or button bar.
+     *
+     * @return display frame excluding all non-decor insets.
      */
-    public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode,
-            DisplayCutout displayCutout) {
-        int width = fullWidth;
-        if (hasNavigationBar()) {
-            final int navBarPosition = navigationBarPosition(rotation);
-            if (navBarPosition == NAV_BAR_LEFT || navBarPosition == NAV_BAR_RIGHT) {
-                width -= getNavigationBarWidth(rotation, uiMode, navBarPosition);
-            }
-        }
-        if (displayCutout != null) {
-            width -= displayCutout.getSafeInsetLeft() + displayCutout.getSafeInsetRight();
-        }
-        return width;
+    Rect getNonDecorDisplayFrame(int fullWidth, int fullHeight, int rotation,
+            WmDisplayCutout cutout) {
+        final DisplayFrames displayFrames =
+                getSimulatedDisplayFrames(rotation, fullWidth, fullHeight, cutout);
+        return getNonDecorDisplayFrameWithSimulatedFrame(displayFrames);
     }
 
-    @VisibleForTesting
-    int getNavigationBarHeight(int rotation) {
-        if (mNavigationBar == null) {
-            return 0;
-        }
-        LayoutParams lp = mNavigationBar.mAttrs.forRotation(rotation);
-        Insets providedInsetsSize = null;
-        if (lp.providedInsets != null) {
-            for (InsetsFrameProvider provider : lp.providedInsets) {
-                if (provider.type != ITYPE_NAVIGATION_BAR) {
-                    continue;
-                }
-                providedInsetsSize = provider.insetsSize;
-                if (providedInsetsSize != null) {
-                    return providedInsetsSize.bottom;
-                }
-                break;
-            }
-        }
-        return lp.height;
+    Rect getNonDecorDisplayFrameWithSimulatedFrame(DisplayFrames displayFrames) {
+        final Rect nonDecorInsets =
+                getInsetsWithInternalTypes(displayFrames, NON_DECOR_TYPES).toRect();
+        final Rect displayFrame = new Rect(displayFrames.mInsetsState.getDisplayFrame());
+        displayFrame.inset(nonDecorInsets);
+        return displayFrame;
     }
 
     /**
@@ -2117,53 +2084,23 @@
     }
 
     /**
-     * Return the display height available after excluding any screen
-     * decorations that could never be removed in Honeycomb. That is, system bar or
-     * button bar.
-     */
-    public int getNonDecorDisplayHeight(int fullHeight, int rotation, DisplayCutout displayCutout) {
-        int height = fullHeight;
-        final int navBarPosition = navigationBarPosition(rotation);
-        if (navBarPosition == NAV_BAR_BOTTOM) {
-            height -= getNavigationBarHeight(rotation);
-        }
-        if (displayCutout != null) {
-            height -= displayCutout.getSafeInsetTop() + displayCutout.getSafeInsetBottom();
-        }
-        return height;
-    }
-
-    /**
-     * Return the available screen width that we should report for the
+     * Return the available screen size that we should report for the
      * configuration.  This must be no larger than
-     * {@link #getNonDecorDisplayWidth(int, int, int, int, DisplayCutout)}; it may be smaller
+     * {@link #getNonDecorDisplayFrame(int, int, int, DisplayCutout)}; it may be smaller
      * than that to account for more transient decoration like a status bar.
      */
-    public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode,
-            DisplayCutout displayCutout) {
-        return getNonDecorDisplayWidth(fullWidth, fullHeight, rotation, uiMode, displayCutout);
+    public Point getConfigDisplaySize(int fullWidth, int fullHeight, int rotation,
+            WmDisplayCutout wmDisplayCutout) {
+        final DisplayFrames displayFrames = getSimulatedDisplayFrames(rotation, fullWidth,
+                fullHeight, wmDisplayCutout);
+        return getConfigDisplaySizeWithSimulatedFrame(displayFrames);
     }
 
-    /**
-     * Return the available screen height that we should report for the
-     * configuration.  This must be no larger than
-     * {@link #getNonDecorDisplayHeight(int, int, DisplayCutout)}; it may be smaller
-     * than that to account for more transient decoration like a status bar.
-     */
-    public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation, int uiMode,
-            DisplayCutout displayCutout) {
-        // There is a separate status bar at the top of the display.  We don't count that as part
-        // of the fixed decor, since it can hide; however, for purposes of configurations,
-        // we do want to exclude it since applications can't generally use that part
-        // of the screen.
-        int statusBarHeight = mStatusBarHeightForRotation[rotation];
-        if (displayCutout != null) {
-            // If there is a cutout, it may already have accounted for some part of the status
-            // bar height.
-            statusBarHeight = Math.max(0, statusBarHeight - displayCutout.getSafeInsetTop());
-        }
-        return getNonDecorDisplayHeight(fullHeight, rotation, displayCutout)
-                - statusBarHeight;
+    Point getConfigDisplaySizeWithSimulatedFrame(DisplayFrames displayFrames) {
+        final Insets insets = getInsetsWithInternalTypes(displayFrames, STABLE_TYPES);
+        Rect configFrame = new Rect(displayFrames.mInsetsState.getDisplayFrame());
+        configFrame.inset(insets);
+        return new Point(configFrame.width(), configFrame.height());
     }
 
     /**
@@ -2195,48 +2132,75 @@
      * Calculates the stable insets without running a layout.
      *
      * @param displayRotation the current display rotation
+     * @param displayWidth full display width
+     * @param displayHeight full display height
      * @param displayCutout the current display cutout
      * @param outInsets the insets to return
      */
-    public void getStableInsetsLw(int displayRotation, DisplayCutout displayCutout,
-            Rect outInsets) {
-        outInsets.setEmpty();
+    public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight,
+            WmDisplayCutout displayCutout, Rect outInsets) {
+        final DisplayFrames displayFrames = getSimulatedDisplayFrames(displayRotation,
+                displayWidth, displayHeight, displayCutout);
+        getStableInsetsWithSimulatedFrame(displayFrames, outInsets);
+    }
 
-        // Navigation bar and status bar.
-        getNonDecorInsetsLw(displayRotation, displayCutout, outInsets);
-        convertNonDecorInsetsToStableInsets(outInsets, displayRotation);
+    void getStableInsetsWithSimulatedFrame(DisplayFrames displayFrames, Rect outInsets) {
+        // Navigation bar, status bar, and cutout.
+        outInsets.set(getInsetsWithInternalTypes(displayFrames, STABLE_TYPES).toRect());
     }
 
     /**
      * Calculates the insets for the areas that could never be removed in Honeycomb, i.e. system
-     * bar or button bar. See {@link #getNonDecorDisplayWidth}.
-     *  @param displayRotation the current display rotation
-     * @param displayCutout the current display cutout
+     * bar or button bar. See {@link #getNonDecorDisplayFrame}.
+     *
+     * @param displayRotation the current display rotation
+     * @param fullWidth the width of the display, including all insets
+     * @param fullHeight the height of the display, including all insets
+     * @param cutout the current display cutout
      * @param outInsets the insets to return
      */
-    public void getNonDecorInsetsLw(int displayRotation, DisplayCutout displayCutout,
-            Rect outInsets) {
-        outInsets.setEmpty();
+    public void getNonDecorInsetsLw(int displayRotation, int fullWidth, int fullHeight,
+            WmDisplayCutout cutout, Rect outInsets) {
+        final DisplayFrames displayFrames =
+                getSimulatedDisplayFrames(displayRotation, fullWidth, fullHeight, cutout);
+        getNonDecorInsetsWithSimulatedFrame(displayFrames, outInsets);
+    }
 
-        // Only navigation bar
-        if (hasNavigationBar()) {
-            final int uiMode = mService.mPolicy.getUiMode();
-            int position = navigationBarPosition(displayRotation);
-            if (position == NAV_BAR_BOTTOM) {
-                outInsets.bottom = getNavigationBarHeight(displayRotation);
-            } else if (position == NAV_BAR_RIGHT) {
-                outInsets.right = getNavigationBarWidth(displayRotation, uiMode, position);
-            } else if (position == NAV_BAR_LEFT) {
-                outInsets.left = getNavigationBarWidth(displayRotation, uiMode, position);
-            }
-        }
+    void getNonDecorInsetsWithSimulatedFrame(DisplayFrames displayFrames, Rect outInsets) {
+        outInsets.set(getInsetsWithInternalTypes(displayFrames, NON_DECOR_TYPES).toRect());
+    }
 
-        if (displayCutout != null) {
-            outInsets.left += displayCutout.getSafeInsetLeft();
-            outInsets.top += displayCutout.getSafeInsetTop();
-            outInsets.right += displayCutout.getSafeInsetRight();
-            outInsets.bottom += displayCutout.getSafeInsetBottom();
-        }
+    DisplayFrames getSimulatedDisplayFrames(int displayRotation, int fullWidth,
+            int fullHeight, WmDisplayCutout cutout) {
+        final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo());
+        info.rotation = displayRotation;
+        info.logicalWidth = fullWidth;
+        info.logicalHeight = fullHeight;
+        info.displayCutout = cutout.getDisplayCutout();
+        final RoundedCorners roundedCorners =
+                mDisplayContent.calculateRoundedCornersForRotation(displayRotation);
+        final PrivacyIndicatorBounds indicatorBounds =
+                mDisplayContent.calculatePrivacyIndicatorBoundsForRotation(displayRotation);
+        final DisplayFrames displayFrames = new DisplayFrames(getDisplayId(), new InsetsState(),
+                info, cutout, roundedCorners, indicatorBounds);
+        simulateLayoutDisplay(displayFrames);
+        return displayFrames;
+    }
+
+    @VisibleForTesting
+    Insets getInsets(DisplayFrames displayFrames, @InsetsType int type) {
+        final InsetsState state = displayFrames.mInsetsState;
+        final Insets insets = state.calculateInsets(state.getDisplayFrame(), type,
+                true /* ignoreVisibility */);
+        return insets;
+    }
+
+    Insets getInsetsWithInternalTypes(DisplayFrames displayFrames,
+            @InternalInsetsType int[] types) {
+        final InsetsState state = displayFrames.mInsetsState;
+        final Insets insets = state.calculateInsetsWithInternalTypes(state.getDisplayFrame(), types,
+                true /* ignoreVisibility */);
+        return insets;
     }
 
     @NavigationBarPosition
@@ -2256,17 +2220,6 @@
     }
 
     /**
-     * @return The side of the screen where navigation bar is positioned.
-     * @see WindowManagerPolicyConstants#NAV_BAR_LEFT
-     * @see WindowManagerPolicyConstants#NAV_BAR_RIGHT
-     * @see WindowManagerPolicyConstants#NAV_BAR_BOTTOM
-     */
-    @NavigationBarPosition
-    public int getNavBarPosition() {
-        return mNavigationBarPosition;
-    }
-
-    /**
      * A new window has been focused.
      */
     public void focusChangedLw(WindowState lastFocus, WindowState newFocus) {
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index df3109a..27550d9 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -56,6 +56,7 @@
     private final Supplier<Boolean> mHasWallpaperBackgroundSupplier;
     private final Supplier<Integer> mBlurRadiusSupplier;
     private final Supplier<Float> mDarkScrimAlphaSupplier;
+    private final Supplier<SurfaceControl> mParentSurfaceSupplier;
 
     private final Rect mOuter = new Rect();
     private final Rect mInner = new Rect();
@@ -87,7 +88,8 @@
             Supplier<Integer> blurRadiusSupplier,
             Supplier<Float> darkScrimAlphaSupplier,
             IntConsumer doubleTapCallbackX,
-            IntConsumer doubleTapCallbackY) {
+            IntConsumer doubleTapCallbackY,
+            Supplier<SurfaceControl> parentSurface) {
         mSurfaceControlFactory = surfaceControlFactory;
         mTransactionFactory = transactionFactory;
         mAreCornersRounded = areCornersRounded;
@@ -97,6 +99,7 @@
         mDarkScrimAlphaSupplier = darkScrimAlphaSupplier;
         mDoubleTapCallbackX = doubleTapCallbackX;
         mDoubleTapCallbackY = doubleTapCallbackY;
+        mParentSurfaceSupplier = parentSurface;
     }
 
     /**
@@ -121,7 +124,6 @@
         mFullWindowSurface.layout(outer.left, outer.top, outer.right, outer.bottom, surfaceOrigin);
     }
 
-
     /**
      * Gets the insets between the outer and inner rects.
      */
@@ -333,6 +335,7 @@
         private SurfaceControl mSurface;
         private Color mColor;
         private boolean mHasWallpaperBackground;
+        private SurfaceControl mParentSurface;
 
         private final Rect mSurfaceFrameRelative = new Rect();
         private final Rect mLayoutFrameGlobal = new Rect();
@@ -403,10 +406,12 @@
                 }
 
                 mColor = mColorSupplier.get();
+                mParentSurface = mParentSurfaceSupplier.get();
                 t.setColor(mSurface, getRgbColorArray());
                 t.setPosition(mSurface, mSurfaceFrameRelative.left, mSurfaceFrameRelative.top);
                 t.setWindowCrop(mSurface, mSurfaceFrameRelative.width(),
                         mSurfaceFrameRelative.height());
+                t.reparent(mSurface, mParentSurface);
 
                 mHasWallpaperBackground = mHasWallpaperBackgroundSupplier.get();
                 updateAlphaAndBlur(t);
@@ -452,12 +457,13 @@
 
         public boolean needsApplySurfaceChanges() {
             return !mSurfaceFrameRelative.equals(mLayoutFrameRelative)
-                    // If mSurfaceFrameRelative is empty then mHasWallpaperBackground and mColor
-                    // may never be updated in applySurfaceChanges but this doesn't mean that
-                    // update is needed.
+                    // If mSurfaceFrameRelative is empty then mHasWallpaperBackground, mColor,
+                    // and mParentSurface may never be updated in applySurfaceChanges but this
+                    // doesn't mean that update is needed.
                     || !mSurfaceFrameRelative.isEmpty()
                     && (mHasWallpaperBackgroundSupplier.get() != mHasWallpaperBackground
-                    || !mColorSupplier.get().equals(mColor));
+                    || !mColorSupplier.get().equals(mColor)
+                    || mParentSurfaceSupplier.get() != mParentSurface);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 57c60f4..a469c6b 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -265,7 +265,7 @@
     }
 
     /**
-     * Overrides corners raidus for activities presented in the letterbox mode. If given value < 0,
+     * Overrides corners radius for activities presented in the letterbox mode. If given value < 0,
      * both it and a value of {@link
      * com.android.internal.R.integer.config_letterboxActivityCornersRadius} will be ignored and
      * corners of the activity won't be rounded.
@@ -275,7 +275,7 @@
     }
 
     /**
-     * Resets corners raidus for activities presented in the letterbox mode to {@link
+     * Resets corners radius for activities presented in the letterbox mode to {@link
      * com.android.internal.R.integer.config_letterboxActivityCornersRadius}.
      */
     void resetLetterboxActivityCornersRadius() {
@@ -291,7 +291,7 @@
     }
 
     /**
-     * Gets corners raidus for activities presented in the letterbox mode.
+     * Gets corners radius for activities presented in the letterbox mode.
      */
     int getLetterboxActivityCornersRadius() {
         return mLetterboxActivityCornersRadius;
@@ -318,7 +318,7 @@
     /**
      * Sets color of letterbox background which is used when {@link
      * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as
-     * fallback for other backfround types.
+     * fallback for other background types.
      */
     void setLetterboxBackgroundColor(Color color) {
         mLetterboxBackgroundColorOverride = color;
@@ -327,7 +327,7 @@
     /**
      * Sets color ID of letterbox background which is used when {@link
      * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as
-     * fallback for other backfround types.
+     * fallback for other background types.
      */
     void setLetterboxBackgroundColorResourceId(int colorId) {
         mLetterboxBackgroundColorResourceIdOverride = colorId;
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index c8ed602..317c93e 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -89,7 +89,7 @@
 
     // Taskbar expanded height. Used to determine whether to crop an app window to display rounded
     // corners above the taskbar.
-    private float mExpandedTaskBarHeight;
+    private final float mExpandedTaskBarHeight;
 
     private boolean mShowWallpaperForLetterboxBackground;
 
@@ -120,7 +120,7 @@
         }
     }
 
-    boolean hasWallpaperBackgroudForLetterbox() {
+    boolean hasWallpaperBackgroundForLetterbox() {
         return mShowWallpaperForLetterboxBackground;
     }
 
@@ -137,6 +137,11 @@
     void getLetterboxInnerBounds(Rect outBounds) {
         if (mLetterbox != null) {
             outBounds.set(mLetterbox.getInnerFrame());
+            final WindowState w = mActivityRecord.findMainWindow();
+            if (w == null) {
+                return;
+            }
+            adjustBoundsForTaskbar(w, outBounds);
         } else {
             outBounds.setEmpty();
         }
@@ -160,13 +165,17 @@
     }
 
     void updateLetterboxSurface(WindowState winHint) {
+        updateLetterboxSurface(winHint, mActivityRecord.getSyncTransaction());
+    }
+
+    void updateLetterboxSurface(WindowState winHint, Transaction t) {
         final WindowState w = mActivityRecord.findMainWindow();
         if (w != winHint && winHint != null && w != null) {
             return;
         }
         layoutLetterbox(winHint);
         if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) {
-            mLetterbox.applySurfaceChanges(mActivityRecord.getSyncTransaction());
+            mLetterbox.applySurfaceChanges(t);
         }
     }
 
@@ -191,14 +200,22 @@
                         mActivityRecord.mWmService.mTransactionFactory,
                         this::shouldLetterboxHaveRoundedCorners,
                         this::getLetterboxBackgroundColor,
-                        this::hasWallpaperBackgroudForLetterbox,
+                        this::hasWallpaperBackgroundForLetterbox,
                         this::getLetterboxWallpaperBlurRadius,
                         this::getLetterboxWallpaperDarkScrimAlpha,
                         this::handleHorizontalDoubleTap,
-                        this::handleVerticalDoubleTap);
+                        this::handleVerticalDoubleTap,
+                        this::getLetterboxParentSurface);
                 mLetterbox.attachInput(w);
             }
-            mActivityRecord.getPosition(mTmpPoint);
+
+            if (mActivityRecord.isInLetterboxAnimation()) {
+                // In this case we attach the letterbox to the task instead of the activity.
+                mActivityRecord.getTask().getPosition(mTmpPoint);
+            } else {
+                mActivityRecord.getPosition(mTmpPoint);
+            }
+
             // Get the bounds of the "space-to-fill". The transformed bounds have the highest
             // priority because the activity is launched in a rotated environment. In multi-window
             // mode, the task-level represents this. In fullscreen-mode, the task container does
@@ -215,6 +232,13 @@
         }
     }
 
+    SurfaceControl getLetterboxParentSurface() {
+        if (mActivityRecord.isInLetterboxAnimation()) {
+            return mActivityRecord.getTask().getSurfaceControl();
+        }
+        return mActivityRecord.getSurfaceControl();
+    }
+
     private boolean shouldLetterboxHaveRoundedCorners() {
         // TODO(b/214030873): remove once background is drawn for transparent activities
         // Letterbox shouldn't have rounded corners if the activity is transparent
@@ -436,7 +460,7 @@
                 }
                 break;
             case LETTERBOX_BACKGROUND_WALLPAPER:
-                if (hasWallpaperBackgroudForLetterbox()) {
+                if (hasWallpaperBackgroundForLetterbox()) {
                     // Color is used for translucent scrim that dims wallpaper.
                     return Color.valueOf(Color.BLACK);
                 }
@@ -459,15 +483,14 @@
     private void updateRoundedCorners(WindowState mainWindow) {
         final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface();
         if (windowSurface != null && windowSurface.isValid()) {
-            Transaction transaction = mActivityRecord.getSyncTransaction();
+            final Transaction transaction = mActivityRecord.getSyncTransaction();
 
-            final InsetsState insetsState = mainWindow.getInsetsState();
-            final InsetsSource taskbarInsetsSource =
-                    insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
-
-            if (!isLetterboxedNotForDisplayCutout(mainWindow)
-                    || !mLetterboxConfiguration.isLetterboxActivityCornersRounded()
-                    || taskbarInsetsSource == null) {
+            if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) {
+                // We don't want corner radius on the window.
+                // In the case the ActivityRecord requires a letterboxed animation we never want
+                // rounded corners on the window because rounded corners are applied at the
+                // animation-bounds surface level and rounded corners on the window would interfere
+                // with that leading to unexpected rounded corner positioning during the animation.
                 transaction
                         .setWindowCrop(windowSurface, null)
                         .setCornerRadius(windowSurface, 0);
@@ -476,48 +499,89 @@
 
             Rect cropBounds = null;
 
-            // Rounded corners should be displayed above the taskbar. When taskbar is hidden,
-            // an insets frame is equal to a navigation bar which shouldn't affect position of
-            // rounded corners since apps are expected to handle navigation bar inset.
-            // This condition checks whether the taskbar is visible.
-            // Do not crop the taskbar inset if the window is in immersive mode - the user can
-            // swipe to show/hide the taskbar as an overlay.
-            if (taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight
-                    && taskbarInsetsSource.isVisible()) {
+            if (hasVisibleTaskbar(mainWindow)) {
                 cropBounds = new Rect(mActivityRecord.getBounds());
                 // Activity bounds are in screen coordinates while (0,0) for activity's surface
                 // control is at the top left corner of an app window so offsetting bounds
                 // accordingly.
                 cropBounds.offsetTo(0, 0);
-                // Rounded cornerners should be displayed above the taskbar.
-                cropBounds.bottom =
-                        Math.min(cropBounds.bottom, taskbarInsetsSource.getFrame().top);
-                if (mActivityRecord.inSizeCompatMode()
-                        && mActivityRecord.getSizeCompatScale() < 1.0f) {
-                    cropBounds.scale(1.0f / mActivityRecord.getSizeCompatScale());
-                }
+                // Rounded corners should be displayed above the taskbar.
+                adjustBoundsForTaskbarUnchecked(mainWindow, cropBounds);
             }
 
             transaction
                     .setWindowCrop(windowSurface, cropBounds)
-                    .setCornerRadius(windowSurface, getRoundedCorners(insetsState));
+                    .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow));
         }
     }
 
-    // Returns rounded corners radius based on override in
+    private boolean requiresRoundedCorners(WindowState mainWindow) {
+        final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow);
+
+        return isLetterboxedNotForDisplayCutout(mainWindow)
+                && mLetterboxConfiguration.isLetterboxActivityCornersRounded()
+                && taskbarInsetsSource != null;
+    }
+
+    // Returns rounded corners radius the letterboxed activity should have based on override in
     // R.integer.config_letterboxActivityCornersRadius or min device bottom corner radii.
     // Device corners can be different on the right and left sides but we use the same radius
     // for all corners for consistency and pick a minimal bottom one for consistency with a
     // taskbar rounded corners.
-    private int getRoundedCorners(InsetsState insetsState) {
+    int getRoundedCornersRadius(WindowState mainWindow) {
+        if (!requiresRoundedCorners(mainWindow)) {
+            return 0;
+        }
+
         if (mLetterboxConfiguration.getLetterboxActivityCornersRadius() >= 0) {
             return mLetterboxConfiguration.getLetterboxActivityCornersRadius();
         }
+
+        final InsetsState insetsState = mainWindow.getInsetsState();
         return Math.min(
                 getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT),
                 getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT));
     }
 
+    /**
+     * Returns whether the taskbar is visible. Returns false if the window is in immersive mode,
+     * since the user can swipe to show/hide the taskbar as an overlay.
+     */
+    private boolean hasVisibleTaskbar(WindowState mainWindow) {
+        final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow);
+
+        return taskbarInsetsSource != null
+                && taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight
+                && taskbarInsetsSource.isVisible();
+    }
+
+    private InsetsSource getTaskbarInsetsSource(WindowState mainWindow) {
+        final InsetsState insetsState = mainWindow.getInsetsState();
+        return insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+    }
+
+    private void adjustBoundsForTaskbar(WindowState mainWindow, Rect bounds) {
+        // Rounded corners should be displayed above the taskbar. When taskbar is hidden,
+        // an insets frame is equal to a navigation bar which shouldn't affect position of
+        // rounded corners since apps are expected to handle navigation bar inset.
+        // This condition checks whether the taskbar is visible.
+        // Do not crop the taskbar inset if the window is in immersive mode - the user can
+        // swipe to show/hide the taskbar as an overlay.
+        if (hasVisibleTaskbar(mainWindow)) {
+            adjustBoundsForTaskbarUnchecked(mainWindow, bounds);
+        }
+    }
+
+    private void adjustBoundsForTaskbarUnchecked(WindowState mainWindow, Rect bounds) {
+        // Rounded corners should be displayed above the taskbar.
+        bounds.bottom =
+                Math.min(bounds.bottom, getTaskbarInsetsSource(mainWindow).getFrame().top);
+        if (mActivityRecord.inSizeCompatMode()
+                && mActivityRecord.getSizeCompatScale() < 1.0f) {
+            bounds.scale(1.0f / mActivityRecord.getSizeCompatScale());
+        }
+    }
+
     private int getInsetsStateCornerRadius(
                 InsetsState insetsState, @RoundedCorner.Position int position) {
         RoundedCorner corner = insetsState.getRoundedCorners().getRoundedCorner(position);
@@ -592,7 +656,7 @@
                 + letterboxBackgroundTypeToString(
                         mLetterboxConfiguration.getLetterboxBackgroundType()));
         pw.println(prefix + "  letterboxCornerRadius="
-                + getRoundedCorners(mainWin.getInsetsState()));
+                + getRoundedCornersRadius(mainWin));
         if (mLetterboxConfiguration.getLetterboxBackgroundType()
                 == LETTERBOX_BACKGROUND_WALLPAPER) {
             pw.println(prefix + "  isLetterboxWallpaperBlurSupported="
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index db79eae..5b702ea 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -161,15 +161,15 @@
      */
     final AppTransitionListener mAppTransitionListener = new AppTransitionListener() {
         @Override
-        public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
+        public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
+                boolean keyguardOccluding, long duration, long statusBarAnimationStartTime,
                 long statusBarAnimationDuration) {
             continueDeferredCancel();
             return 0;
         }
 
         @Override
-        public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
-                boolean keyguardOccludedCancelled) {
+        public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
             continueDeferredCancel();
         }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 552c6a5..077f8b5 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -512,7 +512,7 @@
     void onChildPositionChanged(WindowContainer child) {
         mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
                 !mWmService.mPerDisplayFocusEnabled /* updateInputWindows */);
-        mTaskSupervisor.updateTopResumedActivityIfNeeded();
+        mTaskSupervisor.updateTopResumedActivityIfNeeded("onChildPositionChanged");
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index c7f8a1e..f3670e4 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -27,7 +27,10 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
 import android.graphics.Insets;
+import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.hardware.power.Boost;
@@ -374,43 +377,45 @@
         final int targetSurfaceWidth = bounds.width();
 
         if (maxExtensionInsets.left < 0) {
-            final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight);
+            final Rect edgeBounds = new Rect(bounds.left, bounds.top, bounds.left + 1,
+                    bounds.bottom);
             final Rect extensionRect = new Rect(0, 0,
                     -maxExtensionInsets.left, targetSurfaceHeight);
-            final int xPos = maxExtensionInsets.left;
-            final int yPos = 0;
+            final int xPos = bounds.left + maxExtensionInsets.left;
+            final int yPos = bounds.top;
             createExtensionSurface(leash, edgeBounds,
                     extensionRect, xPos, yPos, "Left Edge Extension", transaction);
         }
 
         if (maxExtensionInsets.top < 0) {
-            final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1);
+            final Rect edgeBounds = new Rect(bounds.left, bounds.top, targetSurfaceWidth,
+                    bounds.top + 1);
             final Rect extensionRect = new Rect(0, 0,
                     targetSurfaceWidth, -maxExtensionInsets.top);
-            final int xPos = 0;
-            final int yPos = maxExtensionInsets.top;
+            final int xPos = bounds.left;
+            final int yPos = bounds.top + maxExtensionInsets.top;
             createExtensionSurface(leash, edgeBounds,
                     extensionRect, xPos, yPos, "Top Edge Extension", transaction);
         }
 
         if (maxExtensionInsets.right < 0) {
-            final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0,
-                    targetSurfaceWidth, targetSurfaceHeight);
+            final Rect edgeBounds = new Rect(bounds.right - 1, bounds.top, bounds.right,
+                    bounds.bottom);
             final Rect extensionRect = new Rect(0, 0,
                     -maxExtensionInsets.right, targetSurfaceHeight);
-            final int xPos = targetSurfaceWidth;
-            final int yPos = 0;
+            final int xPos = bounds.right;
+            final int yPos = bounds.top;
             createExtensionSurface(leash, edgeBounds,
                     extensionRect, xPos, yPos, "Right Edge Extension", transaction);
         }
 
         if (maxExtensionInsets.bottom < 0) {
-            final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1,
-                    targetSurfaceWidth, targetSurfaceHeight);
+            final Rect edgeBounds = new Rect(bounds.left, bounds.bottom - 1,
+                    bounds.right, bounds.bottom);
             final Rect extensionRect = new Rect(0, 0,
                     targetSurfaceWidth, -maxExtensionInsets.bottom);
-            final int xPos = maxExtensionInsets.left;
-            final int yPos = targetSurfaceHeight;
+            final int xPos = bounds.left;
+            final int yPos = bounds.bottom;
             createExtensionSurface(leash, edgeBounds,
                     extensionRect, xPos, yPos, "Bottom Edge Extension", transaction);
         }
@@ -453,16 +458,20 @@
                 .setHidden(true)
                 .setCallsite("DefaultTransitionHandler#startAnimation")
                 .setOpaque(true)
-                .setBufferSize(edgeBounds.width(), edgeBounds.height())
+                .setBufferSize(extensionRect.width(), extensionRect.height())
                 .build();
 
-        final Surface surface = new Surface(edgeExtensionLayer);
-        surface.attachAndQueueBufferWithColorSpace(edgeBuffer.getHardwareBuffer(),
-                edgeBuffer.getColorSpace());
-        surface.release();
+        BitmapShader shader = new BitmapShader(edgeBuffer.asBitmap(),
+                android.graphics.Shader.TileMode.CLAMP,
+                android.graphics.Shader.TileMode.CLAMP);
+        final Paint paint = new Paint();
+        paint.setShader(shader);
 
-        final float scaleX = getScaleXForExtensionSurface(edgeBounds, extensionRect);
-        final float scaleY = getScaleYForExtensionSurface(edgeBounds, extensionRect);
+        final Surface surface = new Surface(edgeExtensionLayer);
+        Canvas c = surface.lockHardwareCanvas();
+        c.drawRect(extensionRect, paint);
+        surface.unlockCanvasAndPost(c);
+        surface.release();
 
         synchronized (mEdgeExtensionLock) {
             if (!mEdgeExtensions.containsKey(leash)) {
@@ -472,7 +481,6 @@
                 return;
             }
 
-            startTransaction.setScale(edgeExtensionLayer, scaleX, scaleY);
             startTransaction.reparent(edgeExtensionLayer, leash);
             startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
             startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
@@ -508,8 +516,6 @@
         throw new RuntimeException("Unexpected edgeBounds and extensionRect heights");
     }
 
-
-
     private static final class RunningAnimation {
         final AnimationSpec mAnimSpec;
         final SurfaceControl mLeash;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 0332935..e38f5fe 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -920,7 +920,7 @@
                 // If the original state is resumed, there is no state change to update focused app.
                 // So here makes sure the activity focus is set if it is the top.
                 if (r.isState(RESUMED) && r == mRootWindowContainer.getTopResumedActivity()) {
-                    mAtmService.setResumedActivityUncheckLocked(r, reason);
+                    mAtmService.setLastResumedActivityUncheckLocked(r, reason);
                 }
             }
             if (!animate) {
@@ -1883,8 +1883,7 @@
         }
 
         final int newWinMode = getWindowingMode();
-        if ((prevWinMode != newWinMode) && (mDisplayContent != null)
-                && shouldStartChangeTransition(prevWinMode, newWinMode)) {
+        if (shouldStartChangeTransition(prevWinMode, mTmpPrevBounds)) {
             initializeChangeTransition(mTmpPrevBounds);
         }
 
@@ -2135,10 +2134,16 @@
         bounds.offset(horizontalDiff, verticalDiff);
     }
 
-    private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) {
+    private boolean shouldStartChangeTransition(int prevWinMode, @NonNull Rect prevBounds) {
         if (!isLeafTask() || !canStartChangeTransition()) {
             return false;
         }
+        final int newWinMode = getWindowingMode();
+        if (mTransitionController.inTransition(this)) {
+            final Rect newBounds = getConfiguration().windowConfiguration.getBounds();
+            return prevWinMode != newWinMode || prevBounds.width() != newBounds.width()
+                    || prevBounds.height() != newBounds.height();
+        }
         // Only do an animation into and out-of freeform mode for now. Other mode
         // transition animations are currently handled by system-ui.
         return (prevWinMode == WINDOWING_MODE_FREEFORM) != (newWinMode == WINDOWING_MODE_FREEFORM);
@@ -2433,11 +2438,7 @@
         focusableTask.moveToFront(myReason);
         // Top display focused root task is changed, update top resumed activity if needed.
         if (rootTask.getTopResumedActivity() != null) {
-            mTaskSupervisor.updateTopResumedActivityIfNeeded();
-            // Set focused app directly because if the next focused activity is already resumed
-            // (e.g. the next top activity is on a different display), there won't have activity
-            // state change to update it.
-            mAtmService.setResumedActivityUncheckLocked(rootTask.getTopResumedActivity(), reason);
+            mTaskSupervisor.updateTopResumedActivityIfNeeded(reason);
         }
         return rootTask;
     }
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 52bf220..4063cae4 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -323,6 +323,10 @@
             // Clear preferred top because the adding focusable task has a higher z-order.
             mPreferredTopFocusableRootTask = null;
         }
+
+        // Update the top resumed activity because the preferred top focusable task may be changed.
+        mAtmService.mTaskSupervisor.updateTopResumedActivityIfNeeded("addChildTask");
+
         mAtmService.updateSleepIfNeededLocked();
         onRootTaskOrderChanged(task);
     }
@@ -416,12 +420,7 @@
         }
 
         // Update the top resumed activity because the preferred top focusable task may be changed.
-        mAtmService.mTaskSupervisor.updateTopResumedActivityIfNeeded();
-
-        final ActivityRecord r = child.getTopResumedActivity();
-        if (r != null && r == mRootWindowContainer.getTopResumedActivity()) {
-            mAtmService.setResumedActivityUncheckLocked(r, "positionChildAt");
-        }
+        mAtmService.mTaskSupervisor.updateTopResumedActivityIfNeeded("positionChildTaskAt");
 
         if (mChildren.indexOf(child) != oldPosition) {
             onRootTaskOrderChanged(child);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 44b5b88..8872b35 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -98,6 +98,7 @@
 import com.android.internal.util.function.pooled.PooledPredicate;
 import com.android.server.am.HostingRecord;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.wm.utils.WmDisplayCutout;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -460,7 +461,7 @@
 
         final ActivityRecord prevR = mResumedActivity;
         mResumedActivity = r;
-        mTaskSupervisor.updateTopResumedActivityIfNeeded();
+        mTaskSupervisor.updateTopResumedActivityIfNeeded(reason);
         if (r == null && prevR.mDisplayContent != null
                 && prevR.mDisplayContent.getFocusedRootTask() == null) {
             // Only need to notify DWPC when no activity will resume.
@@ -773,9 +774,6 @@
                 Slog.v(TAG, "set resumed activity to:" + record + " reason:" + reason);
             }
             setResumedActivity(record, reason + " - onActivityStateChanged");
-            if (record == mRootWindowContainer.getTopResumedActivity()) {
-                mAtmService.setResumedActivityUncheckLocked(record, reason);
-            }
             mTaskSupervisor.mRecentTasks.add(record.getTask());
         }
     }
@@ -1621,7 +1619,8 @@
                 ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, entering PIP mode "
                         + "directly: %s, didAutoPip: %b", prev, didAutoPip);
             } else {
-                schedulePauseActivity(prev, userLeaving, pauseImmediately, reason);
+                schedulePauseActivity(prev, userLeaving, pauseImmediately,
+                        false /* autoEnteringPip */, reason);
             }
         } else {
             mPausingActivity = null;
@@ -1675,7 +1674,7 @@
     }
 
     void schedulePauseActivity(ActivityRecord prev, boolean userLeaving,
-            boolean pauseImmediately, String reason) {
+            boolean pauseImmediately, boolean autoEnteringPip, String reason) {
         ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev);
         try {
             EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev),
@@ -1683,7 +1682,7 @@
 
             mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(),
                     prev.token, PauseActivityItem.obtain(prev.finishing, userLeaving,
-                            prev.configChangeFlags, pauseImmediately));
+                            prev.configChangeFlags, pauseImmediately, autoEnteringPip));
         } catch (Exception e) {
             // Ignore exception, if process died other code will cleanup.
             Slog.w(TAG, "Exception thrown during pause", e);
@@ -2210,11 +2209,13 @@
         mTmpBounds.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
 
         final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy();
-        policy.getNonDecorInsetsLw(displayInfo.rotation,
-                displayInfo.displayCutout, mTmpInsets);
+        final WmDisplayCutout cutout =
+                rootTask.mDisplayContent.calculateDisplayCutoutForRotation(displayInfo.rotation);
+        final DisplayFrames displayFrames = policy.getSimulatedDisplayFrames(displayInfo.rotation,
+                displayInfo.logicalWidth, displayInfo.logicalHeight, cutout);
+        policy.getNonDecorInsetsWithSimulatedFrame(displayFrames, mTmpInsets);
         intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, mTmpInsets);
-
-        policy.convertNonDecorInsetsToStableInsets(mTmpInsets, displayInfo.rotation);
+        policy.getStableInsetsWithSimulatedFrame(displayFrames, mTmpInsets);
         intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets);
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 88059e1..d615583 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -49,7 +49,9 @@
 import android.window.ITaskFragmentOrganizerController;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentTransaction;
+import android.window.WindowContainerTransaction;
 
+import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
 
 import java.lang.annotation.Retention;
@@ -68,6 +70,8 @@
 
     private final ActivityTaskManagerService mAtmService;
     private final WindowManagerGlobalLock mGlobalLock;
+    private final WindowOrganizerController mWindowOrganizerController;
+
     /**
      * A Map which manages the relationship between
      * {@link ITaskFragmentOrganizer} and {@link TaskFragmentOrganizerState}
@@ -82,9 +86,11 @@
 
     private final ArraySet<Task> mTmpTaskSet = new ArraySet<>();
 
-    TaskFragmentOrganizerController(ActivityTaskManagerService atm) {
-        mAtmService = atm;
+    TaskFragmentOrganizerController(@NonNull ActivityTaskManagerService atm,
+            @NonNull WindowOrganizerController windowOrganizerController) {
+        mAtmService = requireNonNull(atm);
         mGlobalLock = atm.mGlobalLock;
+        mWindowOrganizerController = requireNonNull(windowOrganizerController);
     }
 
     /**
@@ -131,6 +137,14 @@
         private final SparseArray<RemoteAnimationDefinition> mRemoteAnimationDefinitions =
                 new SparseArray<>();
 
+        /**
+         * List of {@link TaskFragmentTransaction#getTransactionToken()} that have been sent to the
+         * organizer. If the transaction is sent during a transition, the
+         * {@link TransitionController} will wait until the transaction is finished.
+         * @see #onTransactionFinished(IBinder)
+         */
+        private final List<IBinder> mRunningTransactions = new ArrayList<>();
+
         TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer, int pid, int uid) {
             mOrganizer = organizer;
             mOrganizerPid = pid;
@@ -176,6 +190,10 @@
                 taskFragment.removeImmediately();
                 mOrganizedTaskFragments.remove(taskFragment);
             }
+            for (int i = mRunningTransactions.size() - 1; i >= 0; i--) {
+                // Cleanup any running transaction to unblock the current transition.
+                onTransactionFinished(mRunningTransactions.get(i));
+            }
             mOrganizer.asBinder().unlinkToDeath(this, 0 /*flags*/);
         }
 
@@ -320,6 +338,40 @@
                     .setActivityIntent(activity.intent)
                     .setActivityToken(activityToken);
         }
+
+        void dispatchTransaction(@NonNull TaskFragmentTransaction transaction) {
+            if (transaction.isEmpty()) {
+                return;
+            }
+            try {
+                mOrganizer.onTransactionReady(transaction);
+            } catch (RemoteException e) {
+                Slog.d(TAG, "Exception sending TaskFragmentTransaction", e);
+                return;
+            }
+            onTransactionStarted(transaction.getTransactionToken());
+        }
+
+        /** Called when the transaction is sent to the organizer. */
+        void onTransactionStarted(@NonNull IBinder transactionToken) {
+            if (!mWindowOrganizerController.getTransitionController().isCollecting()) {
+                return;
+            }
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                    "Defer transition ready for TaskFragmentTransaction=%s", transactionToken);
+            mRunningTransactions.add(transactionToken);
+            mWindowOrganizerController.getTransitionController().deferTransitionReady();
+        }
+
+        /** Called when the transaction is finished. */
+        void onTransactionFinished(@NonNull IBinder transactionToken) {
+            if (!mRunningTransactions.remove(transactionToken)) {
+                return;
+            }
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                    "Continue transition ready for TaskFragmentTransaction=%s", transactionToken);
+            mWindowOrganizerController.getTransitionController().continueTransitionReady();
+        }
     }
 
     @Nullable
@@ -336,7 +388,7 @@
     }
 
     @Override
-    public void registerOrganizer(ITaskFragmentOrganizer organizer) {
+    public void registerOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
         synchronized (mGlobalLock) {
@@ -354,7 +406,7 @@
     }
 
     @Override
-    public void unregisterOrganizer(ITaskFragmentOrganizer organizer) {
+    public void unregisterOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
         validateAndGetState(organizer);
         final int pid = Binder.getCallingPid();
         final long uid = Binder.getCallingUid();
@@ -372,8 +424,8 @@
     }
 
     @Override
-    public void registerRemoteAnimations(ITaskFragmentOrganizer organizer, int taskId,
-            RemoteAnimationDefinition definition) {
+    public void registerRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer, int taskId,
+            @NonNull RemoteAnimationDefinition definition) {
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
         synchronized (mGlobalLock) {
@@ -398,7 +450,7 @@
     }
 
     @Override
-    public void unregisterRemoteAnimations(ITaskFragmentOrganizer organizer, int taskId) {
+    public void unregisterRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer, int taskId) {
         final int pid = Binder.getCallingPid();
         final long uid = Binder.getCallingUid();
         synchronized (mGlobalLock) {
@@ -416,6 +468,17 @@
         }
     }
 
+    @Override
+    public void onTransactionHandled(@NonNull ITaskFragmentOrganizer organizer,
+            @NonNull IBinder transactionToken, @NonNull WindowContainerTransaction wct) {
+        synchronized (mGlobalLock) {
+            // Keep the calling identity to avoid unsecure change.
+            mWindowOrganizerController.applyTransaction(wct);
+            final TaskFragmentOrganizerState state = validateAndGetState(organizer);
+            state.onTransactionFinished(transactionToken);
+        }
+    }
+
     /**
      * Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns
      * {@code null} if it doesn't, or if the organizer has activity(ies) embedded in untrusted mode.
@@ -775,13 +838,13 @@
         }
         final int organizerNum = mPendingTaskFragmentEvents.size();
         for (int i = 0; i < organizerNum; i++) {
-            final ITaskFragmentOrganizer organizer = mTaskFragmentOrganizerState.get(
-                    mPendingTaskFragmentEvents.keyAt(i)).mOrganizer;
-            dispatchPendingEvents(organizer, mPendingTaskFragmentEvents.valueAt(i));
+            final TaskFragmentOrganizerState state =
+                    mTaskFragmentOrganizerState.get(mPendingTaskFragmentEvents.keyAt(i));
+            dispatchPendingEvents(state, mPendingTaskFragmentEvents.valueAt(i));
         }
     }
 
-    void dispatchPendingEvents(@NonNull ITaskFragmentOrganizer organizer,
+    void dispatchPendingEvents(@NonNull TaskFragmentOrganizerState state,
             @NonNull List<PendingTaskFragmentEvent> pendingEvents) {
         if (pendingEvents.isEmpty()) {
             return;
@@ -817,7 +880,7 @@
                 if (mTmpTaskSet.add(task)) {
                     // Make sure the organizer know about the Task config.
                     transaction.addChange(prepareChange(new PendingTaskFragmentEvent.Builder(
-                            PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED, organizer)
+                            PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED, state.mOrganizer)
                             .setTask(task)
                             .build()));
                 }
@@ -825,7 +888,7 @@
             transaction.addChange(prepareChange(event));
         }
         mTmpTaskSet.clear();
-        dispatchTransactionInfo(organizer, transaction);
+        state.dispatchTransaction(transaction);
         pendingEvents.removeAll(candidateEvents);
     }
 
@@ -855,6 +918,7 @@
         }
 
         final ITaskFragmentOrganizer organizer = taskFragment.getTaskFragmentOrganizer();
+        final TaskFragmentOrganizerState state = validateAndGetState(organizer);
         final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
         // Make sure the organizer know about the Task config.
         transaction.addChange(prepareChange(new PendingTaskFragmentEvent.Builder(
@@ -862,22 +926,10 @@
                 .setTask(taskFragment.getTask())
                 .build()));
         transaction.addChange(prepareChange(event));
-        dispatchTransactionInfo(event.mTaskFragmentOrg, transaction);
+        state.dispatchTransaction(transaction);
         mPendingTaskFragmentEvents.get(organizer.asBinder()).remove(event);
     }
 
-    private void dispatchTransactionInfo(@NonNull ITaskFragmentOrganizer organizer,
-            @NonNull TaskFragmentTransaction transaction) {
-        if (transaction.isEmpty()) {
-            return;
-        }
-        try {
-            organizer.onTransactionReady(transaction);
-        } catch (RemoteException e) {
-            Slog.d(TAG, "Exception sending TaskFragmentTransaction", e);
-        }
-    }
-
     @Nullable
     private TaskFragmentTransaction.Change prepareChange(
             @NonNull PendingTaskFragmentEvent event) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 6e0d411..80b7514 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -31,7 +32,13 @@
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -62,10 +69,12 @@
 import android.content.pm.ActivityInfo;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.Trace;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -73,6 +82,7 @@
 import android.util.SparseArray;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
+import android.view.animation.Animation;
 import android.window.RemoteTransition;
 import android.window.TransitionInfo;
 
@@ -197,6 +207,9 @@
     /** @see #setCanPipOnFinish */
     private boolean mCanPipOnFinish = true;
 
+    private boolean mIsSeamlessRotation = false;
+    private IContainerFreezer mContainerFreezer = null;
+
     Transition(@TransitionType int type, @TransitionFlags int flags,
             TransitionController controller, BLASTSyncEngine syncEngine) {
         mType = type;
@@ -257,10 +270,31 @@
         return mTargetDisplays.contains(dc);
     }
 
+    /** Set a transition to be a seamless-rotation. */
     void setSeamlessRotation(@NonNull WindowContainer wc) {
         final ChangeInfo info = mChanges.get(wc);
         if (info == null) return;
         info.mFlags = info.mFlags | ChangeInfo.FLAG_SEAMLESS_ROTATION;
+        onSeamlessRotating(wc.getDisplayContent());
+    }
+
+    /**
+     * Called when it's been determined that this is transition is a seamless rotation. This should
+     * be called before any WM changes have happened.
+     */
+    void onSeamlessRotating(@NonNull DisplayContent dc) {
+        // Don't need to do anything special if everything is using BLAST sync already.
+        if (mSyncEngine.getSyncSet(mSyncId).mSyncMethod == BLASTSyncEngine.METHOD_BLAST) return;
+        if (mContainerFreezer == null) {
+            mContainerFreezer = new ScreenshotFreezer();
+        }
+        mIsSeamlessRotation = true;
+        final WindowState top = dc.getDisplayPolicy().getTopFullscreenOpaqueWindow();
+        if (top != null) {
+            top.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST;
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Override sync-method for %s "
+                    + "because seamless rotating", top.getName());
+        }
     }
 
     /**
@@ -277,6 +311,11 @@
         }
     }
 
+    /** Only for testing. */
+    void setContainerFreezer(IContainerFreezer freezer) {
+        mContainerFreezer = freezer;
+    }
+
     @TransitionState
     int getState() {
         return mState;
@@ -306,13 +345,18 @@
         return mState == STATE_COLLECTING || mState == STATE_STARTED;
     }
 
-    /** Starts collecting phase. Once this starts, all relevant surface operations are sync. */
+    @VisibleForTesting
     void startCollecting(long timeoutMs) {
+        startCollecting(timeoutMs, TransitionController.SYNC_METHOD);
+    }
+
+    /** Starts collecting phase. Once this starts, all relevant surface operations are sync. */
+    void startCollecting(long timeoutMs, int method) {
         if (mState != STATE_PENDING) {
             throw new IllegalStateException("Attempting to re-use a transition");
         }
         mState = STATE_COLLECTING;
-        mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG);
+        mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG, method);
 
         mController.mTransitionTracer.logState(this);
     }
@@ -407,6 +451,37 @@
     }
 
     /**
+     * Records that a particular container is changing visibly (ie. something about it is changing
+     * while it remains visible). This only effects windows that are already in the collecting
+     * transition.
+     */
+    void collectVisibleChange(WindowContainer wc) {
+        if (mSyncEngine.getSyncSet(mSyncId).mSyncMethod == BLASTSyncEngine.METHOD_BLAST) {
+            // All windows are synced already.
+            return;
+        }
+        if (!isInTransition(wc)) return;
+
+        if (mContainerFreezer == null) {
+            mContainerFreezer = new ScreenshotFreezer();
+        }
+        Transition.ChangeInfo change = mChanges.get(wc);
+        if (change == null || !change.mVisible || !wc.isVisibleRequested()) return;
+        // Note: many more tests have already been done by caller.
+        mContainerFreezer.freeze(wc, change.mAbsoluteBounds);
+    }
+
+    /**
+     * @return {@code true} if `wc` is a participant or is a descendant of one.
+     */
+    boolean isInTransition(WindowContainer wc) {
+        for (WindowContainer p = wc; p != null; p = p.getParent()) {
+            if (mParticipants.contains(p)) return true;
+        }
+        return false;
+    }
+
+    /**
      * Specifies configuration change explicitly for the window container, so it can be chosen as
      * transition target. This is usually used with transition mode
      * {@link android.view.WindowManager#TRANSIT_CHANGE}.
@@ -523,6 +598,10 @@
                 displays.add(target.getDisplayContent());
             }
         }
+        // Remove screenshot layers if necessary
+        if (mContainerFreezer != null) {
+            mContainerFreezer.cleanUp(t);
+        }
         // Need to update layers on involved displays since they were all paused while
         // the animation played. This puts the layers back into the correct order.
         mController.mBuildingFinishLayers = true;
@@ -809,6 +888,19 @@
                 transaction);
         if (mOverrideOptions != null) {
             info.setAnimationOptions(mOverrideOptions);
+            if (mOverrideOptions.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) {
+                for (int i = 0; i < mTargets.size(); ++i) {
+                    final TransitionInfo.Change c = info.getChanges().get(i);
+                    final ActivityRecord ar = mTargets.get(i).asActivityRecord();
+                    if (ar == null || c.getMode() != TRANSIT_OPEN) continue;
+                    int flags = c.getFlags();
+                    flags |= ar.mUserId == ar.mWmService.mCurrentUserId
+                            ? TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL
+                            : TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
+                    c.setFlags(flags);
+                    break;
+                }
+            }
         }
 
         // TODO(b/188669821): Move to animation impl in shell.
@@ -1058,9 +1150,36 @@
 
     private void handleNonAppWindowsInTransition(@NonNull DisplayContent dc,
             @TransitionType int transit, @TransitionFlags int flags) {
+        if ((transit == TRANSIT_KEYGUARD_GOING_AWAY
+                || (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0)
+                && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
+            if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
+                    && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0
+                    && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) == 0) {
+                Animation anim = mController.mAtm.mWindowManager.mPolicy
+                        .createKeyguardWallpaperExit(
+                                (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0);
+                if (anim != null) {
+                    anim.scaleCurrentDuration(
+                            mController.mAtm.mWindowManager.getTransitionAnimationScaleLocked());
+                    dc.mWallpaperController.startWallpaperAnimation(anim);
+                }
+            }
+            dc.startKeyguardExitOnNonAppWindows(
+                    (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0,
+                    (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0,
+                    (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0);
+            if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
+                // When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI
+                // receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't
+                // need to call IKeyguardService#keyguardGoingAway here.
+                mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation(
+                        SystemClock.uptimeMillis(), 0 /* duration */);
+            }
+        }
         if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
             mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange(
-                    false /* notify */);
+                    true /* keyguardOccludingStarted */);
         }
     }
 
@@ -1780,6 +1899,8 @@
     /** This undoes one call to {@link #deferTransitionReady}. */
     void continueTransitionReady() {
         --mReadyTracker.mDeferReadyDepth;
+        // Apply ready in case it is waiting for the previous defer call.
+        applyReady();
     }
 
     /**
@@ -1945,4 +2066,111 @@
             return sortedTargets;
         }
     }
+
+    /**
+     * Interface for freezing a container's content during sync preparation. Really just one impl
+     * but broken into an interface for testing (since you can't take screenshots in unit tests).
+     */
+    interface IContainerFreezer {
+        /**
+         * Makes sure a particular window is "frozen" for the remainder of a sync.
+         *
+         * @return whether the freeze was successful. It fails if `wc` is already in a frozen window
+         *         or is not visible/ready.
+         */
+        boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds);
+
+        /** Populates `t` with operations that clean-up any state created to set-up the freeze. */
+        void cleanUp(SurfaceControl.Transaction t);
+    }
+
+    /**
+     * Freezes container content by taking a screenshot. Because screenshots are heavy, usage of
+     * any container "freeze" is currently explicit. WM code needs to be prudent about which
+     * containers to freeze.
+     */
+    @VisibleForTesting
+    private class ScreenshotFreezer implements IContainerFreezer {
+        /** Values are the screenshot "surfaces" or null if it was frozen via BLAST override. */
+        private final ArrayMap<WindowContainer, SurfaceControl> mSnapshots = new ArrayMap<>();
+
+        /** Takes a screenshot and puts it at the top of the container's surface. */
+        @Override
+        public boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds) {
+            if (!wc.isVisibleRequested()) return false;
+
+            // Check if any parents have already been "frozen". If so, `wc` is already part of that
+            // snapshot, so just skip it.
+            for (WindowContainer p = wc; p != null; p = p.getParent()) {
+                if (mSnapshots.containsKey(p)) return false;
+            }
+
+            if (mIsSeamlessRotation) {
+                WindowState top = wc.getDisplayContent() == null ? null
+                        : wc.getDisplayContent().getDisplayPolicy().getTopFullscreenOpaqueWindow();
+                if (top != null && (top == wc || top.isDescendantOf(wc))) {
+                    // Don't use screenshots for seamless windows: these will use BLAST even if not
+                    // BLAST mode.
+                    mSnapshots.put(wc, null);
+                    return true;
+                }
+            }
+
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Screenshotting %s [%s]",
+                    wc.toString(), bounds.toString());
+
+            Rect cropBounds = new Rect(bounds);
+            cropBounds.offsetTo(0, 0);
+            SurfaceControl.LayerCaptureArgs captureArgs =
+                    new SurfaceControl.LayerCaptureArgs.Builder(wc.getSurfaceControl())
+                            .setSourceCrop(cropBounds)
+                            .setCaptureSecureLayers(true)
+                            .setAllowProtected(true)
+                            .build();
+            SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
+                    SurfaceControl.captureLayers(captureArgs);
+            final HardwareBuffer buffer = screenshotBuffer == null ? null
+                    : screenshotBuffer.getHardwareBuffer();
+            if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
+                // This can happen when display is not ready.
+                Slog.w(TAG, "Failed to capture screenshot for " + wc);
+                return false;
+            }
+            SurfaceControl snapshotSurface = wc.makeAnimationLeash()
+                    .setName("transition snapshot: " + wc.toString())
+                    .setOpaque(true)
+                    .setParent(wc.getSurfaceControl())
+                    .setSecure(screenshotBuffer.containsSecureLayers())
+                    .setCallsite("Transition.ScreenshotSync")
+                    .setBLASTLayer()
+                    .build();
+            mSnapshots.put(wc, snapshotSurface);
+            SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get();
+
+            t.setBuffer(snapshotSurface, buffer);
+            t.setDataSpace(snapshotSurface, screenshotBuffer.getColorSpace().getDataSpace());
+            t.show(snapshotSurface);
+
+            // Place it on top of anything else in the container.
+            t.setLayer(snapshotSurface, Integer.MAX_VALUE);
+            t.apply();
+            t.close();
+
+            // Detach the screenshot on the sync transaction (the screenshot is just meant to
+            // freeze the window until the sync transaction is applied (with all its other
+            // corresponding changes), so this is how we unfreeze it.
+            wc.getSyncTransaction().reparent(snapshotSurface, null /* newParent */);
+            return true;
+        }
+
+        @Override
+        public void cleanUp(SurfaceControl.Transaction t) {
+            for (int i = 0; i < mSnapshots.size(); ++i) {
+                SurfaceControl snap = mSnapshots.valueAt(i);
+                // May be null if it was frozen via BLAST override.
+                if (snap == null) continue;
+                t.remove(snap);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 010c509..f62efbf 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -46,6 +46,7 @@
 import android.window.TransitionRequestInfo;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.LocalServices;
@@ -64,6 +65,11 @@
     private static final boolean SHELL_TRANSITIONS_ROTATION =
             SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
 
+    /** Which sync method to use for transition syncs. */
+    static final int SYNC_METHOD =
+            android.os.SystemProperties.getBoolean("persist.wm.debug.shell_transit_blast", true)
+                    ? BLASTSyncEngine.METHOD_BLAST : BLASTSyncEngine.METHOD_NONE;
+
     /** The same as legacy APP_TRANSITION_TIMEOUT_MS. */
     private static final int DEFAULT_TIMEOUT_MS = 5000;
     /** Less duration for CHANGE type because it does not involve app startup. */
@@ -160,6 +166,12 @@
 
     /** Starts Collecting */
     void moveToCollecting(@NonNull Transition transition) {
+        moveToCollecting(transition, SYNC_METHOD);
+    }
+
+    /** Starts Collecting */
+    @VisibleForTesting
+    void moveToCollecting(@NonNull Transition transition, int method) {
         if (mCollectingTransition != null) {
             throw new IllegalStateException("Simultaneous transition collection not supported.");
         }
@@ -167,7 +179,7 @@
         // Distinguish change type because the response time is usually expected to be not too long.
         final long timeoutMs =
                 transition.mType == TRANSIT_CHANGE ? CHANGE_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
-        mCollectingTransition.startCollecting(timeoutMs);
+        mCollectingTransition.startCollecting(timeoutMs, method);
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Start collecting in Transition: %s",
                 mCollectingTransition);
         dispatchLegacyAppTransitionPending();
@@ -228,10 +240,7 @@
      */
     boolean inCollectingTransition(@NonNull WindowContainer wc) {
         if (!isCollecting()) return false;
-        for (WindowContainer p = wc; p != null; p = p.getParent()) {
-            if (mCollectingTransition.mParticipants.contains(p)) return true;
-        }
-        return false;
+        return mCollectingTransition.isInTransition(wc);
     }
 
     /**
@@ -247,9 +256,7 @@
      */
     boolean inPlayingTransition(@NonNull WindowContainer wc) {
         for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
-            for (WindowContainer p = wc; p != null; p = p.getParent()) {
-                if (mPlayingTransitions.get(i).mParticipants.contains(p)) return true;
-            }
+            if (mPlayingTransitions.get(i).isInTransition(wc)) return true;
         }
         return false;
     }
@@ -469,6 +476,7 @@
     void collectForDisplayAreaChange(@NonNull DisplayArea<?> wc) {
         final Transition transition = mCollectingTransition;
         if (transition == null || !transition.mParticipants.contains(wc)) return;
+        transition.collectVisibleChange(wc);
         // Collect all visible tasks.
         wc.forAllLeafTasks(task -> {
             if (task.isVisible()) {
@@ -488,6 +496,16 @@
         }
     }
 
+    /**
+     * Records that a particular container is changing visibly (ie. something about it is changing
+     * while it remains visible). This only effects windows that are already in the collecting
+     * transition.
+     */
+    void collectVisibleChange(WindowContainer wc) {
+        if (!isCollecting()) return;
+        mCollectingTransition.collectVisibleChange(wc);
+    }
+
     /** @see Transition#mStatusBarTransitionDelay */
     void setStatusBarTransitionDelay(long delay) {
         if (mCollectingTransition == null) return;
@@ -637,9 +655,11 @@
     }
 
     void dispatchLegacyAppTransitionStarting(TransitionInfo info, long statusBarTransitionDelay) {
+        final boolean keyguardGoingAway = info.isKeyguardGoingAway();
         for (int i = 0; i < mLegacyListeners.size(); ++i) {
             // TODO(shell-transitions): handle (un)occlude transition.
-            mLegacyListeners.get(i).onAppTransitionStartingLocked(
+            mLegacyListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway,
+                    false /* keyguardOcclude */, 0 /* durationHint */,
                     SystemClock.uptimeMillis() + statusBarTransitionDelay,
                     AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
         }
@@ -654,7 +674,7 @@
     void dispatchLegacyAppTransitionCancelled() {
         for (int i = 0; i < mLegacyListeners.size(); ++i) {
             mLegacyListeners.get(i).onAppTransitionCancelledLocked(
-                    false /* keyguardGoingAwayCancelled */, false /* keyguardOccludedCancelled */);
+                    false /* keyguardGoingAway */);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java b/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java
index 6a878b9..f376e8b 100644
--- a/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java
+++ b/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java
@@ -29,13 +29,11 @@
 import com.android.internal.R;
 import com.android.server.utils.AppInstallerUtil;
 
-public class UnsupportedCompileSdkDialog {
-    private final AlertDialog mDialog;
-    private final String mPackageName;
+class UnsupportedCompileSdkDialog extends AppWarnings.BaseDialog {
 
-    public UnsupportedCompileSdkDialog(final AppWarnings manager, Context context,
+    UnsupportedCompileSdkDialog(final AppWarnings manager, Context context,
             ApplicationInfo appInfo) {
-        mPackageName = appInfo.packageName;
+        super(manager, appInfo.packageName);
 
         final PackageManager pm = context.getPackageManager();
         final CharSequence label = appInfo.loadSafeLabel(pm,
@@ -72,16 +70,4 @@
         alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag(
                 mPackageName, AppWarnings.FLAG_HIDE_COMPILE_SDK, !isChecked));
     }
-
-    public String getPackageName() {
-        return mPackageName;
-    }
-
-    public void show() {
-        mDialog.show();
-    }
-
-    public void dismiss() {
-        mDialog.dismiss();
-    }
 }
diff --git a/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java b/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java
index 4a800c4..b11c22d 100644
--- a/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java
+++ b/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java
@@ -16,8 +16,6 @@
 
 package com.android.server.wm;
 
-import com.android.internal.R;
-
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -27,13 +25,13 @@
 import android.view.WindowManager;
 import android.widget.CheckBox;
 
-public class UnsupportedDisplaySizeDialog {
-    private final AlertDialog mDialog;
-    private final String mPackageName;
+import com.android.internal.R;
 
-    public UnsupportedDisplaySizeDialog(final AppWarnings manager, Context context,
+class UnsupportedDisplaySizeDialog extends AppWarnings.BaseDialog {
+
+    UnsupportedDisplaySizeDialog(final AppWarnings manager, Context context,
             ApplicationInfo appInfo) {
-        mPackageName = appInfo.packageName;
+        super(manager, appInfo.packageName);
 
         final PackageManager pm = context.getPackageManager();
         final CharSequence label = appInfo.loadSafeLabel(pm,
@@ -63,16 +61,4 @@
         alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag(
                 mPackageName, AppWarnings.FLAG_HIDE_DISPLAY_SIZE, !isChecked));
     }
-
-    public String getPackageName() {
-        return mPackageName;
-    }
-
-    public void show() {
-        mDialog.show();
-    }
-
-    public void dismiss() {
-        mDialog.dismiss();
-    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 70dd9f3..d820ec4 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -343,6 +343,7 @@
     BLASTSyncEngine.SyncGroup mSyncGroup = null;
     final SurfaceControl.Transaction mSyncTransaction;
     @SyncState int mSyncState = SYNC_STATE_NONE;
+    int mSyncMethodOverride = BLASTSyncEngine.METHOD_UNDEFINED;
 
     private final List<WindowContainerListener> mListeners = new ArrayList<>();
 
@@ -2829,6 +2830,7 @@
      */
     void initializeChangeTransition(Rect startBounds, @Nullable SurfaceControl freezeTarget) {
         if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
+            mDisplayContent.mTransitionController.collectVisibleChange(this);
             // TODO(b/207070762): request shell transition for activityEmbedding change.
             return;
         }
@@ -3019,6 +3021,10 @@
                 final float windowCornerRadius = !inMultiWindowMode()
                         ? getDisplayContent().getWindowCornerRadius()
                         : 0;
+                if (asActivityRecord() != null
+                        && asActivityRecord().isNeedsLetterboxedAnimation()) {
+                    asActivityRecord().getLetterboxInnerBounds(mTmpRect);
+                }
                 AnimationAdapter adapter = new LocalAnimationAdapter(
                         new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
                                 getDisplayContent().mAppTransition.canSkipFirstFrame(),
@@ -3658,6 +3664,7 @@
     boolean onSyncFinishedDrawing() {
         if (mSyncState == SYNC_STATE_NONE) return false;
         mSyncState = SYNC_STATE_READY;
+        mSyncMethodOverride = BLASTSyncEngine.METHOD_UNDEFINED;
         mWmService.mWindowPlacerLocked.requestTraversal();
         ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "onSyncFinishedDrawing %s", this);
         return true;
@@ -3676,6 +3683,13 @@
         mSyncGroup = group;
     }
 
+    @Nullable
+    BLASTSyncEngine.SyncGroup getSyncGroup() {
+        if (mSyncGroup != null) return mSyncGroup;
+        if (mParent != null) return mParent.getSyncGroup();
+        return null;
+    }
+
     /**
      * Prepares this container for participation in a sync-group. This includes preparing all its
      * children.
@@ -3715,6 +3729,7 @@
         }
         if (cancel && mSyncGroup != null) mSyncGroup.onCancelSync(this);
         mSyncState = SYNC_STATE_NONE;
+        mSyncMethodOverride = BLASTSyncEngine.METHOD_UNDEFINED;
         mSyncGroup = null;
     }
 
@@ -3817,6 +3832,7 @@
         // disable this when shell transitions is disabled.
         if (mTransitionController.isShellTransitionsEnabled()) {
             mSyncState = SYNC_STATE_NONE;
+            mSyncMethodOverride = BLASTSyncEngine.METHOD_UNDEFINED;
         }
         prepareSync();
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 4b5b6da..a71c386 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -220,13 +220,9 @@
         /**
          * Called when a pending app transition gets cancelled.
          *
-         * @param keyguardGoingAwayCancelled {@code true} if keyguard going away transition was
-         *        cancelled.
-         * @param keyguardOccludedCancelled {@code true} if keyguard (un)occluded transition was
-         *        cancelled.
+         * @param keyguardGoingAway true if keyguard going away transition got cancelled.
          */
-        public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
-                boolean keyguardOccludedCancelled) {}
+        public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {}
 
         /**
          * Called when an app transition is timed out.
@@ -236,6 +232,9 @@
         /**
          * Called when an app transition gets started
          *
+         * @param keyguardGoingAway true if keyguard going away transition is started.
+         * @param keyguardOccluding true if keyguard (un)occlude transition is started.
+         * @param duration the total duration of the transition
          * @param statusBarAnimationStartTime the desired start time for all visual animations in
          *        the status bar caused by this app transition in uptime millis
          * @param statusBarAnimationDuration the duration for all visual animations in the status
@@ -246,7 +245,8 @@
          * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER},
          * or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}.
          */
-        public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
+        public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
+                boolean keyguardOccluding, long duration, long statusBarAnimationStartTime,
                 long statusBarAnimationDuration) {
             return 0;
         }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 298f93d..b4386f6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -92,7 +92,6 @@
 import static android.view.WindowManagerGlobal.ADD_OKAY;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
@@ -321,6 +320,7 @@
 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
 import com.android.server.power.ShutdownThread;
 import com.android.server.utils.PriorityDump;
+import com.android.server.wm.utils.WmDisplayCutout;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -425,6 +425,32 @@
             SystemProperties.getBoolean(ENABLE_SHELL_TRANSITIONS, false);
 
     /**
+     * Run Keyguard animation as remote animation in System UI instead of local animation in
+     * the server process.
+     *
+     * 0: Runs all keyguard animation as local animation
+     * 1: Only runs keyguard going away animation as remote animation
+     * 2: Runs all keyguard animation as remote animation
+     */
+    private static final String ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY =
+            "persist.wm.enable_remote_keyguard_animation";
+
+    private static final int sEnableRemoteKeyguardAnimation =
+            SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 2);
+
+    /**
+     * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
+     */
+    public static final boolean sEnableRemoteKeyguardGoingAwayAnimation =
+            sEnableRemoteKeyguardAnimation >= 1;
+
+    /**
+     * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
+     */
+    public static final boolean sEnableRemoteKeyguardOccludeAnimation =
+            sEnableRemoteKeyguardAnimation >= 2;
+
+    /**
      * Allows a fullscreen windowing mode activity to launch in its desired orientation directly
      * when the display has different orientation.
      */
@@ -1092,8 +1118,7 @@
             = new WindowManagerInternal.AppTransitionListener() {
 
         @Override
-        public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
-                boolean keyguardOccludedCancelled) {
+        public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
         }
 
         @Override
@@ -1848,7 +1873,8 @@
             ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s"
                     + ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5));
 
-            if (win.isVisibleRequestedOrAdding() && displayContent.updateOrientation()) {
+            if ((win.isVisibleRequestedOrAdding() && displayContent.updateOrientation())
+                    || win.providesNonDecorInsets()) {
                 displayContent.sendNewConfiguration();
             }
 
@@ -2558,7 +2584,7 @@
                 final int maybeSyncSeqId;
                 if (USE_BLAST_SYNC && win.useBLASTSync() && viewVisibility != View.GONE
                         && win.mSyncSeqId > lastSyncSeqId) {
-                    maybeSyncSeqId = win.mSyncSeqId;
+                    maybeSyncSeqId = win.shouldSyncWithBuffers() ? win.mSyncSeqId : -1;
                     win.markRedrawForSyncReported();
                 } else {
                     maybeSyncSeqId = -1;
@@ -6359,27 +6385,6 @@
         }
     }
 
-    /**
-     * Used by ActivityManager to determine where to position an app with aspect ratio shorter then
-     * the screen is.
-     * @see DisplayPolicy#getNavBarPosition()
-     */
-    @Override
-    @WindowManagerPolicy.NavigationBarPosition
-    public int getNavBarPosition(int displayId) {
-        synchronized (mGlobalLock) {
-            // Perform layout if it was scheduled before to make sure that we get correct nav bar
-            // position when doing rotations.
-            final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
-            if (displayContent == null) {
-                Slog.w(TAG, "getNavBarPosition with invalid displayId=" + displayId
-                        + " callers=" + Debug.getCallers(3));
-                return NAV_BAR_INVALID;
-            }
-            return displayContent.getDisplayPolicy().getNavBarPosition();
-        }
-    }
-
     @Override
     public void createInputConsumer(IBinder token, String name, int displayId,
             InputChannel inputChannel) {
@@ -7217,7 +7222,9 @@
         final DisplayContent dc = mRoot.getDisplayContent(displayId);
         if (dc != null) {
             final DisplayInfo di = dc.getDisplayInfo();
-            dc.getDisplayPolicy().getStableInsetsLw(di.rotation, di.displayCutout, outInsets);
+            final WmDisplayCutout cutout = dc.calculateDisplayCutoutForRotation(di.rotation);
+            dc.getDisplayPolicy().getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+                    cutout, outInsets);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 4f03264..f839255 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -147,7 +147,7 @@
         mGlobalLock = atm.mGlobalLock;
         mTaskOrganizerController = new TaskOrganizerController(mService);
         mDisplayAreaOrganizerController = new DisplayAreaOrganizerController(mService);
-        mTaskFragmentOrganizerController = new TaskFragmentOrganizerController(atm);
+        mTaskFragmentOrganizerController = new TaskFragmentOrganizerController(atm, this);
     }
 
     void setWindowManager(WindowManagerService wms) {
@@ -1405,7 +1405,7 @@
     private BLASTSyncEngine.SyncGroup prepareSyncWithOrganizer(
             IWindowContainerTransactionCallback callback) {
         final BLASTSyncEngine.SyncGroup s = mService.mWindowManager.mSyncEngine
-                .prepareSyncSet(this, "");
+                .prepareSyncSet(this, "", BLASTSyncEngine.METHOD_BLAST);
         mTransactionCallbacksByPendingSyncId.put(s.mSyncId, callback);
         return s;
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a85dcbf..3469303 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -242,6 +242,7 @@
 import android.view.ViewDebug;
 import android.view.ViewTreeObserver;
 import android.view.WindowInfo;
+import android.view.WindowInsets;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowManager;
 import android.view.animation.Animation;
@@ -390,7 +391,7 @@
      */
     int mSyncSeqId = 0;
 
-    /** The last syncId associated with a prepareSync or 0 when no sync is active. */
+    /** The last syncId associated with a BLAST prepareSync or 0 when no BLAST sync is active. */
     int mPrepareSyncSeqId = 0;
 
     /**
@@ -1896,6 +1897,19 @@
         return (mPolicyVisibility & POLICY_VISIBILITY_ALL) == POLICY_VISIBILITY_ALL;
     }
 
+    boolean providesNonDecorInsets() {
+        if (mProvidedInsetsSources == null) {
+            return false;
+        }
+        for (int i = mProvidedInsetsSources.size() - 1; i >= 0; i--) {
+            final int type = mProvidedInsetsSources.keyAt(i);
+            if ((InsetsState.toPublicType(type) & WindowInsets.Type.navigationBars()) != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     void clearPolicyVisibilityFlag(int policyVisibilityFlag) {
         mPolicyVisibility &= ~policyVisibilityFlag;
         mWmService.scheduleAnimationLocked();
@@ -2608,14 +2622,19 @@
             }
 
             removeImmediately();
-            // Removing a visible window will effect the computed orientation
-            // So just update orientation if needed.
+            boolean sentNewConfig = false;
             if (wasVisible) {
+                // Removing a visible window will effect the computed orientation
+                // So just update orientation if needed.
                 final DisplayContent displayContent = getDisplayContent();
                 if (displayContent.updateOrientation()) {
                     displayContent.sendNewConfiguration();
+                    sentNewConfig = true;
                 }
             }
+            if (!sentNewConfig && providesNonDecorInsets()) {
+                getDisplayContent().sendNewConfiguration();
+            }
             mWmService.updateFocusedWindowLocked(isFocused()
                             ? UPDATE_FOCUS_REMOVING_FOCUS
                             : UPDATE_FOCUS_NORMAL,
@@ -3892,9 +3911,10 @@
         fillClientWindowFramesAndConfiguration(mClientWindowFrames, mLastReportedConfiguration,
                 true /* useLatestConfig */, false /* relayoutVisible */);
         final boolean syncRedraw = shouldSendRedrawForSync();
+        final boolean syncWithBuffers = syncRedraw && shouldSyncWithBuffers();
         final boolean reportDraw = syncRedraw || drawPending;
         final boolean isDragResizeChanged = isDragResizeChanged();
-        final boolean forceRelayout = syncRedraw || isDragResizeChanged;
+        final boolean forceRelayout = syncWithBuffers || isDragResizeChanged;
         final DisplayContent displayContent = getDisplayContent();
         final boolean alwaysConsumeSystemBars =
                 displayContent.getDisplayPolicy().areSystemBarsForcedShownLw();
@@ -3920,7 +3940,7 @@
         try {
             mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
                     getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
-                    mSyncSeqId, resizeMode);
+                    syncWithBuffers ? mSyncSeqId : -1, resizeMode);
             if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration
                     .getMergedConfiguration().windowConfiguration.getRotation()) {
                 mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime();
@@ -5924,7 +5944,9 @@
         }
 
         mSyncSeqId++;
-        mPrepareSyncSeqId = mSyncSeqId;
+        if (getSyncMethod() == BLASTSyncEngine.METHOD_BLAST) {
+            mPrepareSyncSeqId = mSyncSeqId;
+        }
         requestRedrawForSync();
         return true;
     }
@@ -5997,6 +6019,7 @@
             postDrawTransaction = null;
             skipLayout = true;
         } else if (syncActive) {
+            // Currently in a Sync that is using BLAST.
             if (!syncStillPending) {
                 onSyncFinishedDrawing();
             }
@@ -6005,6 +6028,9 @@
                 // Consume the transaction because the sync group will merge it.
                 postDrawTransaction = null;
             }
+        } else if (useBLASTSync()) {
+            // Sync that is not using BLAST
+            onSyncFinishedDrawing();
         }
 
         final boolean layoutNeeded =
@@ -6041,7 +6067,7 @@
     }
 
     boolean hasWallpaperForLetterboxBackground() {
-        return mActivityRecord != null && mActivityRecord.hasWallpaperBackgroudForLetterbox();
+        return mActivityRecord != null && mActivityRecord.hasWallpaperBackgroundForLetterbox();
     }
 
     /**
@@ -6063,6 +6089,18 @@
         return useBLASTSync();
     }
 
+    int getSyncMethod() {
+        final BLASTSyncEngine.SyncGroup syncGroup = getSyncGroup();
+        if (syncGroup == null) return BLASTSyncEngine.METHOD_NONE;
+        if (mSyncMethodOverride != BLASTSyncEngine.METHOD_UNDEFINED) return mSyncMethodOverride;
+        return syncGroup.mSyncMethod;
+    }
+
+    boolean shouldSyncWithBuffers() {
+        if (!mDrawHandlers.isEmpty()) return true;
+        return getSyncMethod() == BLASTSyncEngine.METHOD_BLAST;
+    }
+
     void requestRedrawForSync() {
         mRedrawForSyncReported = false;
     }
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 8567110..9abf107 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -11,7 +11,7 @@
 # BatteryStats
 per-file com_android_server_am_BatteryStatsService.cpp = file:/BATTERY_STATS_OWNERS
 
-per-file Android.bp = file:platform/build/soong:/OWNERS
+per-file Android.bp = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION}
 per-file com_android_server_Usb* = file:/services/usb/OWNERS
 per-file com_android_server_Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS
 per-file com_android_server_hdmi_* = file:/core/java/android/hardware/hdmi/OWNERS
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
index 054181d..0305c35 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -671,6 +671,21 @@
         if (mFactoryResetReason != null) {
             pw.print("mFactoryResetReason="); pw.println(mFactoryResetReason);
         }
+        if (mDelegationMap.size() != 0) {
+            pw.println("mDelegationMap=");
+            pw.increaseIndent();
+            for (int i = 0; i < mDelegationMap.size(); i++) {
+                List<String> delegationScopes = mDelegationMap.valueAt(i);
+                pw.println(mDelegationMap.keyAt(i) + "[size=" + delegationScopes.size()
+                        + "]");
+                pw.increaseIndent();
+                for (int j = 0; j < delegationScopes.size(); j++) {
+                    pw.println(j + ": " + delegationScopes.get(j));
+                }
+                pw.decreaseIndent();
+            }
+            pw.decreaseIndent();
+        }
         pw.decreaseIndent();
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c1c7bce..fbaf1ce 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5786,29 +5786,8 @@
     @VisibleForTesting
     public void enforceCallerCanRequestDeviceIdAttestation(CallerIdentity caller)
             throws SecurityException {
-        /**
-         *  First check if there's a profile owner because the device could be in COMP mode (where
-         *  there's a device owner and profile owner on the same device).
-         *  If the caller is from the work profile, then it must be the PO or the delegate, and
-         *  it must have the right permission to access device identifiers.
-         */
-        int callerUserId = caller.getUserId();
-        if (hasProfileOwner(callerUserId)) {
-            // Make sure that the caller is the profile owner or delegate.
-            Preconditions.checkCallAuthorization(canInstallCertificates(caller));
-            // Verify that the managed profile is on an organization-owned device (or is affiliated
-            // with the device owner user) and as such the profile owner can access Device IDs.
-            if (isProfileOwnerOfOrganizationOwnedDevice(callerUserId)
-                    || isUserAffiliatedWithDevice(callerUserId)) {
-                return;
-            }
-            throw new SecurityException(
-                    "Profile Owner is not allowed to access Device IDs.");
-        }
-
-        // If not, fall back to the device owner check.
-        Preconditions.checkCallAuthorization(
-                isDefaultDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
+        Preconditions.checkCallAuthorization(hasDeviceIdAccessUnchecked(caller.getPackageName(),
+                caller.getUid()));
     }
 
     @VisibleForTesting
@@ -5856,7 +5835,6 @@
         final boolean deviceIdAttestationRequired = attestationUtilsFlags != null;
         KeyGenParameterSpec keySpec = parcelableKeySpec.getSpec();
         final String alias = keySpec.getKeystoreAlias();
-
         Preconditions.checkStringNotEmpty(alias, "Empty alias provided");
         Preconditions.checkArgument(
                 !deviceIdAttestationRequired || keySpec.getAttestationChallenge() != null,
@@ -8166,7 +8144,8 @@
         }
 
         final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+        Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle)
+                || isCameraServerUid(caller));
 
         if (parent) {
             Preconditions.checkCallAuthorization(
@@ -8363,6 +8342,7 @@
                 + PackageManager.FEATURE_DEVICE_ADMIN + " feature.");
     }
 
+    // TODO(b/240562946): Remove owner name from API parameters.
     @Override
     public boolean setDeviceOwner(ComponentName admin, String ownerName, int userId,
             boolean setProfileOwnerOnCurrentUserIfNecessary) {
@@ -8397,7 +8377,7 @@
                         .write();
             }
 
-            mOwners.setDeviceOwner(admin, ownerName, userId);
+            mOwners.setDeviceOwner(admin, userId);
             mOwners.writeDeviceOwner();
             setDeviceOwnershipSystemPropertyLocked();
 
@@ -8649,6 +8629,7 @@
         }
     }
 
+    // TODO(b/240562946): Remove api as owner name is not used.
     /**
      * Returns the "name" of the device owner.  It'll work for non-DO users too, but requires
      * MANAGE_USERS.
@@ -8819,6 +8800,7 @@
         });
     }
 
+    // TODO(b/240562946): Remove owner name from API parameters.
     @Override
     public boolean setProfileOwner(ComponentName who, String ownerName, int userHandle) {
         if (!mHasFeature) {
@@ -8866,7 +8848,7 @@
             // Shutting down backup manager service permanently.
             toggleBackupServiceActive(userHandle, /* makeActive= */ false);
 
-            mOwners.setProfileOwner(who, ownerName, userHandle);
+            mOwners.setProfileOwner(who, userHandle);
             mOwners.writeProfileOwner(userHandle);
             Slogf.i(LOG_TAG, "Profile owner set: " + who + " on user " + userHandle);
 
@@ -9337,6 +9319,7 @@
         return who.getPackageName().equals(configPackage);
     }
 
+    // TODO(b/240562946): Remove api as owner name is not used.
     @Override
     public String getProfileOwnerName(int userHandle) {
         if (!mHasFeature) {
@@ -9389,26 +9372,33 @@
         if (!hasPermission(permission.READ_PHONE_STATE, pid, uid)) {
             return false;
         }
+        return hasDeviceIdAccessUnchecked(packageName, uid);
+    }
 
-        // Allow access to the device owner or delegate cert installer or profile owner of an
-        // affiliated user
+    /**
+     * Check if caller is device owner, delegate cert installer or profile owner of
+     * affiliated user. Or if caller is profile owner for a specified user or delegate cert
+     * installer on an organization-owned device.
+     */
+    private boolean hasDeviceIdAccessUnchecked(String packageName, int uid) {
+        // Is the caller a  device owner, delegate cert installer or profile owner of an
+        // affiliated user.
         ComponentName deviceOwner = getDeviceOwnerComponent(true);
         if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName)
                 || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) {
             return true;
         }
         final int userId = UserHandle.getUserId(uid);
-        // Allow access to the profile owner for the specified user, or delegate cert installer
-        // But only if this is an organization-owned device.
+        // Is the caller the profile owner for the specified user, or delegate cert installer on an
+        // organization-owned device.
         ComponentName profileOwner = getProfileOwnerAsUser(userId);
         final boolean isCallerProfileOwnerOrDelegate = profileOwner != null
                 && (profileOwner.getPackageName().equals(packageName)
-                        || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL));
+                || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL));
         if (isCallerProfileOwnerOrDelegate && (isProfileOwnerOfOrganizationOwnedDevice(userId)
                 || isUserAffiliatedWithDevice(userId))) {
             return true;
         }
-
         return false;
     }
 
@@ -9677,6 +9667,10 @@
         return UserHandle.isSameApp(caller.getUid(), Process.SHELL_UID);
     }
 
+    private boolean isCameraServerUid(CallerIdentity caller) {
+        return UserHandle.isSameApp(caller.getUid(), Process.CAMERASERVER_UID);
+    }
+
     private @UserIdInt int getCurrentForegroundUserId() {
         try {
             UserInfo currentUser = mInjector.getIActivityManager().getCurrentUser();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 08bd3e4..3b46d52 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -193,12 +193,6 @@
         }
     }
 
-    String getDeviceOwnerName() {
-        synchronized (mData) {
-            return mData.mDeviceOwner != null ? mData.mDeviceOwner.name : null;
-        }
-    }
-
     ComponentName getDeviceOwnerComponent() {
         synchronized (mData) {
             return mData.mDeviceOwner != null ? mData.mDeviceOwner.admin : null;
@@ -217,7 +211,7 @@
         }
     }
 
-    void setDeviceOwner(ComponentName admin, String ownerName, int userId) {
+    void setDeviceOwner(ComponentName admin, int userId) {
         if (userId < 0) {
             Slog.e(TAG, "Invalid user id for device owner user: " + userId);
             return;
@@ -226,7 +220,7 @@
             // A device owner is allowed to access device identifiers. Even though this flag
             // is not currently checked for device owner, it is set to true here so that it is
             // semantically compatible with the meaning of this flag.
-            mData.mDeviceOwner = new OwnerInfo(ownerName, admin, /* remoteBugreportUri =*/ null,
+            mData.mDeviceOwner = new OwnerInfo(admin, /* remoteBugreportUri =*/ null,
                     /* remoteBugreportHash =*/ null, /* isOrganizationOwnedDevice =*/ true);
             mData.mDeviceOwnerUserId = userId;
 
@@ -248,10 +242,10 @@
         }
     }
 
-    void setProfileOwner(ComponentName admin, String ownerName, int userId) {
+    void setProfileOwner(ComponentName admin, int userId) {
         synchronized (mData) {
             // For a newly set PO, there's no need for migration.
-            mData.mProfileOwners.put(userId, new OwnerInfo(ownerName, admin,
+            mData.mProfileOwners.put(userId, new OwnerInfo(admin,
                     /* remoteBugreportUri =*/ null, /* remoteBugreportHash =*/ null,
                     /* isOrganizationOwnedDevice =*/ false));
             mUserManagerInternal.setUserManaged(userId, true);
@@ -270,7 +264,7 @@
     void transferProfileOwner(ComponentName target, int userId) {
         synchronized (mData) {
             final OwnerInfo ownerInfo = mData.mProfileOwners.get(userId);
-            final OwnerInfo newOwnerInfo = new OwnerInfo(target.getPackageName(), target,
+            final OwnerInfo newOwnerInfo = new OwnerInfo(target,
                     ownerInfo.remoteBugreportUri, ownerInfo.remoteBugreportHash,
                     ownerInfo.isOrganizationOwnedDevice);
             mData.mProfileOwners.put(userId, newOwnerInfo);
@@ -282,9 +276,7 @@
         synchronized (mData) {
             Integer previousDeviceOwnerType = mData.mDeviceOwnerTypes.remove(
                     mData.mDeviceOwner.packageName);
-            // We don't set a name because it's not used anyway.
-            // See DevicePolicyManagerService#getDeviceOwnerName
-            mData.mDeviceOwner = new OwnerInfo(null, target,
+            mData.mDeviceOwner = new OwnerInfo(target,
                     mData.mDeviceOwner.remoteBugreportUri,
                     mData.mDeviceOwner.remoteBugreportHash,
                     mData.mDeviceOwner.isOrganizationOwnedDevice);
@@ -305,13 +297,6 @@
         }
     }
 
-    String getProfileOwnerName(int userId) {
-        synchronized (mData) {
-            OwnerInfo profileOwner = mData.mProfileOwners.get(userId);
-            return profileOwner != null ? profileOwner.name : null;
-        }
-    }
-
     String getProfileOwnerPackage(int userId) {
         synchronized (mData) {
             OwnerInfo profileOwner = mData.mProfileOwners.get(userId);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 6948420..2ab5464 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -493,16 +493,14 @@
     }
 
     static class OwnerInfo {
-        public final String name;
         public final String packageName;
         public final ComponentName admin;
         public String remoteBugreportUri;
         public String remoteBugreportHash;
         public boolean isOrganizationOwnedDevice;
 
-        OwnerInfo(String name, ComponentName admin, String remoteBugreportUri,
+        OwnerInfo(ComponentName admin, String remoteBugreportUri,
                 String remoteBugreportHash, boolean isOrganizationOwnedDevice) {
-            this.name = name;
             this.admin = admin;
             this.packageName = admin.getPackageName();
             this.remoteBugreportUri = remoteBugreportUri;
@@ -512,9 +510,6 @@
 
         public void writeToXml(TypedXmlSerializer out, String tag) throws IOException {
             out.startTag(null, tag);
-            if (name != null) {
-                out.attribute(null, ATTR_NAME, name);
-            }
             if (admin != null) {
                 out.attribute(null, ATTR_COMPONENT_NAME, admin.flattenToString());
             }
@@ -532,7 +527,6 @@
         }
 
         public static OwnerInfo readFromXml(TypedXmlPullParser parser) {
-            final String name = parser.getAttributeValue(null, ATTR_NAME);
             final String componentName = parser.getAttributeValue(null, ATTR_COMPONENT_NAME);
             final String remoteBugreportUri =
                     parser.getAttributeValue(null, ATTR_REMOTE_BUGREPORT_URI);
@@ -556,13 +550,11 @@
                 return null;
             }
 
-            return new OwnerInfo(
-                    name, admin, remoteBugreportUri, remoteBugreportHash, isOrgOwnedDevice);
+            return new OwnerInfo(admin, remoteBugreportUri, remoteBugreportHash, isOrgOwnedDevice);
         }
 
         public void dump(IndentingPrintWriter pw) {
             pw.println("admin=" + admin);
-            pw.println("name=" + name);
             pw.println("package=" + packageName);
             pw.println("isOrganizationOwnedDevice=" + isOrganizationOwnedDevice);
         }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
index 4a631a12..59cb43f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
@@ -33,6 +33,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.os.BatteryManagerInternal;
 import android.os.RemoteException;
@@ -75,6 +76,8 @@
     private JobSchedulerService mJobSchedulerService;
     @Mock
     private PackageManagerInternal mPackageManagerInternal;
+    @Mock
+    private PackageManager mPackageManager;
 
     @Before
     public void setUp() {
@@ -100,6 +103,9 @@
         ArgumentCaptor<BroadcastReceiver> receiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
 
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(false);
         mFlexibilityController =
                 new FlexibilityController(mJobSchedulerService, mock(PrefetchController.class));
         mBatteryController = new BatteryController(mJobSchedulerService, mFlexibilityController);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 953a72d..1f85f2c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -51,6 +51,7 @@
 import android.app.job.JobInfo;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
@@ -96,6 +97,8 @@
     private NetworkPolicyManagerInternal mNetPolicyManagerInternal;
     @Mock
     private JobSchedulerService mService;
+    @Mock
+    private PackageManager mPackageManager;
 
     private Constants mConstants;
 
@@ -115,10 +118,6 @@
         LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
         LocalServices.addService(NetworkPolicyManagerInternal.class, mNetPolicyManagerInternal);
 
-        when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
-        mFlexibilityController =
-                new FlexibilityController(mService, mock(PrefetchController.class));
-
         // Freeze the clocks at this moment in time
         JobSchedulerService.sSystemClock =
                 Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
@@ -142,6 +141,13 @@
         when(mService.getTestableContext()).thenReturn(mContext);
         when(mService.getLock()).thenReturn(mService);
         when(mService.getConstants()).thenReturn(mConstants);
+        // Instantiate Flexibility Controller
+        when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(false);
+        mFlexibilityController =
+                new FlexibilityController(mService, mock(PrefetchController.class));
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 1b39add..0eb0a00 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -29,10 +29,12 @@
 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE;
 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FLEXIBILITY_ENABLED;
 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS;
+import static com.android.server.job.controllers.FlexibilityController.NUM_FLEXIBLE_CONSTRAINTS;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
+import static com.android.server.job.controllers.JobStatus.NO_LATEST_RUNTIME;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -48,6 +50,7 @@
 import android.app.job.JobInfo;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.os.Looper;
 import android.provider.DeviceConfig;
@@ -90,6 +93,8 @@
     private JobSchedulerService mJobSchedulerService;
     @Mock
     private PrefetchController mPrefetchController;
+    @Mock
+    private PackageManager mPackageManager;
 
     @Before
     public void setup() {
@@ -107,6 +112,9 @@
         // Called in FlexibilityController constructor.
         when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
         when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(false);
         // Used in FlexibilityController.FcConstants.
         doAnswer((Answer<Void>) invocationOnMock -> null)
                 .when(() -> DeviceConfig.addOnPropertiesChangedListener(
@@ -190,7 +198,7 @@
      */
     @Test
     public void testDefaultVariableValues() {
-        assertEquals(FlexibilityController.NUM_FLEXIBLE_CONSTRAINTS,
+        assertEquals(NUM_FLEXIBLE_CONSTRAINTS,
                 mFlexibilityController.mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS.length
         );
     }
@@ -209,7 +217,8 @@
     public void testOnConstantsUpdated_DeadlineProximity() {
         JobStatus js = createJobStatus("testDeadlineProximityConfig", createJob(0));
         setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, Long.MAX_VALUE);
-        mFlexibilityController.mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js);
+        mFlexibilityController.mFlexibilityAlarmQueue
+                .scheduleDropNumConstraintsAlarm(js, FROZEN_TIME);
         assertEquals(0, js.getNumRequiredFlexibleConstraints());
     }
 
@@ -336,26 +345,31 @@
     @Test
     public void testCurPercent() {
         long deadline = 1000;
+        long nowElapsed;
         JobInfo.Builder jb = createJob(0).setOverrideDeadline(deadline);
         JobStatus js = createJobStatus("time", jb);
 
         assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
         assertEquals(deadline + FROZEN_TIME,
                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME));
+        nowElapsed = 600 + FROZEN_TIME;
         JobSchedulerService.sElapsedRealtimeClock =
-                Clock.fixed(Instant.ofEpochMilli(600 + FROZEN_TIME), ZoneOffset.UTC);
-        assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+        assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
 
+        nowElapsed = 1400;
         JobSchedulerService.sElapsedRealtimeClock =
-                Clock.fixed(Instant.ofEpochMilli(1400), ZoneOffset.UTC);
-        assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+        assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
 
+        nowElapsed = 950 + FROZEN_TIME;
         JobSchedulerService.sElapsedRealtimeClock =
-                Clock.fixed(Instant.ofEpochMilli(950 + FROZEN_TIME), ZoneOffset.UTC);
-        assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+        assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
 
+        nowElapsed = FROZEN_TIME;
         JobSchedulerService.sElapsedRealtimeClock =
-                Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
         long delay = 100;
         deadline = 1100;
         jb = createJob(0).setOverrideDeadline(deadline).setMinimumLatency(delay);
@@ -366,22 +380,25 @@
         assertEquals(deadline + FROZEN_TIME,
                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME + delay));
 
+        nowElapsed = 600 + FROZEN_TIME + delay;
         JobSchedulerService.sElapsedRealtimeClock =
-                Clock.fixed(Instant.ofEpochMilli(600 + FROZEN_TIME + delay), ZoneOffset.UTC);
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
-        assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+        assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
 
+        nowElapsed = 1400;
         JobSchedulerService.sElapsedRealtimeClock =
-                Clock.fixed(Instant.ofEpochMilli(1400), ZoneOffset.UTC);
-        assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+        assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
 
+        nowElapsed = 950 + FROZEN_TIME + delay;
         JobSchedulerService.sElapsedRealtimeClock =
-                Clock.fixed(Instant.ofEpochMilli(950 + FROZEN_TIME + delay), ZoneOffset.UTC);
-        assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+        assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
     }
 
     @Test
-    public void testGetLifeCycleBeginningElapsedLocked_prefetch() {
+    public void testGetLifeCycleBeginningElapsedLocked_Prefetch() {
         // prefetch with lifecycle
         when(mPrefetchController.getLaunchTimeThresholdMs()).thenReturn(700L);
         JobInfo.Builder jb = createJob(0).setPrefetch(true);
@@ -408,7 +425,7 @@
     }
 
     @Test
-    public void testGetLifeCycleBeginningElapsedLocked_nonPrefetch() {
+    public void testGetLifeCycleBeginningElapsedLocked_NonPrefetch() {
         // delay
         long delay = 100;
         JobInfo.Builder jb = createJob(0).setMinimumLatency(delay);
@@ -423,7 +440,7 @@
     }
 
     @Test
-    public void testGetLifeCycleEndElapsedLocked_prefetch() {
+    public void testGetLifeCycleEndElapsedLocked_Prefetch() {
         // prefetch no estimate
         JobInfo.Builder jb = createJob(0).setPrefetch(true);
         JobStatus js = createJobStatus("time", jb);
@@ -435,8 +452,9 @@
         when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(1000L);
         assertEquals(1000L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
     }
+
     @Test
-    public void testGetLifeCycleEndElapsedLocked_nonPrefetch() {
+    public void testGetLifeCycleEndElapsedLocked_NonPrefetch() {
         // deadline
         JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000L);
         JobStatus js = createJobStatus("time", jb);
@@ -450,6 +468,28 @@
     }
 
     @Test
+    public void testGetLifeCycleEndElapsedLocked_Rescheduled() {
+        JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000L);
+        JobStatus js = createJobStatus("time", jb);
+        js = new JobStatus(
+                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, FROZEN_TIME, FROZEN_TIME);
+
+        assertEquals(mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+
+        js = new JobStatus(
+                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 3, FROZEN_TIME, FROZEN_TIME);
+
+        assertEquals(2 * mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+
+        js = new JobStatus(
+                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 10, FROZEN_TIME, FROZEN_TIME);
+        assertEquals(mFcConfig.MAX_RESCHEDULED_DEADLINE_MS,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+    }
+
+    @Test
     public void testWontStopJobFromRunning() {
         JobStatus js = createJobStatus("testWontStopJobFromRunning", createJob(101));
         // Stop satisfied constraints from causing a false positive.
@@ -464,9 +504,9 @@
     public void testFlexibilityTracker() {
         FlexibilityController.FlexibilityTracker flexTracker =
                 mFlexibilityController.new
-                        FlexibilityTracker(FlexibilityController.NUM_FLEXIBLE_CONSTRAINTS);
-
-        assertEquals(4, flexTracker.size());
+                        FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS);
+        // Plus one for jobs with 0 required constraint.
+        assertEquals(NUM_FLEXIBLE_CONSTRAINTS + 1, flexTracker.size());
         JobStatus[] jobs = new JobStatus[4];
         JobInfo.Builder jb;
         for (int i = 0; i < jobs.length; i++) {
@@ -486,57 +526,65 @@
 
         synchronized (mFlexibilityController.mLock) {
             ArrayList<ArraySet<JobStatus>> trackedJobs = flexTracker.getArrayList();
-            assertEquals(0, trackedJobs.get(0).size());
-            assertEquals(0, trackedJobs.get(1).size());
-            assertEquals(3, trackedJobs.get(2).size());
-            assertEquals(0, trackedJobs.get(3).size());
-
-            flexTracker.adjustJobsRequiredConstraints(jobs[0], -1);
-            assertEquals(0, trackedJobs.get(0).size());
-            assertEquals(1, trackedJobs.get(1).size());
-            assertEquals(2, trackedJobs.get(2).size());
-            assertEquals(0, trackedJobs.get(3).size());
-
-            flexTracker.adjustJobsRequiredConstraints(jobs[0], -1);
             assertEquals(1, trackedJobs.get(0).size());
             assertEquals(0, trackedJobs.get(1).size());
-            assertEquals(2, trackedJobs.get(2).size());
-            assertEquals(0, trackedJobs.get(3).size());
+            assertEquals(0, trackedJobs.get(2).size());
+            assertEquals(3, trackedJobs.get(3).size());
+            assertEquals(0, trackedJobs.get(4).size());
 
-            flexTracker.adjustJobsRequiredConstraints(jobs[0], -1);
-            assertEquals(0, trackedJobs.get(0).size());
+            flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
+            assertEquals(1, trackedJobs.get(0).size());
             assertEquals(0, trackedJobs.get(1).size());
-            assertEquals(2, trackedJobs.get(2).size());
-            assertEquals(0, trackedJobs.get(3).size());
+            assertEquals(1, trackedJobs.get(2).size());
+            assertEquals(2, trackedJobs.get(3).size());
+            assertEquals(0, trackedJobs.get(4).size());
+
+            flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
+            assertEquals(1, trackedJobs.get(0).size());
+            assertEquals(1, trackedJobs.get(1).size());
+            assertEquals(0, trackedJobs.get(2).size());
+            assertEquals(2, trackedJobs.get(3).size());
+            assertEquals(0, trackedJobs.get(4).size());
+
+            flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
+            assertEquals(2, trackedJobs.get(0).size());
+            assertEquals(0, trackedJobs.get(1).size());
+            assertEquals(0, trackedJobs.get(2).size());
+            assertEquals(2, trackedJobs.get(3).size());
+            assertEquals(0, trackedJobs.get(4).size());
 
             flexTracker.remove(jobs[1]);
-            assertEquals(0, trackedJobs.get(0).size());
+            assertEquals(2, trackedJobs.get(0).size());
             assertEquals(0, trackedJobs.get(1).size());
-            assertEquals(1, trackedJobs.get(2).size());
-            assertEquals(0, trackedJobs.get(3).size());
+            assertEquals(0, trackedJobs.get(2).size());
+            assertEquals(1, trackedJobs.get(3).size());
+            assertEquals(0, trackedJobs.get(4).size());
 
-            flexTracker.resetJobNumDroppedConstraints(jobs[0]);
-            assertEquals(0, trackedJobs.get(0).size());
+            flexTracker.resetJobNumDroppedConstraints(jobs[0], FROZEN_TIME);
+            assertEquals(2, trackedJobs.get(0).size());
             assertEquals(0, trackedJobs.get(1).size());
-            assertEquals(2, trackedJobs.get(2).size());
-            assertEquals(0, trackedJobs.get(3).size());
+            assertEquals(0, trackedJobs.get(2).size());
+            assertEquals(2, trackedJobs.get(3).size());
+            assertEquals(0, trackedJobs.get(4).size());
 
-            flexTracker.adjustJobsRequiredConstraints(jobs[0], -2);
-
-            assertEquals(1, trackedJobs.get(0).size());
-            assertEquals(0, trackedJobs.get(1).size());
-            assertEquals(1, trackedJobs.get(2).size());
-            assertEquals(0, trackedJobs.get(3).size());
-
-            JobSchedulerService.sElapsedRealtimeClock =
-                    Clock.fixed(Instant.ofEpochMilli((DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 2)
-                            + HOUR_IN_MILLIS), ZoneOffset.UTC);
-
-            flexTracker.resetJobNumDroppedConstraints(jobs[0]);
-            assertEquals(0, trackedJobs.get(0).size());
+            flexTracker.adjustJobsRequiredConstraints(jobs[0], -2, FROZEN_TIME);
+            assertEquals(2, trackedJobs.get(0).size());
             assertEquals(1, trackedJobs.get(1).size());
+            assertEquals(0, trackedJobs.get(2).size());
+            assertEquals(1, trackedJobs.get(3).size());
+            assertEquals(0, trackedJobs.get(4).size());
+
+            final long nowElapsed = ((DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 2)
+                    + HOUR_IN_MILLIS);
+            JobSchedulerService.sElapsedRealtimeClock =
+                    Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+
+            flexTracker.resetJobNumDroppedConstraints(jobs[0], nowElapsed);
+            assertEquals(2, trackedJobs.get(0).size());
+            assertEquals(0, trackedJobs.get(1).size());
             assertEquals(1, trackedJobs.get(2).size());
-            assertEquals(0, trackedJobs.get(3).size());
+            assertEquals(1, trackedJobs.get(3).size());
+            assertEquals(0, trackedJobs.get(4).size());
         }
     }
 
@@ -568,6 +616,15 @@
     }
 
     @Test
+    public void testExceptions_RescheduledOnce() {
+        JobInfo.Builder jb = createJob(0);
+        JobStatus js = createJobStatus("time", jb);
+        js = new JobStatus(
+                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 1, FROZEN_TIME, FROZEN_TIME);
+        assertFalse(js.hasFlexibilityConstraint());
+    }
+
+    @Test
     public void testExceptions_None() {
         JobInfo.Builder jb = createJob(0);
         JobStatus js = createJobStatus("testExceptions_None", jb);
@@ -615,13 +672,13 @@
 
     @Test
     public void testSetConstraintSatisfied_Constraints() {
-        mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false);
+        mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false, FROZEN_TIME);
         assertFalse(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE));
 
-        mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, true);
+        mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, true, FROZEN_TIME);
         assertTrue(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE));
 
-        mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false);
+        mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false, FROZEN_TIME);
         assertFalse(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE));
     }
 
@@ -651,20 +708,21 @@
                         createJobStatus(String.valueOf(i), jb), null);
             }
         }
-        mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING, false);
-        mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false);
-        mFlexibilityController.setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, false);
+        mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING, false, FROZEN_TIME);
+        mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false, FROZEN_TIME);
+        mFlexibilityController.setConstraintSatisfied(
+                CONSTRAINT_BATTERY_NOT_LOW, false, FROZEN_TIME);
 
         assertEquals(0, mFlexibilityController.mSatisfiedFlexibleConstraints);
 
         for (int i = 0; i < constraintCombinations.length; i++) {
             constraints = constraintCombinations[i];
             mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING,
-                    (constraints & CONSTRAINT_CHARGING) != 0);
+                    (constraints & CONSTRAINT_CHARGING) != 0, FROZEN_TIME);
             mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE,
-                    (constraints & CONSTRAINT_IDLE) != 0);
+                    (constraints & CONSTRAINT_IDLE) != 0, FROZEN_TIME);
             mFlexibilityController.setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW,
-                    (constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0);
+                    (constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0, FROZEN_TIME);
 
             assertEquals(constraints, mFlexibilityController.mSatisfiedFlexibleConstraints);
             synchronized (mFlexibilityController.mLock) {
@@ -679,6 +737,7 @@
         JobInfo.Builder jb = createJob(22).setOverrideDeadline(100L);
         JobStatus js = createJobStatus("testResetJobNumDroppedConstraints", jb);
         js.adjustNumRequiredFlexibleConstraints(3);
+        long nowElapsed;
 
         mFlexibilityController.mFlexibilityTracker.add(js);
 
@@ -687,45 +746,51 @@
         assertEquals(1, mFlexibilityController
                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
 
-        JobSchedulerService.sElapsedRealtimeClock =
-                Clock.fixed(Instant.ofEpochMilli(155L), ZoneOffset.UTC);
 
-        mFlexibilityController.mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1);
+        nowElapsed = 155L;
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+
+        mFlexibilityController.mFlexibilityTracker
+                .adjustJobsRequiredConstraints(js, -1, nowElapsed);
 
         assertEquals(2, js.getNumRequiredFlexibleConstraints());
         assertEquals(1, js.getNumDroppedFlexibleConstraints());
         assertEquals(1, mFlexibilityController
                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
 
-        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
 
         assertEquals(2, js.getNumRequiredFlexibleConstraints());
         assertEquals(1, js.getNumDroppedFlexibleConstraints());
         assertEquals(1, mFlexibilityController
                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
 
+        nowElapsed = 140L;
         JobSchedulerService.sElapsedRealtimeClock =
-                Clock.fixed(Instant.ofEpochMilli(140L), ZoneOffset.UTC);
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
-        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
 
         assertEquals(3, js.getNumRequiredFlexibleConstraints());
         assertEquals(0, js.getNumDroppedFlexibleConstraints());
         assertEquals(1, mFlexibilityController
                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
 
+        nowElapsed = 175L;
         JobSchedulerService.sElapsedRealtimeClock =
-                Clock.fixed(Instant.ofEpochMilli(175), ZoneOffset.UTC);
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
-        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
 
         assertEquals(0, js.getNumRequiredFlexibleConstraints());
         assertEquals(3, js.getNumDroppedFlexibleConstraints());
 
+        nowElapsed = 165L;
         JobSchedulerService.sElapsedRealtimeClock =
-                Clock.fixed(Instant.ofEpochMilli(165L), ZoneOffset.UTC);
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
-        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
 
         assertEquals(1, js.getNumRequiredFlexibleConstraints());
         assertEquals(2, js.getNumDroppedFlexibleConstraints());
@@ -745,12 +810,14 @@
 
         mFlexibilityController.maybeStartTrackingJobLocked(js, null);
 
+        final long nowElapsed = 150L;
         JobSchedulerService.sElapsedRealtimeClock =
-                Clock.fixed(Instant.ofEpochMilli(150L), ZoneOffset.UTC);
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
         mFlexibilityController.mPrefetchChangedListener.onPrefetchCacheUpdated(
                 jobs, js.getUserId(), js.getSourcePackageName(), Long.MAX_VALUE,
-                1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS);
+                1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
+                nowElapsed);
 
         assertEquals(150L,
                 (long) mFlexibilityController.mPrefetchLifeCycleStart
@@ -758,7 +825,7 @@
         assertEquals(150L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
         assertEquals(1150L,
                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, 150L));
-        assertEquals(0, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+        assertEquals(0, mFlexibilityController.getCurPercentOfLifecycleLocked(js, FROZEN_TIME));
         assertEquals(650L, mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js));
         assertEquals(3, js.getNumRequiredFlexibleConstraints());
@@ -798,6 +865,29 @@
 
     }
 
+    @Test
+    public void testDeviceDisabledFlexibility_Auto() {
+        when(mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(true);
+        mFlexibilityController =
+                new FlexibilityController(mJobSchedulerService, mPrefetchController);
+        assertFalse(mFlexibilityController.mFlexibilityEnabled);
+
+        JobStatus js = createJobStatus("testIsAuto", createJob(0));
+
+        mFlexibilityController.maybeStartTrackingJobLocked(js, null);
+        assertTrue(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
+
+        setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
+        assertFalse(mFlexibilityController.mFlexibilityEnabled);
+
+        ArrayList<ArraySet<JobStatus>> jobs =
+                mFlexibilityController.mFlexibilityTracker.getArrayList();
+        for (int i = 0; i < jobs.size(); i++) {
+            assertEquals(0, jobs.get(i).size());
+        }
+    }
+
     private void setUidBias(int uid, int bias) {
         int prevBias = mJobSchedulerService.getUidBias(uid);
         doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index 969389c..bb477b1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -490,9 +490,9 @@
         final PrefetchController.PrefetchChangedListener prefetchChangedListener =
                 new PrefetchController.PrefetchChangedListener() {
                     @Override
-                    public void onPrefetchCacheUpdated(
-                            ArraySet<JobStatus> jobs, int userId, String pkgName,
-                            long prevEstimatedLaunchTime, long newEstimatedLaunchTime) {
+                    public void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs,
+                            int userId, String pkgName, long prevEstimatedLaunchTime,
+                            long newEstimatedLaunchTime, long nowElapsed) {
                         onPrefetchCacheChangedCalled[0] = true;
                     }
                 };
diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
index 3de006c..81e0664 100644
--- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -507,7 +507,7 @@
     }
 
     @Test
-    public void dump_callerDoesNotHavePermission_ignored() {
+    public void dump_callerDoesNotHaveDumpPermission_ignored() {
         when(mContextMock.checkCallingOrSelfPermission(
                 android.Manifest.permission.DUMP)).thenReturn(
                 PackageManager.PERMISSION_DENIED);
@@ -518,6 +518,18 @@
         verifyNoMoreInteractions(mNonSystemUserBackupManagerService);
     }
 
+    @Test
+    public void dump_callerDoesNotHavePackageUsageStatsPermission_ignored() {
+        when(mContextMock.checkCallingOrSelfPermission(
+                Manifest.permission.PACKAGE_USAGE_STATS)).thenReturn(
+                PackageManager.PERMISSION_DENIED);
+
+        mService.dump(mFileDescriptorStub, mPrintWriterMock, new String[0]);
+
+        verifyNoMoreInteractions(mUserBackupManagerService);
+        verifyNoMoreInteractions(mNonSystemUserBackupManagerService);
+    }
+
     /**
      * Test that {@link BackupManagerService#dump()} dumps system user information before non-system
      * user information.
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java
new file mode 100644
index 0000000..903ed90
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
+import android.hardware.face.IFaceService;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FaceServiceRegistryTest {
+
+    private static final int SENSOR_ID_1 = 1;
+    private static final int SENSOR_ID_2 = 2;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private IBiometricService mBiometricService;
+    @Mock
+    private IFaceService mFaceService;
+    @Mock
+    private ServiceProvider mProvider1;
+    @Mock
+    private ServiceProvider mProvider2;
+    @Captor
+    private ArgumentCaptor<Integer> mIdCaptor;
+    @Captor
+    private ArgumentCaptor<Integer> mStrengthCaptor;
+
+    private FaceSensorPropertiesInternal mProvider1Props;
+    private FaceSensorPropertiesInternal mProvider2Props;
+    private FaceServiceRegistry mRegistry;
+
+    @Before
+    public void setup() {
+        mProvider1Props = new FaceSensorPropertiesInternal(SENSOR_ID_1,
+                STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */,
+                List.of(), FaceSensorProperties.TYPE_RGB,
+                true /* supportsFace Detection */,
+                true /* supportsSelfIllumination */,
+                false /* resetLockoutRequiresHardwareAuthToken */);
+        mProvider2Props = new FaceSensorPropertiesInternal(SENSOR_ID_2,
+                STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+                List.of(), FaceSensorProperties.TYPE_IR,
+                true /* supportsFace Detection */,
+                true /* supportsSelfIllumination */,
+                false /* resetLockoutRequiresHardwareAuthToken */);
+
+        when(mProvider1.getSensorProperties()).thenReturn(List.of(mProvider1Props));
+        when(mProvider1.containsSensor(eq(SENSOR_ID_1))).thenReturn(true);
+        when(mProvider2.getSensorProperties()).thenReturn(List.of(mProvider2Props));
+        when(mProvider2.containsSensor(eq(SENSOR_ID_2))).thenReturn(true);
+        mRegistry = new FaceServiceRegistry(mFaceService, () -> mBiometricService);
+    }
+
+    @Test
+    public void registersAllProviders() throws Exception {
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+        assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2);
+        assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props);
+        verify(mBiometricService, times(2)).registerAuthenticator(
+                mIdCaptor.capture(), eq(TYPE_FACE), mStrengthCaptor.capture(), any());
+        assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2);
+        assertThat(mStrengthCaptor.getAllValues())
+                .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG);
+    }
+
+    @Test
+    public void getsProviderById() {
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+        assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
+        assertThat(mRegistry.getProviderForSensor(SENSOR_ID_2)).isSameInstanceAs(mProvider2);
+        assertThat(mRegistry.getProviderForSensor(500)).isNull();
+    }
+
+    @Test
+    public void getsSingleProvider() {
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1));
+
+        assertThat(mRegistry.getSingleProvider().second).isSameInstanceAs(mProvider1);
+        assertThat(mRegistry.getProviders()).containsExactly(mProvider1);
+        assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
+    }
+
+    @Test
+    public void getSingleProviderFindsFirstWhenMultiple() {
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+        assertThat(mRegistry.getSingleProvider().second).isSameInstanceAs(mProvider1);
+    }
+
+    @Test
+    public void registersListenerBeforeAllRegistered() {
+        final List<FaceSensorPropertiesInternal> all = new ArrayList<>();
+        mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
+            @Override
+            public void onAllAuthenticatorsRegistered(
+                    List<FaceSensorPropertiesInternal> sensors) {
+                all.addAll(sensors);
+            }
+        });
+
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+        assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
+    }
+
+    @Test
+    public void registersListenerAfterAllRegistered() {
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+        final List<FaceSensorPropertiesInternal> all = new ArrayList<>();
+        mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
+            @Override
+            public void onAllAuthenticatorsRegistered(
+                    List<FaceSensorPropertiesInternal> sensors) {
+                all.addAll(sensors);
+            }
+        });
+
+        assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
index 5f88c99..3b66eab 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.fingerprint;
 
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -24,38 +26,75 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricStateListener;
+import android.hardware.biometrics.SensorPropertiesInternal;
+import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.BiometricServiceProvider;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.EnrollClient;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
 
 @Presubmit
 @SmallTest
 public class BiometricStateCallbackTest {
 
-    private BiometricStateCallback mCallback;
+    private static final int USER_ID = 10;
+    private static final int SENSOR_ID = 2;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    private BiometricStateCallback<FakeProvider, SensorPropertiesInternal> mCallback;
 
     @Mock
-    BiometricStateListener mBiometricStateListener;
+    private UserManager mUserManager;
+    @Mock
+    private BiometricStateListener mBiometricStateListener;
+    @Mock
+    private FakeProvider mFakeProvider;
+
+    private SensorPropertiesInternal mFakeProviderProps;
 
     @Before
     public void setup() {
-        MockitoAnnotations.initMocks(this);
+        mFakeProviderProps = new SensorPropertiesInternal(SENSOR_ID, STRENGTH_STRONG,
+                5 /* maxEnrollmentsPerUser */, List.of(),
+                false /* resetLockoutRequiresHardwareAuthToken */,
+                false /* resetLockoutRequiresChallenge */);
+        when(mFakeProvider.getSensorProperties()).thenReturn(List.of(mFakeProviderProps));
+        when(mFakeProvider.getSensorProperties(eq(SENSOR_ID))).thenReturn(mFakeProviderProps);
+        when(mFakeProvider.hasEnrollments(eq(SENSOR_ID), eq(USER_ID))).thenReturn(true);
+        when(mUserManager.getAliveUsers()).thenReturn(
+                List.of(new UserInfo(USER_ID, "name", 0)));
+        when(mBiometricStateListener.asBinder()).thenReturn(mBiometricStateListener);
 
-        mCallback = new BiometricStateCallback();
+
+        mCallback = new BiometricStateCallback<>(mUserManager);
         mCallback.registerBiometricStateListener(mBiometricStateListener);
     }
 
     @Test
+    public void startNotifiesEnrollments() {
+        mCallback.start(List.of(mFakeProvider));
+
+        verify(mBiometricStateListener).onEnrollmentsChanged(eq(USER_ID), eq(SENSOR_ID), eq(true));
+    }
+
+    @Test
     public void testNoEnrollmentsToEnrollments_callbackNotified() {
         testEnrollmentCallback(true /* changed */, true /* isNowEnrolled */,
                 true /* expectCallback */, true /* expectedCallbackValue */);
@@ -73,6 +112,14 @@
                 false /* expectCallback */, false /* expectedCallbackValue */);
     }
 
+    @Test
+    public void testBinderDeath() {
+        mCallback.binderDied(mBiometricStateListener.asBinder());
+
+        testEnrollmentCallback(true /* changed */, false /* isNowEnrolled */,
+                false /* expectCallback */, false /* expectedCallbackValue */);
+    }
+
     private void testEnrollmentCallback(boolean changed, boolean isNowEnrolled,
             boolean expectCallback, boolean expectedCallbackValue) {
         EnrollClient<?> client = mock(EnrollClient.class);
@@ -102,4 +149,6 @@
         verify(mBiometricStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(),
                 anyBoolean());
     }
+
+    private interface FakeProvider extends BiometricServiceProvider<SensorPropertiesInternal> {}
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java
new file mode 100644
index 0000000..13c3f64
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.hardware.fingerprint.IFingerprintService;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FingerprintServiceRegistryTest {
+
+    private static final int SENSOR_ID_1 = 1;
+    private static final int SENSOR_ID_2 = 2;
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private IBiometricService mBiometricService;
+    @Mock
+    private IFingerprintService mFingerprintService;
+    @Mock
+    private ServiceProvider mProvider1;
+    @Mock
+    private ServiceProvider mProvider2;
+    @Captor
+    private ArgumentCaptor<Integer> mIdCaptor;
+    @Captor
+    private ArgumentCaptor<Integer> mStrengthCaptor;
+
+    private FingerprintSensorPropertiesInternal mProvider1Props;
+    private FingerprintSensorPropertiesInternal mProvider2Props;
+    private FingerprintServiceRegistry mRegistry;
+
+    @Before
+    public void setup() {
+        mProvider1Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_1,
+                STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */,
+                List.of(), FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+                false /* resetLockoutRequiresHardwareAuthToken */);
+        mProvider2Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_2,
+                STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+                List.of(), FingerprintSensorProperties.TYPE_UNKNOWN,
+                false /* resetLockoutRequiresHardwareAuthToken */);
+
+        when(mProvider1.getSensorProperties()).thenReturn(List.of(mProvider1Props));
+        when(mProvider1.containsSensor(eq(SENSOR_ID_1))).thenReturn(true);
+        when(mProvider2.getSensorProperties()).thenReturn(List.of(mProvider2Props));
+        when(mProvider2.containsSensor(eq(SENSOR_ID_2))).thenReturn(true);
+        mRegistry = new FingerprintServiceRegistry(mFingerprintService, () -> mBiometricService);
+    }
+
+    @Test
+    public void registersAllProviders() throws Exception {
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+        assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2);
+        assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props);
+        verify(mBiometricService, times(2)).registerAuthenticator(
+                mIdCaptor.capture(), eq(TYPE_FINGERPRINT), mStrengthCaptor.capture(), any());
+        assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2);
+        assertThat(mStrengthCaptor.getAllValues())
+                .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG);
+    }
+
+    @Test
+    public void getsProviderById() {
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+        assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
+        assertThat(mRegistry.getProviderForSensor(SENSOR_ID_2)).isSameInstanceAs(mProvider2);
+        assertThat(mRegistry.getProviderForSensor(500)).isNull();
+    }
+
+    @Test
+    public void getsSingleProvider() {
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1));
+
+        assertThat(mRegistry.getSingleProvider().second).isSameInstanceAs(mProvider1);
+        assertThat(mRegistry.getProviders()).containsExactly(mProvider1);
+        assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
+    }
+
+    @Test
+    public void getSingleProviderFindsFirstWhenMultiple() {
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+        assertThat(mRegistry.getSingleProvider().second).isSameInstanceAs(mProvider1);
+    }
+
+    @Test
+    public void registersListenerBeforeAllRegistered() {
+        final List<FingerprintSensorPropertiesInternal> all = new ArrayList<>();
+        mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+            @Override
+            public void onAllAuthenticatorsRegistered(
+                    List<FingerprintSensorPropertiesInternal> sensors) {
+                all.addAll(sensors);
+            }
+        });
+
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+        assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
+    }
+
+    @Test
+    public void registersListenerAfterAllRegistered() {
+        mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+        final List<FingerprintSensorPropertiesInternal> all = new ArrayList<>();
+        mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+            @Override
+            public void onAllAuthenticatorsRegistered(
+                    List<FingerprintSensorPropertiesInternal> sensors) {
+                all.addAll(sensors);
+            }
+        });
+
+        assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index ca3677e..a4048a2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -32,7 +32,8 @@
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.SensorLocation;
 import android.hardware.biometrics.fingerprint.SensorProps;
-import android.os.RemoteException;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.testing.TestableContext;
@@ -52,6 +53,8 @@
 import org.mockito.junit.MockitoRule;
 
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 @Presubmit
 @SmallTest
@@ -94,9 +97,12 @@
 
         mContext.getTestablePermissions().setPermission(
                 USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_GRANTED);
+    }
 
+    private void initServiceWith(String... aidlInstances) {
         mService = new FingerprintService(mContext, mBiometricContext,
                 () -> mIBiometricService,
+                () -> aidlInstances,
                 (fqName) -> {
                     if (fqName.endsWith(NAME_DEFAULT)) return mIFingerprintDefault;
                     if (fqName.endsWith(NAME_VIRTUAL)) return mIFingerprintVirtual;
@@ -105,29 +111,50 @@
     }
 
     @Test
-    public void registerAuthenticators_defaultOnly() throws RemoteException {
-        mService.registerAuthenticatorsForService(List.of(NAME_DEFAULT, NAME_VIRTUAL), List.of());
+    public void registerAuthenticators_defaultOnly() throws Exception {
+        initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
+
+        mService.mServiceWrapper.registerAuthenticators(List.of());
+        waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
     }
 
     @Test
-    public void registerAuthenticators_virtualOnly() throws RemoteException {
+    public void registerAuthenticators_virtualOnly() throws Exception {
+        initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
         Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
                 Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
 
-        mService.registerAuthenticatorsForService(List.of(NAME_DEFAULT, NAME_VIRTUAL), List.of());
+        mService.mServiceWrapper.registerAuthenticators(List.of());
+        waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
     }
 
     @Test
-    public void registerAuthenticators_virtualAlwaysWhenNoOther() throws RemoteException {
-        mService.registerAuthenticatorsForService(List.of(NAME_VIRTUAL), List.of());
+    public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception {
+        initServiceWith(NAME_VIRTUAL);
+
+        mService.mServiceWrapper.registerAuthenticators(List.of());
+        waitForRegistration();
 
         verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
     }
 
+    private void waitForRegistration() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
+                new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+                    @Override
+                    public void onAllAuthenticatorsRegistered(
+                            List<FingerprintSensorPropertiesInternal> sensors) {
+                        latch.countDown();
+                    }
+                });
+        latch.await(5, TimeUnit.SECONDS);
+    }
+
     private static SensorProps createProps(int id, byte strength, byte type) {
         final SensorProps props = new SensorProps();
         props.commonProps = new CommonProps();
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 34b40c7..b1ad8ec 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -16,53 +16,79 @@
 
 package com.android.server.pm;
 
+import static android.os.UserManager.DISALLOW_USER_SWITCH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.ActivityManager;
+import android.app.PropertyInvalidatedCache;
+import android.content.Context;
 import android.content.pm.UserInfo;
 import android.os.Bundle;
 import android.os.FileUtils;
+import android.os.Looper;
 import android.os.Parcelable;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Postsubmit;
 import android.support.test.uiautomator.UiDevice;
-import android.test.AndroidTestCase;
-import android.text.TextUtils;
 import android.util.AtomicFile;
 
 import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.io.File;
 import java.io.IOException;
 import java.util.Arrays;
 
+/** Test {@link UserManagerService} functionality. */
 @Postsubmit
-@SmallTest
-public class UserManagerServiceTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class UserManagerServiceTest {
     private static String[] STRING_ARRAY = new String[] {"<tag", "<![CDATA["};
     private File restrictionsFile;
     private int tempUserId = UserHandle.USER_NULL;
+    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+    private UserManagerService mUserManagerService;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setup() throws Exception {
+        // Currently UserManagerService cannot be instantiated twice inside a VM without a cleanup
+        // TODO: Remove once UMS supports proper dependency injection
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        // Disable binder caches in this process.
+        PropertyInvalidatedCache.disableForTestMode();
+
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
+        mUserManagerService = new UserManagerService(InstrumentationRegistry.getContext());
+
         restrictionsFile = new File(mContext.getCacheDir(), "restrictions.xml");
         restrictionsFile.delete();
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void teardown() throws Exception {
         restrictionsFile.delete();
         if (tempUserId != UserHandle.USER_NULL) {
             UserManager.get(mContext).removeUser(tempUserId);
         }
-        super.tearDown();
     }
 
+    @Test
     public void testWriteReadApplicationRestrictions() throws IOException {
         AtomicFile atomicFile = new AtomicFile(restrictionsFile);
         Bundle bundle = createBundle();
         UserManagerService.writeApplicationRestrictionsLAr(bundle, atomicFile);
-        assertTrue(atomicFile.getBaseFile().exists());
+        assertThat(atomicFile.getBaseFile().exists()).isTrue();
         String s = FileUtils.readTextFile(restrictionsFile, 10000, "");
         System.out.println("restrictionsFile: " + s);
         bundle = UserManagerService.readApplicationRestrictionsLAr(atomicFile);
@@ -70,22 +96,22 @@
         assertBundle(bundle);
     }
 
+    @Test
     public void testAddUserWithAccount() {
         UserManager um = UserManager.get(mContext);
         UserInfo user = um.createUser("Test User", 0);
-        assertNotNull(user);
+        assertThat(user).isNotNull();
         tempUserId = user.id;
         String accountName = "Test Account";
         um.setUserAccount(tempUserId, accountName);
-        assertEquals(accountName, um.getUserAccount(tempUserId));
+        assertThat(um.getUserAccount(tempUserId)).isEqualTo(accountName);
     }
 
+    @Test
     public void testUserSystemPackageWhitelist() throws Exception {
         String cmd = "cmd user report-system-user-package-whitelist-problems --critical-only";
         final String result = runShellCommand(cmd);
-        if (!TextUtils.isEmpty(result)) {
-            fail("Command '" + cmd + " reported errors:\n" + result);
-        }
+        assertThat(result).isEmpty();
     }
 
     private Bundle createBundle() {
@@ -114,26 +140,141 @@
     }
 
     private void assertBundle(Bundle bundle) {
-        assertFalse(bundle.getBoolean("boolean_0"));
-        assertTrue(bundle.getBoolean("boolean_1"));
-        assertEquals(100, bundle.getInt("integer"));
-        assertEquals("", bundle.getString("empty"));
-        assertEquals("text", bundle.getString("string"));
-        assertEquals(Arrays.asList(STRING_ARRAY), Arrays.asList(bundle.getStringArray("string[]")));
+        assertThat(bundle.getBoolean("boolean_0")).isFalse();
+        assertThat(bundle.getBoolean("boolean_1")).isTrue();
+        assertThat(bundle.getInt("integer")).isEqualTo(100);
+        assertThat(bundle.getString("empty")).isEqualTo("");
+        assertThat(bundle.getString("string")).isEqualTo("text");
+        assertThat(Arrays.asList(bundle.getStringArray("string[]")))
+                .isEqualTo(Arrays.asList(STRING_ARRAY));
         Parcelable[] bundle_array = bundle.getParcelableArray("bundle_array");
-        assertEquals(2, bundle_array.length);
+        assertThat(bundle_array.length).isEqualTo(2);
         Bundle bundle1 = (Bundle) bundle_array[0];
-        assertEquals("bundle_array_string", bundle1.getString("bundle_array_string"));
-        assertNotNull(bundle1.getBundle("bundle_array_bundle"));
+        assertThat(bundle1.getString("bundle_array_string"))
+                .isEqualTo("bundle_array_string");
+        assertThat(bundle1.getBundle("bundle_array_bundle")).isNotNull();
         Bundle bundle2 = (Bundle) bundle_array[1];
-        assertEquals("bundle_array_string2", bundle2.getString("bundle_array_string2"));
+        assertThat(bundle2.getString("bundle_array_string2"))
+                .isEqualTo("bundle_array_string2");
         Bundle childBundle = bundle.getBundle("bundle");
-        assertEquals("bundle_string", childBundle.getString("bundle_string"));
-        assertEquals(1, childBundle.getInt("bundle_int"));
+        assertThat(childBundle.getString("bundle_string"))
+                .isEqualTo("bundle_string");
+        assertThat(childBundle.getInt("bundle_int")).isEqualTo(1);
     }
 
+    @Test
+    public void assertHasUserRestriction() throws Exception {
+        int userId = ActivityManager.getCurrentUser();
+
+        mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, true, userId);
+        assertThat(mUserManagerService.hasUserRestriction(DISALLOW_USER_SWITCH, userId)).isTrue();
+
+        mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, false, userId);
+        assertThat(mUserManagerService.hasUserRestriction(DISALLOW_USER_SWITCH, userId)).isFalse();
+    }
+
+    @Test
+    public void assertIsUserSwitcherEnabledOnMultiUserSettings() throws Exception {
+        int userId = ActivityManager.getCurrentUser();
+        resetUserSwitcherEnabled();
+
+        setUserSwitch(false);
+        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
+
+        setUserSwitch(true);
+        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
+    }
+
+    @Test
+    public void assertIsUserSwitcherEnabledOnMaxSupportedUsers()  throws Exception {
+        int userId = ActivityManager.getCurrentUser();
+        setMaxSupportedUsers(1);
+
+        assertThat(UserManager.supportsMultipleUsers()).isFalse();
+        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
+
+        setMaxSupportedUsers(8);
+
+        assertThat(UserManager.supportsMultipleUsers()).isTrue();
+        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
+    }
+
+
+    @Test
+    public void assertIsUserSwitcherEnabledOnShowMultiuserUI()  throws Exception {
+        int userId = ActivityManager.getCurrentUser();
+        setShowMultiuserUI(false);
+
+        assertThat(UserManager.supportsMultipleUsers()).isFalse();
+        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
+
+        setShowMultiuserUI(true);
+
+        assertThat(UserManager.supportsMultipleUsers()).isTrue();
+        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
+    }
+
+    @Test
+    public void assertIsUserSwitcherEnabledOnUserRestrictions() throws Exception {
+        int userId = ActivityManager.getCurrentUser();
+        resetUserSwitcherEnabled();
+
+        mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, true, userId);
+        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
+
+        mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, false, userId);
+        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
+    }
+
+    @Test
+    public void assertIsUserSwitcherEnabledOnDemoMode()  throws Exception {
+        int userId = ActivityManager.getCurrentUser();
+        resetUserSwitcherEnabled();
+
+        setDeviceDemoMode(true);
+        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
+
+        setDeviceDemoMode(false);
+        assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
+    }
+
+    private void resetUserSwitcherEnabled() throws Exception {
+        int userId = ActivityManager.getCurrentUser();
+        setUserSwitch(true);
+        setShowMultiuserUI(true);
+        setDeviceDemoMode(false);
+        setMaxSupportedUsers(8);
+        mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, false, userId);
+    }
+
+    private void setUserSwitch(boolean enabled) {
+        android.provider.Settings.Global.putInt(mContext.getContentResolver(),
+                android.provider.Settings.Global.USER_SWITCHER_ENABLED, enabled ? 1 : 0);
+    }
+
+    private void setDeviceDemoMode(boolean enabled) {
+        android.provider.Settings.Global.putInt(mContext.getContentResolver(),
+                android.provider.Settings.Global.DEVICE_DEMO_MODE, enabled ? 1 : 0);
+    }
+
+
     private static String runShellCommand(String cmd) throws Exception {
         return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
                 .executeShellCommand(cmd);
     }
+
+    private static String setSystemProperty(String name, String value) throws Exception {
+        final String oldValue = runShellCommand("getprop " + name);
+        assertThat(runShellCommand("setprop " + name + " " + value))
+                .isEqualTo("");
+        return oldValue;
+    }
+
+    private static void setMaxSupportedUsers(int max) throws Exception {
+        setSystemProperty("fw.max_users", String.valueOf(max));
+    }
+
+    public static void setShowMultiuserUI(boolean show) throws Exception {
+        setSystemProperty("fw.show_multiuserui", String.valueOf(show));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index 5c934852..61a7f38 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -28,7 +28,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.BatteryStatsHistory;
-import com.android.internal.os.Clock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -50,14 +49,13 @@
     private final Parcel mHistoryBuffer = Parcel.obtain();
     private File mSystemDir;
     private File mHistoryDir;
-    private final Clock mClock = new MockClock();
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         Context context = InstrumentationRegistry.getContext();
         mSystemDir = context.getDataDir();
-        mHistoryDir = new File(mSystemDir, "battery-history");
+        mHistoryDir = new File(mSystemDir, BatteryStatsHistory.HISTORY_DIR);
         String[] files = mHistoryDir.list();
         if (files != null) {
             for (int i = 0; i < files.length; i++) {
@@ -69,8 +67,8 @@
 
     @Test
     public void testConstruct() {
-        BatteryStatsHistory history = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
-                null, mClock);
+        BatteryStatsHistory history =
+                new BatteryStatsHistory(mHistoryBuffer, mSystemDir, () -> 32);
         createActiveFile(history);
         verifyFileNumbers(history, Arrays.asList(0));
         verifyActiveFile(history, "0.bin");
@@ -78,8 +76,8 @@
 
     @Test
     public void testStartNextFile() {
-        BatteryStatsHistory history = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
-                null, mClock);
+        BatteryStatsHistory history =
+                new BatteryStatsHistory(mHistoryBuffer, mSystemDir, () -> 32);
         List<Integer> fileList = new ArrayList<>();
         fileList.add(0);
         createActiveFile(history);
@@ -116,13 +114,13 @@
         assertEquals(0, history.getHistoryUsedSize());
 
         // create a new BatteryStatsHistory object, it will pick up existing history files.
-        BatteryStatsHistory history2 = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
-                null, mClock);
-        // verify constructor can pick up all files from file system.
+        BatteryStatsHistory history2 =
+                new BatteryStatsHistory(mHistoryBuffer, mSystemDir, () -> 32);
+        // verify construct can pick up all files from file system.
         verifyFileNumbers(history2, fileList);
         verifyActiveFile(history2, "33.bin");
 
-        history2.reset();
+        history2.resetAllFiles();
         createActiveFile(history2);
         // verify all existing files are deleted.
         for (int i = 2; i < 33; ++i) {
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index 570b2ee..713e786 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -63,7 +63,6 @@
     MockBatteryStatsImpl(Clock clock, File historyDirectory) {
         super(clock, historyDirectory);
         initTimersAndCounters();
-        setMaxHistoryBuffer(128 * 1024);
 
         setExternalStatsSyncLocked(mExternalStatsSync);
         informThatAllExternalStatsAreFlushed();
@@ -105,6 +104,12 @@
         return mForceOnBattery ? true : super.isOnBattery();
     }
 
+    public void forceRecordAllHistory() {
+        mHaveBatteryLevel = true;
+        mRecordingHistory = true;
+        mRecordAllHistory = true;
+    }
+
     public TimeBase getOnBatteryBackgroundTimeBase(int uid) {
         return getUidStatsLocked(uid).mOnBatteryBackgroundTimeBase;
     }
@@ -196,14 +201,12 @@
     @GuardedBy("this")
     public MockBatteryStatsImpl setMaxHistoryFiles(int maxHistoryFiles) {
         mConstants.MAX_HISTORY_FILES = maxHistoryFiles;
-        mConstants.onChange();
         return this;
     }
 
     @GuardedBy("this")
     public MockBatteryStatsImpl setMaxHistoryBuffer(int maxHistoryBuffer) {
         mConstants.MAX_HISTORY_BUFFER = maxHistoryBuffer;
-        mConstants.onChange();
         return this;
     }
 
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 40cda34..ec48e23 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -4239,6 +4239,59 @@
     }
 
     @Test
+    public void testSubstituteAppName_hasPermission() throws RemoteException {
+        String subName = "Substitute Name";
+        when(mPackageManager.checkPermission(
+                eq(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME), any(), anyInt()))
+                .thenReturn(PERMISSION_GRANTED);
+        Bundle extras = new Bundle();
+        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, subName);
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .addExtras(extras);
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testSubstituteAppNamePermission", mUid, 0,
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+        waitForIdle();
+        NotificationRecord posted = mService.findNotificationLocked(
+                PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
+
+        assertTrue(posted.getNotification().extras
+                .containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME));
+        assertEquals(posted.getNotification().extras
+                .getString(Notification.EXTRA_SUBSTITUTE_APP_NAME), subName);
+    }
+
+    @Test
+    public void testSubstituteAppName_noPermission() throws RemoteException {
+        when(mPackageManager.checkPermission(
+                eq(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME), any(), anyInt()))
+                .thenReturn(PERMISSION_DENIED);
+        Bundle extras = new Bundle();
+        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, "Substitute Name");
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .addExtras(extras);
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testSubstituteAppNamePermission", mUid, 0,
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+        waitForIdle();
+        NotificationRecord posted = mService.findNotificationLocked(
+                PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
+
+        assertFalse(posted.getNotification().extras
+                .containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME));
+    }
+
+    @Test
     public void testGetNotificationCountLocked() {
         String sampleTagToExclude = null;
         int sampleIdToExclude = 0;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index d62ac99..598a22b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -93,7 +93,6 @@
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Parcel;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -2448,35 +2447,6 @@
     }
 
     @Test
-    public void testGetNotificationChannelGroup() throws Exception {
-        NotificationChannelGroup notDeleted = new NotificationChannelGroup("not", "deleted");
-        NotificationChannel base =
-                new NotificationChannel("not deleted", "belongs to notDeleted", IMPORTANCE_DEFAULT);
-        base.setGroup("not");
-        NotificationChannel convo =
-                new NotificationChannel("convo", "belongs to notDeleted", IMPORTANCE_DEFAULT);
-        convo.setGroup("not");
-        convo.setConversationId("not deleted", "banana");
-
-        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, notDeleted, true);
-        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, base, true, false);
-        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, convo, true, false);
-        mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, notDeleted, true);
-
-        NotificationChannelGroup g
-                = mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG_N_MR1, UID_N_MR1);
-        Parcel parcel = Parcel.obtain();
-        g.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-
-        NotificationChannelGroup g2
-                = mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG_N_MR1, UID_N_MR1);
-        Parcel parcel2 = Parcel.obtain();
-        g2.writeToParcel(parcel2, 0);
-        parcel2.setDataPosition(0);
-    }
-
-    @Test
     public void testOnUserRemoved() throws Exception {
         int[] user0Uids = {98, 235, 16, 3782};
         int[] user1Uids = new int[user0Uids.length];
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
index c12f0a9..d72cfc7 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java
@@ -17,7 +17,9 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.contains;
 import static org.mockito.ArgumentMatchers.eq;
@@ -30,6 +32,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
+import android.app.NotificationChannel;
 import android.app.Person;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
@@ -39,8 +42,11 @@
 import android.os.Bundle;
 import android.os.UserManager;
 import android.provider.ContactsContract;
+import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.text.SpannableString;
+import android.util.ArraySet;
+import android.util.LruCache;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -323,6 +329,69 @@
                 isNull());  // sort order
     }
 
+    @Test
+    public void testValidatePeople_needsLookupWhenNoCache() {
+        final Context mockContext = mock(Context.class);
+        final ContentResolver mockContentResolver = mock(ContentResolver.class);
+        when(mockContext.getContentResolver()).thenReturn(mockContentResolver);
+        final NotificationUsageStats mockNotificationUsageStats =
+                mock(NotificationUsageStats.class);
+
+        // Create validator with empty cache
+        ValidateNotificationPeople vnp = new ValidateNotificationPeople();
+        LruCache cache = new LruCache<String, ValidateNotificationPeople.LookupResult>(5);
+        vnp.initForTests(mockContext, mockNotificationUsageStats, cache);
+
+        NotificationRecord record = getNotificationRecord();
+        String[] callNumber = new String[]{"tel:12345678910"};
+        setNotificationPeople(record, callNumber);
+
+        // Returned ranking reconsideration not null indicates that there is a lookup to be done
+        RankingReconsideration rr = vnp.validatePeople(mockContext, record);
+        assertNotNull(rr);
+    }
+
+    @Test
+    public void testValidatePeople_noLookupWhenCached_andPopulatesContactInfo() {
+        final Context mockContext = mock(Context.class);
+        final ContentResolver mockContentResolver = mock(ContentResolver.class);
+        when(mockContext.getContentResolver()).thenReturn(mockContentResolver);
+        when(mockContext.getUserId()).thenReturn(1);
+        final NotificationUsageStats mockNotificationUsageStats =
+                mock(NotificationUsageStats.class);
+
+        // Information to be passed in & returned from the lookup result
+        String lookup = "lookup:contactinfohere";
+        String lookupTel = "16175551234";
+        float affinity = 0.7f;
+
+        // Create a fake LookupResult for the data we'll pass in
+        LruCache cache = new LruCache<String, ValidateNotificationPeople.LookupResult>(5);
+        ValidateNotificationPeople.LookupResult lr =
+                mock(ValidateNotificationPeople.LookupResult.class);
+        when(lr.getAffinity()).thenReturn(affinity);
+        when(lr.getPhoneNumbers()).thenReturn(new ArraySet<>(new String[]{lookupTel}));
+        when(lr.isExpired()).thenReturn(false);
+        cache.put(ValidateNotificationPeople.getCacheKey(1, lookup), lr);
+
+        // Create validator with the established cache
+        ValidateNotificationPeople vnp = new ValidateNotificationPeople();
+        vnp.initForTests(mockContext, mockNotificationUsageStats, cache);
+
+        NotificationRecord record = getNotificationRecord();
+        String[] peopleInfo = new String[]{lookup};
+        setNotificationPeople(record, peopleInfo);
+
+        // Returned ranking reconsideration null indicates that there is no pending work to be done
+        RankingReconsideration rr = vnp.validatePeople(mockContext, record);
+        assertNull(rr);
+
+        // Confirm that the affinity & phone number made it into our record
+        assertEquals(affinity, record.getContactAffinity(), 1e-8);
+        assertNotNull(record.getPhoneNumbers());
+        assertTrue(record.getPhoneNumbers().contains(lookupTel));
+    }
+
     // Creates a cursor that points to one item of Contacts data with the specified
     // columns.
     private Cursor makeMockCursor(int id, String lookupKey, int starred, int hasPhone) {
@@ -365,4 +434,17 @@
         String resultString = Arrays.toString(result);
         assertEquals(message + ": arrays differ", expectedString, resultString);
     }
+
+    private NotificationRecord getNotificationRecord() {
+        StatusBarNotification sbn = mock(StatusBarNotification.class);
+        Notification notification = mock(Notification.class);
+        when(sbn.getNotification()).thenReturn(notification);
+        return new NotificationRecord(mContext, sbn, mock(NotificationChannel.class));
+    }
+
+    private void setNotificationPeople(NotificationRecord r, String[] people) {
+        Bundle extras = new Bundle();
+        extras.putObject(Notification.EXTRA_PEOPLE_LIST, people);
+        r.getSbn().getNotification().extras = extras;
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 15d1a3c..4449483 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -158,6 +158,7 @@
 
 import com.android.internal.R;
 import com.android.server.wm.ActivityRecord.State;
+import com.android.server.wm.utils.WmDisplayCutout;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -551,7 +552,8 @@
         final Rect insets = new Rect();
         final DisplayInfo displayInfo = task.mDisplayContent.getDisplayInfo();
         final DisplayPolicy policy = task.mDisplayContent.getDisplayPolicy();
-        policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.displayCutout, insets);
+        policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth,
+                displayInfo.logicalHeight, WmDisplayCutout.NO_CUTOUT, insets);
         policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation);
         Task.intersectWithInsetsIfFits(stableRect, stableRect, insets);
 
@@ -592,7 +594,8 @@
         final Rect insets = new Rect();
         final DisplayInfo displayInfo = rootTask.mDisplayContent.getDisplayInfo();
         final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy();
-        policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.displayCutout, insets);
+        policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth,
+                displayInfo.logicalHeight, WmDisplayCutout.NO_CUTOUT, insets);
         policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation);
         Task.intersectWithInsetsIfFits(stableRect, stableRect, insets);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index 75ecfd8..d5e336b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -228,7 +228,7 @@
                 mAtm.getTaskChangeNotificationController();
         spyOn(taskChangeNotifier);
 
-        mAtm.setResumedActivityUncheckLocked(fullScreenActivityA, "resumeA");
+        mAtm.setLastResumedActivityUncheckLocked(fullScreenActivityA, "resumeA");
         verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskA.mTaskId) /* taskId */,
                 eq(true) /* focused */);
         reset(taskChangeNotifier);
@@ -237,7 +237,7 @@
                 .build();
         final Task taskB = fullScreenActivityB.getTask();
 
-        mAtm.setResumedActivityUncheckLocked(fullScreenActivityB, "resumeB");
+        mAtm.setLastResumedActivityUncheckLocked(fullScreenActivityB, "resumeB");
         verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskA.mTaskId) /* taskId */,
                 eq(false) /* focused */);
         verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskB.mTaskId) /* taskId */,
@@ -295,6 +295,7 @@
         activity1.moveFocusableActivityToTop("test");
         assertEquals(activity1.getUid(), pendingTopUid[0]);
         verify(mAtm).updateOomAdj();
+        verify(mAtm).setLastResumedActivityUncheckLocked(any(), eq("test"));
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
index 2158caf..aa5a74e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
@@ -25,10 +25,13 @@
 
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.util.Pair;
 import android.view.DisplayInfo;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.wm.utils.WmDisplayCutout;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ErrorCollector;
@@ -46,7 +49,8 @@
 
     @Test
     public void portrait() {
-        final DisplayInfo di = displayInfoForRotation(ROTATION_0, false /* withCutout */);
+        final Pair<DisplayInfo, WmDisplayCutout> di =
+                displayInfoForRotation(ROTATION_0, false /* withCutout */);
 
         verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
         verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT);
@@ -55,7 +59,8 @@
 
     @Test
     public void portrait_withCutout() {
-        final DisplayInfo di = displayInfoForRotation(ROTATION_0, true /* withCutout */);
+        final Pair<DisplayInfo, WmDisplayCutout> di =
+                displayInfoForRotation(ROTATION_0, true /* withCutout */);
 
         verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
         verifyNonDecorInsets(di, 0, DISPLAY_CUTOUT_HEIGHT, 0, NAV_BAR_HEIGHT);
@@ -64,7 +69,8 @@
 
     @Test
     public void landscape() {
-        final DisplayInfo di = displayInfoForRotation(ROTATION_90, false /* withCutout */);
+        final Pair<DisplayInfo, WmDisplayCutout> di =
+                displayInfoForRotation(ROTATION_90, false /* withCutout */);
 
         if (mDisplayPolicy.navigationBarCanMove()) {
             verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
@@ -79,7 +85,8 @@
 
     @Test
     public void landscape_withCutout() {
-        final DisplayInfo di = displayInfoForRotation(ROTATION_90, true /* withCutout */);
+        final Pair<DisplayInfo, WmDisplayCutout> di =
+                displayInfoForRotation(ROTATION_90, true /* withCutout */);
 
         if (mDisplayPolicy.navigationBarCanMove()) {
             verifyStableInsets(di, DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
@@ -94,7 +101,8 @@
 
     @Test
     public void seascape() {
-        final DisplayInfo di = displayInfoForRotation(ROTATION_270, false /* withCutout */);
+        final Pair<DisplayInfo, WmDisplayCutout> di =
+                displayInfoForRotation(ROTATION_270, false /* withCutout */);
 
         if (mDisplayPolicy.navigationBarCanMove()) {
             verifyStableInsets(di, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, 0, 0);
@@ -109,7 +117,8 @@
 
     @Test
     public void seascape_withCutout() {
-        final DisplayInfo di = displayInfoForRotation(ROTATION_270, true /* withCutout */);
+        final Pair<DisplayInfo, WmDisplayCutout> di =
+                displayInfoForRotation(ROTATION_270, true /* withCutout */);
 
         if (mDisplayPolicy.navigationBarCanMove()) {
             verifyStableInsets(di, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, DISPLAY_CUTOUT_HEIGHT, 0);
@@ -124,7 +133,8 @@
 
     @Test
     public void upsideDown() {
-        final DisplayInfo di = displayInfoForRotation(ROTATION_180, false /* withCutout */);
+        final Pair<DisplayInfo, WmDisplayCutout> di =
+                displayInfoForRotation(ROTATION_180, false /* withCutout */);
 
         verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
         verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT);
@@ -133,28 +143,34 @@
 
     @Test
     public void upsideDown_withCutout() {
-        final DisplayInfo di = displayInfoForRotation(ROTATION_180, true /* withCutout */);
+        final Pair<DisplayInfo, WmDisplayCutout> di =
+                displayInfoForRotation(ROTATION_180, true /* withCutout */);
 
         verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT + DISPLAY_CUTOUT_HEIGHT);
         verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT + DISPLAY_CUTOUT_HEIGHT);
         verifyConsistency(di);
     }
 
-    private void verifyStableInsets(DisplayInfo di, int left, int top, int right, int bottom) {
-        mErrorCollector.checkThat("stableInsets", getStableInsetsLw(di), equalTo(new Rect(
+    private void verifyStableInsets(Pair<DisplayInfo, WmDisplayCutout> diPair, int left, int top,
+            int right, int bottom) {
+        mErrorCollector.checkThat("stableInsets", getStableInsetsLw(diPair.first, diPair.second),
+                equalTo(new Rect(left, top, right, bottom)));
+    }
+
+    private void verifyNonDecorInsets(Pair<DisplayInfo, WmDisplayCutout> diPair, int left, int top,
+            int right, int bottom) {
+        mErrorCollector.checkThat("nonDecorInsets",
+                getNonDecorInsetsLw(diPair.first, diPair.second), equalTo(new Rect(
                 left, top, right, bottom)));
     }
 
-    private void verifyNonDecorInsets(DisplayInfo di, int left, int top, int right, int bottom) {
-        mErrorCollector.checkThat("nonDecorInsets", getNonDecorInsetsLw(di), equalTo(new Rect(
-                left, top, right, bottom)));
-    }
-
-    private void verifyConsistency(DisplayInfo di) {
-        verifyConsistency("configDisplay", di, getStableInsetsLw(di),
-                getConfigDisplayWidth(di), getConfigDisplayHeight(di));
-        verifyConsistency("nonDecorDisplay", di, getNonDecorInsetsLw(di),
-                getNonDecorDisplayWidth(di), getNonDecorDisplayHeight(di));
+    private void verifyConsistency(Pair<DisplayInfo, WmDisplayCutout> diPair) {
+        final DisplayInfo di = diPair.first;
+        final WmDisplayCutout cutout = diPair.second;
+        verifyConsistency("configDisplay", di, getStableInsetsLw(di, cutout),
+                getConfigDisplayWidth(di, cutout), getConfigDisplayHeight(di, cutout));
+        verifyConsistency("nonDecorDisplay", di, getNonDecorInsetsLw(di, cutout),
+                getNonDecorDisplayWidth(di, cutout), getNonDecorDisplayHeight(di, cutout));
     }
 
     private void verifyConsistency(String what, DisplayInfo di, Rect insets, int width,
@@ -165,39 +181,42 @@
                 equalTo(di.logicalHeight - insets.top - insets.bottom));
     }
 
-    private Rect getStableInsetsLw(DisplayInfo di) {
+    private Rect getStableInsetsLw(DisplayInfo di, WmDisplayCutout cutout) {
         Rect result = new Rect();
-        mDisplayPolicy.getStableInsetsLw(di.rotation, di.displayCutout, result);
+        mDisplayPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+                cutout, result);
         return result;
     }
 
-    private Rect getNonDecorInsetsLw(DisplayInfo di) {
+    private Rect getNonDecorInsetsLw(DisplayInfo di, WmDisplayCutout cutout) {
         Rect result = new Rect();
-        mDisplayPolicy.getNonDecorInsetsLw(di.rotation, di.displayCutout, result);
+        mDisplayPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+                cutout, result);
         return result;
     }
 
-    private int getNonDecorDisplayWidth(DisplayInfo di) {
-        return mDisplayPolicy.getNonDecorDisplayWidth(di.logicalWidth, di.logicalHeight,
-                di.rotation, 0 /* ui */, di.displayCutout);
+    private int getNonDecorDisplayWidth(DisplayInfo di, WmDisplayCutout cutout) {
+        return mDisplayPolicy.getNonDecorDisplayFrame(di.logicalWidth, di.logicalHeight,
+                di.rotation, cutout).width();
     }
 
-    private int getNonDecorDisplayHeight(DisplayInfo di) {
-        return mDisplayPolicy.getNonDecorDisplayHeight(di.logicalHeight, di.rotation,
-                di.displayCutout);
+    private int getNonDecorDisplayHeight(DisplayInfo di, WmDisplayCutout cutout) {
+        return mDisplayPolicy.getNonDecorDisplayFrame(di.logicalWidth, di.logicalHeight,
+                di.rotation, cutout).height();
     }
 
-    private int getConfigDisplayWidth(DisplayInfo di) {
-        return mDisplayPolicy.getConfigDisplayWidth(di.logicalWidth, di.logicalHeight,
-                di.rotation, 0 /* ui */, di.displayCutout);
+    private int getConfigDisplayWidth(DisplayInfo di, WmDisplayCutout cutout) {
+        return mDisplayPolicy.getConfigDisplaySize(di.logicalWidth, di.logicalHeight,
+                di.rotation, cutout).x;
     }
 
-    private int getConfigDisplayHeight(DisplayInfo di) {
-        return mDisplayPolicy.getConfigDisplayHeight(di.logicalWidth, di.logicalHeight,
-                di.rotation, 0 /* ui */, di.displayCutout);
+    private int getConfigDisplayHeight(DisplayInfo di, WmDisplayCutout cutout) {
+        return mDisplayPolicy.getConfigDisplaySize(di.logicalWidth, di.logicalHeight,
+                di.rotation, cutout).y;
     }
 
-    private static DisplayInfo displayInfoForRotation(int rotation, boolean withDisplayCutout) {
-        return displayInfoAndCutoutForRotation(rotation, withDisplayCutout, false).first;
+    private static Pair<DisplayInfo, WmDisplayCutout> displayInfoForRotation(int rotation,
+            boolean withDisplayCutout) {
+        return displayInfoAndCutoutForRotation(rotation, withDisplayCutout, false);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index e502f2f..d400a4c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -56,6 +56,7 @@
     private boolean mHasWallpaperBackground = false;
     private int mBlurRadius = 0;
     private float mDarkScrimAlpha = 0.5f;
+    private SurfaceControl mParentSurface = mock(SurfaceControl.class);
 
     @Before
     public void setUp() throws Exception {
@@ -63,7 +64,8 @@
         mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
                 () -> mAreCornersRounded, () -> Color.valueOf(mColor),
                 () -> mHasWallpaperBackground, () -> mBlurRadius, () -> mDarkScrimAlpha,
-                /* doubleTapCallbackX= */ x -> {}, /* doubleTapCallbackY= */ y -> {});
+                /* doubleTapCallbackX= */ x -> {}, /* doubleTapCallbackY= */ y -> {},
+                () -> mParentSurface);
         mTransaction = spy(StubTransaction.class);
     }
 
@@ -205,6 +207,22 @@
     }
 
     @Test
+    public void testNeedsApplySurfaceChanges_setParentSurface() {
+        mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+        mLetterbox.applySurfaceChanges(mTransaction);
+
+        verify(mTransaction).reparent(mSurfaces.top, mParentSurface);
+        assertFalse(mLetterbox.needsApplySurfaceChanges());
+
+        mParentSurface = mock(SurfaceControl.class);
+
+        assertTrue(mLetterbox.needsApplySurfaceChanges());
+
+        mLetterbox.applySurfaceChanges(mTransaction);
+        verify(mTransaction).reparent(mSurfaces.top, mParentSurface);
+    }
+
+    @Test
     public void testApplySurfaceChanges_cornersNotRounded_surfaceFullWindowSurfaceNotCreated() {
         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
         mLetterbox.applySurfaceChanges(mTransaction);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 88e58ea..8546763 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -281,7 +281,7 @@
         verify(mMockRunner).onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */);
 
         // Simulate the app transition finishing
-        mController.mAppTransitionListener.onAppTransitionStartingLocked(0, 0);
+        mController.mAppTransitionListener.onAppTransitionStartingLocked(false, false, 0, 0, 0);
         verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
index 420ea8e..d3aa073 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
@@ -16,13 +16,18 @@
 
 package com.android.server.wm;
 
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.BLASTSyncEngine.METHOD_BLAST;
+import static com.android.server.wm.BLASTSyncEngine.METHOD_NONE;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 import static com.android.server.wm.WindowContainer.SYNC_STATE_NONE;
+import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -67,7 +72,7 @@
         BLASTSyncEngine.TransactionReadyListener listener = mock(
                 BLASTSyncEngine.TransactionReadyListener.class);
 
-        int id = bse.startSyncSet(listener);
+        int id = startSyncSet(bse, listener);
         bse.addToSyncSet(id, mockWC);
         // Make sure a traversal is requested
         verify(mWm.mWindowPlacerLocked, times(1)).requestTraversal();
@@ -95,7 +100,7 @@
         BLASTSyncEngine.TransactionReadyListener listener = mock(
                 BLASTSyncEngine.TransactionReadyListener.class);
 
-        int id = bse.startSyncSet(listener);
+        int id = startSyncSet(bse, listener);
         bse.addToSyncSet(id, mockWC);
         bse.setReady(id);
         // Make sure traversals requested (one for add and another for setReady)
@@ -119,7 +124,7 @@
         BLASTSyncEngine.TransactionReadyListener listener = mock(
                 BLASTSyncEngine.TransactionReadyListener.class);
 
-        int id = bse.startSyncSet(listener);
+        int id = startSyncSet(bse, listener);
         bse.addToSyncSet(id, mockWC);
         bse.setReady(id);
         // Make sure traversals requested (one for add and another for setReady)
@@ -147,7 +152,7 @@
         BLASTSyncEngine.TransactionReadyListener listener = mock(
                 BLASTSyncEngine.TransactionReadyListener.class);
 
-        int id = bse.startSyncSet(listener);
+        int id = startSyncSet(bse, listener);
         bse.addToSyncSet(id, parentWC);
         bse.setReady(id);
         bse.onSurfacePlacement();
@@ -180,7 +185,7 @@
         BLASTSyncEngine.TransactionReadyListener listener = mock(
                 BLASTSyncEngine.TransactionReadyListener.class);
 
-        int id = bse.startSyncSet(listener);
+        int id = startSyncSet(bse, listener);
         bse.addToSyncSet(id, parentWC);
         bse.setReady(id);
         bse.onSurfacePlacement();
@@ -211,7 +216,7 @@
         BLASTSyncEngine.TransactionReadyListener listener = mock(
                 BLASTSyncEngine.TransactionReadyListener.class);
 
-        int id = bse.startSyncSet(listener);
+        int id = startSyncSet(bse, listener);
         bse.addToSyncSet(id, parentWC);
         bse.setReady(id);
         bse.onSurfacePlacement();
@@ -243,7 +248,7 @@
         BLASTSyncEngine.TransactionReadyListener listener = mock(
                 BLASTSyncEngine.TransactionReadyListener.class);
 
-        int id = bse.startSyncSet(listener);
+        int id = startSyncSet(bse, listener);
         bse.addToSyncSet(id, parentWC);
         bse.setReady(id);
         bse.onSurfacePlacement();
@@ -278,7 +283,7 @@
         BLASTSyncEngine.TransactionReadyListener listener = mock(
                 BLASTSyncEngine.TransactionReadyListener.class);
 
-        int id = bse.startSyncSet(listener);
+        int id = startSyncSet(bse, listener);
         bse.addToSyncSet(id, parentWC);
         bse.setReady(id);
         bse.onSurfacePlacement();
@@ -317,7 +322,7 @@
         BLASTSyncEngine.TransactionReadyListener listener = mock(
                 BLASTSyncEngine.TransactionReadyListener.class);
 
-        int id = bse.startSyncSet(listener);
+        int id = startSyncSet(bse, listener);
         bse.addToSyncSet(id, parentWC);
         final BLASTSyncEngine.SyncGroup syncGroup = parentWC.mSyncGroup;
         bse.setReady(id);
@@ -350,6 +355,33 @@
         assertEquals(SYNC_STATE_NONE, botChildWC.mSyncState);
     }
 
+    @Test
+    public void testNonBlastMethod() {
+        mAppWindow = createWindow(null, TYPE_BASE_APPLICATION, "mAppWindow");
+
+        final BLASTSyncEngine bse = createTestBLASTSyncEngine();
+
+        BLASTSyncEngine.TransactionReadyListener listener = mock(
+                BLASTSyncEngine.TransactionReadyListener.class);
+
+        int id = startSyncSet(bse, listener, METHOD_NONE);
+        bse.addToSyncSet(id, mAppWindow.mToken);
+        mAppWindow.prepareSync();
+        assertFalse(mAppWindow.shouldSyncWithBuffers());
+
+        mAppWindow.removeImmediately();
+    }
+
+    static int startSyncSet(BLASTSyncEngine engine,
+            BLASTSyncEngine.TransactionReadyListener listener) {
+        return startSyncSet(engine, listener, METHOD_BLAST);
+    }
+
+    static int startSyncSet(BLASTSyncEngine engine,
+            BLASTSyncEngine.TransactionReadyListener listener, int method) {
+        return engine.startSyncSet(listener, BLAST_TIMEOUT_DURATION, "", method);
+    }
+
     static class TestWindowContainer extends WindowContainer {
         final boolean mWaiter;
         boolean mVisibleRequested = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index da72030..9274eb3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -25,6 +25,7 @@
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
@@ -46,7 +47,6 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -65,6 +65,7 @@
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentOrganizer;
 import android.window.TaskFragmentOrganizerToken;
+import android.window.TaskFragmentTransaction;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 import android.window.WindowContainerTransactionCallback;
@@ -90,6 +91,7 @@
 
     private TaskFragmentOrganizerController mController;
     private WindowOrganizerController mWindowOrganizerController;
+    private TransitionController mTransitionController;
     private TaskFragmentOrganizer mOrganizer;
     private TaskFragmentOrganizerToken mOrganizerToken;
     private ITaskFragmentOrganizer mIOrganizer;
@@ -107,9 +109,10 @@
     private Task mTask;
 
     @Before
-    public void setup() {
+    public void setup() throws RemoteException {
         MockitoAnnotations.initMocks(this);
         mWindowOrganizerController = mAtm.mWindowOrganizerController;
+        mTransitionController = mWindowOrganizerController.mTransitionController;
         mController = mWindowOrganizerController.mTaskFragmentOrganizerController;
         mOrganizer = new TaskFragmentOrganizer(Runnable::run);
         mOrganizerToken = mOrganizer.getOrganizerToken();
@@ -128,11 +131,16 @@
         spyOn(mController);
         spyOn(mOrganizer);
         spyOn(mTaskFragment);
+        spyOn(mWindowOrganizerController);
+        spyOn(mTransitionController);
         doReturn(mIOrganizer).when(mTaskFragment).getTaskFragmentOrganizer();
         doReturn(mTaskFragmentInfo).when(mTaskFragment).getTaskFragmentInfo();
         doReturn(new SurfaceControl()).when(mTaskFragment).getSurfaceControl();
         doReturn(mFragmentToken).when(mTaskFragment).getFragmentToken();
         doReturn(new Configuration()).when(mTaskFragmentInfo).getConfiguration();
+
+        // To prevent it from calling the real server.
+        doNothing().when(mOrganizer).onTransactionHandled(any(), any());
     }
 
     @Test
@@ -866,7 +874,7 @@
         assertFalse(parentTask.shouldBeVisible(null));
 
         // Verify the info changed callback still occurred despite the task being invisible
-        reset(mOrganizer);
+        clearInvocations(mOrganizer);
         mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
         mController.dispatchPendingEvents();
         verify(mOrganizer).onTaskFragmentInfoChanged(any(), any());
@@ -899,7 +907,7 @@
         verify(mOrganizer).onTaskFragmentInfoChanged(any(), any());
 
         // Verify the info changed callback is not called when the task is invisible
-        reset(mOrganizer);
+        clearInvocations(mOrganizer);
         doReturn(false).when(task).shouldBeVisible(any());
         mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
         mController.dispatchPendingEvents();
@@ -1092,6 +1100,40 @@
                 .that(mTaskFragment.getBounds()).isEqualTo(task.getBounds());
     }
 
+    @Test
+    public void testOnTransactionReady_invokeOnTransactionHandled() {
+        mController.registerOrganizer(mIOrganizer);
+        final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
+        mOrganizer.onTransactionReady(transaction);
+
+        // Organizer should always trigger #onTransactionHandled when receives #onTransactionReady
+        verify(mOrganizer).onTransactionHandled(eq(transaction.getTransactionToken()), any());
+        verify(mOrganizer, never()).applyTransaction(any());
+    }
+
+    @Test
+    public void testDispatchTransaction_deferTransitionReady() {
+        mController.registerOrganizer(mIOrganizer);
+        setupMockParent(mTaskFragment, mTask);
+        final ArgumentCaptor<IBinder> tokenCaptor = ArgumentCaptor.forClass(IBinder.class);
+        final ArgumentCaptor<WindowContainerTransaction> wctCaptor =
+                ArgumentCaptor.forClass(WindowContainerTransaction.class);
+        doReturn(true).when(mTransitionController).isCollecting();
+
+        mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
+        mController.dispatchPendingEvents();
+
+        // Defer transition when send TaskFragment transaction during transition collection.
+        verify(mTransitionController).deferTransitionReady();
+        verify(mOrganizer).onTransactionHandled(tokenCaptor.capture(), wctCaptor.capture());
+
+        mController.onTransactionHandled(mIOrganizer, tokenCaptor.getValue(), wctCaptor.getValue());
+
+        // Apply the organizer change and continue transition.
+        verify(mWindowOrganizerController).applyTransaction(wctCaptor.getValue());
+        verify(mTransitionController).continueTransitionReady();
+    }
+
     /**
      * Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls
      * {@link WindowOrganizerController#applyTransaction} to apply the transaction,
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 1e64e46..f5304d0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -18,6 +18,13 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+import static android.view.InsetsState.ITYPE_BOTTOM_DISPLAY_CUTOUT;
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
+import static android.view.InsetsState.ITYPE_LEFT_DISPLAY_CUTOUT;
+import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT;
+import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -26,12 +33,9 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doReturn;
-
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -43,6 +47,7 @@
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
+import android.view.WindowInsets;
 
 import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
 
@@ -204,7 +209,26 @@
                 doReturn(true).when(newDisplay).supportsSystemDecorations();
                 doReturn(true).when(displayPolicy).hasNavigationBar();
                 doReturn(NAV_BAR_BOTTOM).when(displayPolicy).navigationBarPosition(anyInt());
-                doReturn(20).when(displayPolicy).getNavigationBarHeight(anyInt());
+                doReturn(Insets.of(0, 0, 0, 20)).when(displayPolicy).getInsets(any(),
+                        eq(WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars()));
+                doReturn(Insets.of(0, 20, 0, 20)).when(displayPolicy).getInsets(any(),
+                        eq(WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars()
+                                | WindowInsets.Type.statusBars()));
+                final int[] nonDecorTypes = new int[]{
+                        ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT,
+                        ITYPE_BOTTOM_DISPLAY_CUTOUT, ITYPE_LEFT_DISPLAY_CUTOUT, ITYPE_NAVIGATION_BAR
+                };
+                doReturn(Insets.of(0, 0, 0, 20)).when(displayPolicy).getInsetsWithInternalTypes(
+                        any(),
+                        eq(nonDecorTypes));
+                final int[] stableTypes = new int[]{
+                        ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT,
+                        ITYPE_BOTTOM_DISPLAY_CUTOUT, ITYPE_LEFT_DISPLAY_CUTOUT,
+                        ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR, ITYPE_CLIMATE_BAR
+                };
+                doReturn(Insets.of(0, 20, 0, 20)).when(displayPolicy).getInsetsWithInternalTypes(
+                        any(),
+                        eq(stableTypes));
             } else {
                 doReturn(false).when(displayPolicy).hasNavigationBar();
                 doReturn(false).when(displayPolicy).hasStatusBar();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 13da154..d2cb7ba 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -310,11 +310,11 @@
     }
 
     @Override
-    public void startKeyguardExitAnimation(long startTime) {
+    public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
     }
 
     @Override
-    public int applyKeyguardOcclusionChange(boolean notify) {
+    public int applyKeyguardOcclusionChange(boolean keyguardOccludingStarted) {
         return 0;
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 85ac7bd..55ca5fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -57,6 +57,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
@@ -73,6 +74,7 @@
 import android.window.TaskFragmentOrganizer;
 import android.window.TransitionInfo;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
@@ -1117,6 +1119,36 @@
         assertTrue(targets.contains(activity));
     }
 
+    @Test
+    public void testTransitionVisibleChange() {
+        registerTestTransitionPlayer();
+        final ActivityRecord app = createActivityRecord(mDisplayContent);
+        final Transition transition = new Transition(TRANSIT_OPEN, 0 /* flags */,
+                app.mTransitionController, mWm.mSyncEngine);
+        app.mTransitionController.moveToCollecting(transition, BLASTSyncEngine.METHOD_NONE);
+        final ArrayList<WindowContainer> freezeCalls = new ArrayList<>();
+        transition.setContainerFreezer(new Transition.IContainerFreezer() {
+            @Override
+            public boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds) {
+                freezeCalls.add(wc);
+                return true;
+            }
+
+            @Override
+            public void cleanUp(SurfaceControl.Transaction t) {
+            }
+        });
+        final Task task = app.getTask();
+        transition.collect(task);
+        final Rect bounds = new Rect(task.getBounds());
+        Configuration c = new Configuration(task.getRequestedOverrideConfiguration());
+        bounds.inset(10, 10);
+        c.windowConfiguration.setBounds(bounds);
+        task.onRequestedOverrideConfigurationChanged(c);
+        assertTrue(freezeCalls.contains(task));
+        transition.abort();
+    }
+
     private static void makeTaskOrganized(Task... tasks) {
         final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
         for (Task t : tasks) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index a62625c..3b64c51 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -42,8 +42,10 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.BLASTSyncEngine.METHOD_BLAST;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 import static com.android.server.wm.WindowContainer.SYNC_STATE_READY;
+import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -997,7 +999,7 @@
         BLASTSyncEngine.TransactionReadyListener transactionListener =
                 mock(BLASTSyncEngine.TransactionReadyListener.class);
 
-        int id = bse.startSyncSet(transactionListener);
+        int id = bse.startSyncSet(transactionListener, BLAST_TIMEOUT_DURATION, "", METHOD_BLAST);
         bse.addToSyncSet(id, task);
         bse.setReady(id);
         bse.onSurfacePlacement();
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 564d3ca..6785979 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -331,6 +331,10 @@
             mNavBarWindow.mAttrs.gravity = Gravity.BOTTOM;
             mNavBarWindow.mAttrs.paramsForRotation = new WindowManager.LayoutParams[4];
             mNavBarWindow.mAttrs.setFitInsetsTypes(0);
+            mNavBarWindow.mAttrs.layoutInDisplayCutoutMode =
+                    LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+            mNavBarWindow.mAttrs.privateFlags |=
+                    WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
             for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
                 mNavBarWindow.mAttrs.paramsForRotation[rot] =
                         getNavBarLayoutParamsForRotation(rot);
@@ -381,6 +385,9 @@
         lp.height = height;
         lp.gravity = gravity;
         lp.setFitInsetsTypes(0);
+        lp.privateFlags |=
+                WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
+        lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         return lp;
     }
 
diff --git a/tests/FlickerTests/libs/window-extensions-release.aar b/tests/FlickerTests/libs/window-extensions-release.aar
index 6fc9a67..918e514 100644
--- a/tests/FlickerTests/libs/window-extensions-release.aar
+++ b/tests/FlickerTests/libs/window-extensions-release.aar
Binary files differ
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index be7fb73..8df3548 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -21,7 +21,6 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.traces.common.ComponentMatcher
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import org.junit.Assume
 import org.junit.Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 472a0fa..eddb553 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -19,7 +19,7 @@
 
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.traces.region.RegionSubject
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.common.IComponentMatcher
 
 /**
@@ -28,7 +28,7 @@
  */
 fun FlickerTestParameter.statusBarWindowIsAlwaysVisible() {
     assertWm {
-        this.isAboveAppWindowVisible(ComponentMatcher.STATUS_BAR)
+        this.isAboveAppWindowVisible(ComponentNameMatcher.STATUS_BAR)
     }
 }
 
@@ -38,7 +38,7 @@
  */
 fun FlickerTestParameter.navBarWindowIsAlwaysVisible() {
     assertWm {
-        this.isAboveAppWindowVisible(ComponentMatcher.NAV_BAR)
+        this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR)
     }
 }
 
@@ -48,10 +48,10 @@
  */
 fun FlickerTestParameter.navBarWindowIsVisibleAtStartAndEnd() {
     assertWmStart {
-        this.isAboveAppWindowVisible(ComponentMatcher.NAV_BAR)
+        this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR)
     }
     assertWmEnd {
-        this.isAboveAppWindowVisible(ComponentMatcher.NAV_BAR)
+        this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR)
     }
 }
 
@@ -61,7 +61,7 @@
  */
 fun FlickerTestParameter.taskBarWindowIsAlwaysVisible() {
     assertWm {
-        this.isAboveAppWindowVisible(ComponentMatcher.TASK_BAR)
+        this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR)
     }
 }
 
@@ -71,7 +71,7 @@
  */
 fun FlickerTestParameter.taskBarWindowIsVisibleAtEnd() {
     assertWmEnd {
-        this.isAboveAppWindowVisible(ComponentMatcher.TASK_BAR)
+        this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR)
     }
 }
 
@@ -114,10 +114,10 @@
  */
 fun FlickerTestParameter.navBarLayerIsVisibleAtStartAndEnd() {
     assertLayersStart {
-        this.isVisible(ComponentMatcher.NAV_BAR)
+        this.isVisible(ComponentNameMatcher.NAV_BAR)
     }
     assertLayersEnd {
-        this.isVisible(ComponentMatcher.NAV_BAR)
+        this.isVisible(ComponentNameMatcher.NAV_BAR)
     }
 }
 
@@ -136,7 +136,7 @@
  */
 fun FlickerTestParameter.taskBarLayerIsVisibleAtStart() {
     assertLayersStart {
-        this.isVisible(ComponentMatcher.TASK_BAR)
+        this.isVisible(ComponentNameMatcher.TASK_BAR)
     }
 }
 
@@ -146,7 +146,7 @@
  */
 fun FlickerTestParameter.taskBarLayerIsVisibleAtEnd() {
     assertLayersEnd {
-        this.isVisible(ComponentMatcher.TASK_BAR)
+        this.isVisible(ComponentNameMatcher.TASK_BAR)
     }
 }
 
@@ -156,10 +156,10 @@
  */
 fun FlickerTestParameter.statusBarLayerIsVisibleAtStartAndEnd() {
     assertLayersStart {
-        this.isVisible(ComponentMatcher.STATUS_BAR)
+        this.isVisible(ComponentNameMatcher.STATUS_BAR)
     }
     assertLayersEnd {
-        this.isVisible(ComponentMatcher.STATUS_BAR)
+        this.isVisible(ComponentNameMatcher.STATUS_BAR)
     }
 }
 
@@ -171,7 +171,7 @@
     assertLayersStart {
         val display = this.entry.displays.firstOrNull { !it.isVirtual }
                 ?: error("There is no display!")
-        this.visibleRegion(ComponentMatcher.NAV_BAR)
+        this.visibleRegion(ComponentNameMatcher.NAV_BAR)
             .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation))
     }
 }
@@ -184,7 +184,7 @@
     assertLayersEnd {
         val display = this.entry.displays.minByOrNull { it.id }
             ?: throw RuntimeException("There is no display!")
-        this.visibleRegion(ComponentMatcher.NAV_BAR)
+        this.visibleRegion(ComponentNameMatcher.NAV_BAR)
             .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation))
     }
 }
@@ -206,7 +206,7 @@
     assertLayersStart {
         val display = this.entry.displays.minByOrNull { it.id }
             ?: throw RuntimeException("There is no display!")
-        this.visibleRegion(ComponentMatcher.STATUS_BAR)
+        this.visibleRegion(ComponentNameMatcher.STATUS_BAR)
             .coversExactly(WindowUtils.getStatusBarPosition(display))
     }
 }
@@ -219,7 +219,7 @@
     assertLayersEnd {
         val display = this.entry.displays.minByOrNull { it.id }
             ?: throw RuntimeException("There is no display!")
-        this.visibleRegion(ComponentMatcher.STATUS_BAR)
+        this.visibleRegion(ComponentNameMatcher.STATUS_BAR)
             .coversExactly(WindowUtils.getStatusBarPosition(display))
     }
 }
@@ -244,7 +244,7 @@
         invoke("snapshotStartingWindowLayerCoversExactlyOnApp") {
             val snapshotLayers = it.subjects.filter { subject ->
                 subject.name.contains(
-                    ComponentMatcher.SNAPSHOT.toLayerName()) && subject.isVisible
+                    ComponentNameMatcher.SNAPSHOT.toLayerName()) && subject.isVisible
             }
             // Verify the size of snapshotRegion covers appVisibleRegion exactly in animation.
             if (snapshotLayers.isNotEmpty()) {
@@ -291,10 +291,10 @@
         val assertion = this.isVisible(originalLayer)
 
         if (ignoreEntriesWithRotationLayer) {
-            assertion.then().isVisible(ComponentMatcher.ROTATION, isOptional = true)
+            assertion.then().isVisible(ComponentNameMatcher.ROTATION, isOptional = true)
         }
         if (ignoreSnapshot) {
-            assertion.then().isVisible(ComponentMatcher.SNAPSHOT, isOptional = true)
+            assertion.then().isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
         }
         if (ignoreSplashscreen) {
             assertion.then().isSplashScreenVisibleFor(newLayer, isOptional = true)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
index 7ff0934..cb197cd 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
@@ -24,7 +24,7 @@
 import com.android.server.wm.flicker.helpers.StandardAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.replacesLayer
-import com.android.server.wm.traces.common.ComponentMatcher.Companion.LAUNCHER
+import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.LAUNCHER
 import org.junit.Test
 
 /**
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index af3a8c5..b8fe9f9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -26,7 +26,7 @@
 import androidx.window.extensions.WindowExtensionsProvider
 import androidx.window.extensions.embedding.ActivityEmbeddingComponent
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.common.windowmanager.WindowManagerState.Companion.STATE_RESUMED
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
@@ -35,7 +35,7 @@
 class ActivityEmbeddingAppHelper @JvmOverloads constructor(
         instr: Instrumentation,
         launcherName: String = ActivityOptions.ACTIVITY_EMBEDDING_LAUNCHER_NAME,
-        component: IComponentMatcher = MAIN_ACTIVITY_COMPONENT,
+        component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT,
         launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
                 .getInstance(instr)
                 .launcherStrategy
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt
index bd0ae4d..34f9ce4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt
@@ -21,12 +21,11 @@
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
 import android.provider.MediaStore
-import com.android.server.wm.traces.common.ComponentMatcher
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 
 class CameraAppHelper @JvmOverloads constructor(
-        instrumentation: Instrumentation,
-        private val pkgManager: PackageManager = instrumentation.context.packageManager
+    instrumentation: Instrumentation,
+    pkgManager: PackageManager = instrumentation.context.packageManager
 ) : StandardAppHelper(instrumentation, getCameraLauncherName(pkgManager),
         getCameraComponent(pkgManager)){
     companion object{
@@ -40,9 +39,9 @@
                     ?: error("unable to resolve camera activity")
         }
 
-        private fun getCameraComponent(pkgManager: PackageManager): IComponentMatcher {
+        private fun getCameraComponent(pkgManager: PackageManager): ComponentNameMatcher {
             val resolveInfo = getResolveInfo(pkgManager)
-            return ComponentMatcher(resolveInfo.activityInfo.packageName,
+            return ComponentNameMatcher(resolveInfo.activityInfo.packageName,
                     className = resolveInfo.activityInfo.name)
         }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
index 28858d4..b696fc3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
@@ -20,13 +20,13 @@
 import android.support.test.launcherhelper.ILauncherStrategy
 import android.support.test.launcherhelper.LauncherStrategyFactory
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 
 class FixedOrientationAppHelper @JvmOverloads constructor(
      instr: Instrumentation,
      launcherName: String = ActivityOptions.PORTRAIT_ONLY_ACTIVITY_LAUNCHER_NAME,
-     component: IComponentMatcher =
+     component: ComponentNameMatcher =
              ActivityOptions.PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
      launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
              .getInstance(instr)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
index f536a15..e01cceb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
@@ -23,7 +23,7 @@
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import java.util.regex.Pattern
@@ -33,7 +33,7 @@
     private val rotation: Int,
     private val imePackageName: String = IME_PACKAGE,
     launcherName: String = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_LAUNCHER_NAME,
-    component: IComponentMatcher =
+    component: ComponentNameMatcher =
         ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
 ) : ImeAppHelper(instr, launcherName, component) {
     override fun openIME(wmHelper: WindowManagerStateHelper) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
index db64c47..b672b1b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
@@ -22,14 +22,14 @@
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 
 open class ImeAppHelper @JvmOverloads constructor(
     instr: Instrumentation,
     launcherName: String = ActivityOptions.IME_ACTIVITY_LAUNCHER_NAME,
-    component: IComponentMatcher =
+    component: ComponentNameMatcher =
         ActivityOptions.IME_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
     launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
             .getInstance(instr)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt
index f74054e..df47e9d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt
@@ -20,16 +20,14 @@
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 
 class ImeEditorPopupDialogAppHelper @JvmOverloads constructor(
     instr: Instrumentation,
-    private val rotation: Int,
-    private val imePackageName: String = IME_PACKAGE,
     launcherName: String = ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_LAUNCHER_NAME,
-    component: IComponentMatcher =
+    component: ComponentNameMatcher =
             ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
 ) : ImeAppHelper(instr, launcherName, component) {
     override fun openIME(wmHelper: WindowManagerStateHelper) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
index 7f8b563..d3945c1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
@@ -20,13 +20,13 @@
 import android.support.test.launcherhelper.ILauncherStrategy
 import android.support.test.launcherhelper.LauncherStrategyFactory
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 
 class ImeStateInitializeHelper @JvmOverloads constructor(
     instr: Instrumentation,
     launcherName: String = ActivityOptions.IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME,
-    component: IComponentMatcher =
+    component: ComponentNameMatcher =
         ActivityOptions.IME_ACTIVITY_INITIALIZE_COMPONENT_NAME.toFlickerComponent(),
     launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
             .getInstance(instr)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
index 149576e..9fb574c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
@@ -23,14 +23,14 @@
 import androidx.test.uiautomator.UiDevice
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 
 class NewTasksAppHelper @JvmOverloads constructor(
     instr: Instrumentation,
     launcherName: String = ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME,
-    component: IComponentMatcher =
+    component: ComponentNameMatcher =
         ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
     launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
         .getInstance(instr)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
index a9769d0..a1dbeea 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
@@ -20,13 +20,13 @@
 import android.support.test.launcherhelper.ILauncherStrategy
 import android.support.test.launcherhelper.LauncherStrategyFactory
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 
 class NonResizeableAppHelper @JvmOverloads constructor(
     instr: Instrumentation,
     launcherName: String = ActivityOptions.NON_RESIZEABLE_ACTIVITY_LAUNCHER_NAME,
-    component: IComponentMatcher =
+    component: ComponentNameMatcher =
         ActivityOptions.NON_RESIZEABLE_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
     launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
         .getInstance(instr)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
index 50d036c..b031a45 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
@@ -22,14 +22,14 @@
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 
 class NotificationAppHelper @JvmOverloads constructor(
     instr: Instrumentation,
     launcherName: String = ActivityOptions.NOTIFICATION_ACTIVITY_LAUNCHER_NAME,
-    component: IComponentMatcher =
+    component: ComponentNameMatcher =
             ActivityOptions.NOTIFICATION_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
     launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
             .getInstance(instr)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
index 459ca5b..6d466d7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
@@ -20,13 +20,13 @@
 import android.support.test.launcherhelper.ILauncherStrategy
 import android.support.test.launcherhelper.LauncherStrategyFactory
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 
 class SeamlessRotationAppHelper @JvmOverloads constructor(
     instr: Instrumentation,
     launcherName: String = ActivityOptions.SEAMLESS_ACTIVITY_LAUNCHER_NAME,
-    component: IComponentMatcher =
+    component: ComponentNameMatcher =
         ActivityOptions.SEAMLESS_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
     launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
         .getInstance(instr)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
index 4952dba..804ab38 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
@@ -20,13 +20,13 @@
 import android.support.test.launcherhelper.ILauncherStrategy
 import android.support.test.launcherhelper.LauncherStrategyFactory
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 
 class ShowWhenLockedAppHelper @JvmOverloads constructor(
     instr: Instrumentation,
     launcherName: String = ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME,
-    component: IComponentMatcher =
+    component: ComponentNameMatcher =
             ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
     launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
             .getInstance(instr)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
index 6bddcac..5da273a7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
@@ -20,13 +20,13 @@
 import android.support.test.launcherhelper.ILauncherStrategy
 import android.support.test.launcherhelper.LauncherStrategyFactory
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 
 class SimpleAppHelper @JvmOverloads constructor(
     instr: Instrumentation,
     launcherName: String = ActivityOptions.SIMPLE_ACTIVITY_LAUNCHER_NAME,
-    component: IComponentMatcher =
+    component: ComponentNameMatcher =
         ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent(),
     launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
         .getInstance(instr)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
index a17344f..060e9af 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
@@ -23,14 +23,14 @@
 import androidx.test.uiautomator.UiDevice
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 
 class TwoActivitiesAppHelper @JvmOverloads constructor(
     instr: Instrumentation,
     launcherName: String = ActivityOptions.BUTTON_ACTIVITY_LAUNCHER_NAME,
-    component: IComponentMatcher =
+    component: ComponentNameMatcher =
         ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
     launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
         .getInstance(instr)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index 6b8fde2..f6f3f58 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -27,7 +27,7 @@
 import com.android.server.wm.flicker.annotation.Group2
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -83,7 +83,7 @@
     @Test
     fun imeLayerVisibleStart() {
         testSpec.assertLayersStart {
-            this.isVisible(ComponentMatcher.IME)
+            this.isVisible(ComponentNameMatcher.IME)
         }
     }
 
@@ -91,7 +91,7 @@
     @Test
     fun imeLayerInvisibleEnd() {
         testSpec.assertLayersEnd {
-            this.isInvisible(ComponentMatcher.IME)
+            this.isInvisible(ComponentNameMatcher.IME)
         }
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index a92ecb9..52f561e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -27,7 +27,7 @@
 import com.android.server.wm.flicker.annotation.Group2
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -92,7 +92,7 @@
     @Test
     fun imeLayerVisibleStart() {
         testSpec.assertLayersStart {
-            this.isVisible(ComponentMatcher.IME)
+            this.isVisible(ComponentNameMatcher.IME)
         }
     }
 
@@ -100,7 +100,7 @@
     @Test
     fun imeLayerInvisibleEnd() {
         testSpec.assertLayersEnd {
-            this.isInvisible(ComponentMatcher.IME)
+            this.isInvisible(ComponentNameMatcher.IME)
         }
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
index 0e4d1dd..c6e25d3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
@@ -28,7 +28,7 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ImeEditorPopupDialogAppHelper
 import com.android.server.wm.flicker.traces.region.RegionSubject
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -41,7 +41,7 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group4
 class CloseImeEditorPopupDialogTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
-    private val imeTestApp = ImeEditorPopupDialogAppHelper(instrumentation, testSpec.startRotation)
+    private val imeTestApp = ImeEditorPopupDialogAppHelper(instrumentation)
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit = {
@@ -102,12 +102,12 @@
     @Test
     fun imeLayerAndImeSnapshotVisibleOnScreen() {
         testSpec.assertLayers {
-            this.isVisible(ComponentMatcher.IME)
+            this.isVisible(ComponentNameMatcher.IME)
                 .then()
-                .isVisible(ComponentMatcher.IME_SNAPSHOT)
+                .isVisible(ComponentNameMatcher.IME_SNAPSHOT)
                 .then()
-                .isInvisible(ComponentMatcher.IME_SNAPSHOT, isOptional = true)
-                .isInvisible(ComponentMatcher.IME)
+                .isInvisible(ComponentNameMatcher.IME_SNAPSHOT, isOptional = true)
+                .isInvisible(ComponentNameMatcher.IME)
         }
     }
 
@@ -118,7 +118,7 @@
             this.invoke("imeSnapshotAssociatedOnAppVisibleRegion") {
                 val imeSnapshotLayers = it.subjects.filter { subject ->
                     subject.name.contains(
-                        ComponentMatcher.IME_SNAPSHOT.toLayerName()
+                        ComponentNameMatcher.IME_SNAPSHOT.toLayerName()
                     ) && subject.isVisible
                 }
                 if (imeSnapshotLayers.isNotEmpty()) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index 452aa63..23bd220 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -27,7 +27,7 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -73,9 +73,9 @@
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
         testSpec.assertWm {
             this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(
-                ComponentMatcher.IME,
-                ComponentMatcher.SPLASH_SCREEN,
-                ComponentMatcher.SNAPSHOT))
+                ComponentNameMatcher.IME,
+                ComponentNameMatcher.SPLASH_SCREEN,
+                ComponentNameMatcher.SNAPSHOT))
         }
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index 856df26..8ce1840 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -27,7 +27,7 @@
 import com.android.server.wm.flicker.annotation.Group2
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ImeAppHelper
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -78,9 +78,9 @@
         testSpec.assertWm {
             this.visibleWindowsShownMoreThanOneConsecutiveEntry(
                 listOf(
-                    ComponentMatcher.IME,
-                    ComponentMatcher.SPLASH_SCREEN,
-                    ComponentMatcher.SNAPSHOT
+                    ComponentNameMatcher.IME,
+                    ComponentNameMatcher.SPLASH_SCREEN,
+                    ComponentNameMatcher.SNAPSHOT
                 )
             )
         }
@@ -93,8 +93,8 @@
         testSpec.assertLayers {
             this.visibleLayersShownMoreThanOneConsecutiveEntry(
                 listOf(
-                    ComponentMatcher.IME,
-                    ComponentMatcher.SPLASH_SCREEN
+                    ComponentNameMatcher.IME,
+                    ComponentNameMatcher.SPLASH_SCREEN
                 )
             )
         }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
index 19cab3c..9c99d96 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
@@ -18,52 +18,52 @@
 package com.android.server.wm.flicker.ime
 
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 
 fun FlickerTestParameter.imeLayerBecomesVisible() {
     assertLayers {
-        this.isInvisible(ComponentMatcher.IME)
+        this.isInvisible(ComponentNameMatcher.IME)
             .then()
-            .isVisible(ComponentMatcher.IME)
+            .isVisible(ComponentNameMatcher.IME)
     }
 }
 
 fun FlickerTestParameter.imeLayerBecomesInvisible() {
     assertLayers {
-        this.isVisible(ComponentMatcher.IME)
+        this.isVisible(ComponentNameMatcher.IME)
             .then()
-            .isInvisible(ComponentMatcher.IME)
+            .isInvisible(ComponentNameMatcher.IME)
     }
 }
 
 fun FlickerTestParameter.imeWindowIsAlwaysVisible(rotatesScreen: Boolean = false) {
     if (rotatesScreen) {
         assertWm {
-            this.isNonAppWindowVisible(ComponentMatcher.IME)
+            this.isNonAppWindowVisible(ComponentNameMatcher.IME)
                 .then()
-                .isNonAppWindowInvisible(ComponentMatcher.IME)
+                .isNonAppWindowInvisible(ComponentNameMatcher.IME)
                 .then()
-                .isNonAppWindowVisible(ComponentMatcher.IME)
+                .isNonAppWindowVisible(ComponentNameMatcher.IME)
         }
     } else {
         assertWm {
-            this.isNonAppWindowVisible(ComponentMatcher.IME)
+            this.isNonAppWindowVisible(ComponentNameMatcher.IME)
         }
     }
 }
 
 fun FlickerTestParameter.imeWindowBecomesVisible() {
     assertWm {
-        this.isNonAppWindowInvisible(ComponentMatcher.IME)
+        this.isNonAppWindowInvisible(ComponentNameMatcher.IME)
             .then()
-            .isNonAppWindowVisible(ComponentMatcher.IME)
+            .isNonAppWindowVisible(ComponentNameMatcher.IME)
     }
 }
 
 fun FlickerTestParameter.imeWindowBecomesInvisible() {
     assertWm {
-        this.isNonAppWindowVisible(ComponentMatcher.IME)
+        this.isNonAppWindowVisible(ComponentNameMatcher.IME)
             .then()
-            .isNonAppWindowInvisible(ComponentMatcher.IME)
+            .isNonAppWindowInvisible(ComponentNameMatcher.IME)
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
index 4569a5b..a04a50f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
@@ -30,7 +30,7 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.FixMethodOrder
@@ -101,7 +101,7 @@
     @Test
     fun imeLayerExistsEnd() {
         testSpec.assertLayersEnd {
-            this.isVisible(ComponentMatcher.IME)
+            this.isVisible(ComponentNameMatcher.IME)
         }
     }
 
@@ -112,7 +112,7 @@
     @Test
     fun imeSnapshotNotVisible() {
         testSpec.assertLayers {
-            this.isInvisible(ComponentMatcher.IME_SNAPSHOT)
+            this.isInvisible(ComponentNameMatcher.IME_SNAPSHOT)
         }
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
index 977719c..04e4bc9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
@@ -28,7 +28,7 @@
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
 import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper
 import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -114,7 +114,7 @@
     @Test
     fun imeLayerNotExistsStart() {
         testSpec.assertLayersStart {
-            this.isInvisible(ComponentMatcher.IME)
+            this.isInvisible(ComponentNameMatcher.IME)
         }
     }
 
@@ -125,7 +125,7 @@
     @Test
     fun imeLayerExistsEnd() {
         testSpec.assertLayersEnd {
-            this.isVisible(ComponentMatcher.IME)
+            this.isVisible(ComponentNameMatcher.IME)
         }
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
index 16cf22a..9475734 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
@@ -31,7 +31,7 @@
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
 import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.common.WindowManagerConditionsFactory
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import org.junit.Assume
@@ -138,10 +138,10 @@
         Assume.assumeTrue(testSpec.isGesturalNavigation)
         Assume.assumeTrue(isShellTransitionsEnabled)
         testSpec.assertLayersStart {
-            this.isVisible(ComponentMatcher.NAV_BAR)
+            this.isVisible(ComponentNameMatcher.NAV_BAR)
         }
         testSpec.assertLayersEnd {
-            this.isInvisible(ComponentMatcher.NAV_BAR)
+            this.isInvisible(ComponentNameMatcher.NAV_BAR)
         }
     }
 
@@ -156,10 +156,10 @@
         Assume.assumeTrue(testSpec.isGesturalNavigation)
         Assume.assumeFalse(testSpec.isTablet)
         testSpec.assertLayersStart {
-            this.isVisible(ComponentMatcher.STATUS_BAR)
+            this.isVisible(ComponentNameMatcher.STATUS_BAR)
         }
         testSpec.assertLayersEnd {
-            this.isInvisible(ComponentMatcher.STATUS_BAR)
+            this.isInvisible(ComponentNameMatcher.STATUS_BAR)
         }
     }
 
@@ -215,10 +215,10 @@
         Assume.assumeFalse(testSpec.isTablet)
         Assume.assumeTrue(isShellTransitionsEnabled)
         testSpec.assertLayersStart {
-            this.isVisible(ComponentMatcher.STATUS_BAR)
+            this.isVisible(ComponentNameMatcher.STATUS_BAR)
         }
         testSpec.assertLayersEnd {
-            this.isInvisible(ComponentMatcher.STATUS_BAR)
+            this.isInvisible(ComponentNameMatcher.STATUS_BAR)
         }
     }
 
@@ -235,7 +235,7 @@
     @Test
     fun imeLayerIsVisibleAndAssociatedWithAppWidow() {
         testSpec.assertLayersStart {
-            isVisible(ComponentMatcher.IME).visibleRegion(ComponentMatcher.IME)
+            isVisible(ComponentNameMatcher.IME).visibleRegion(ComponentNameMatcher.IME)
                 .coversAtMost(
                     isVisible(imeTestApp)
                         .visibleRegion(imeTestApp).region
@@ -243,10 +243,10 @@
         }
         testSpec.assertLayers {
             this.invoke("imeLayerIsVisibleAndAlignAppWidow") {
-                val imeVisibleRegion = it.visibleRegion(ComponentMatcher.IME)
+                val imeVisibleRegion = it.visibleRegion(ComponentNameMatcher.IME)
                 val appVisibleRegion = it.visibleRegion(imeTestApp)
                 if (imeVisibleRegion.region.isNotEmpty) {
-                    it.isVisible(ComponentMatcher.IME)
+                    it.isVisible(ComponentNameMatcher.IME)
                     imeVisibleRegion.coversAtMost(appVisibleRegion.region)
                 }
             }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index 2207fe5..2e22e62 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -29,7 +29,7 @@
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.reopenAppFromOverview
 import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -84,11 +84,11 @@
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
         // depends on how much of the animation transactions are sent to SF at once
         // sometimes this layer appears for 2-3 frames, sometimes for only 1
-        val recentTaskComponent = ComponentMatcher("", "RecentTaskScreenshotSurface")
+        val recentTaskComponent = ComponentNameMatcher("", "RecentTaskScreenshotSurface")
         testSpec.assertLayers {
             this.visibleLayersShownMoreThanOneConsecutiveEntry(
-                listOf(ComponentMatcher.SPLASH_SCREEN,
-                    ComponentMatcher.SNAPSHOT, recentTaskComponent)
+                listOf(ComponentNameMatcher.SPLASH_SCREEN,
+                    ComponentNameMatcher.SNAPSHOT, recentTaskComponent)
             )
         }
     }
@@ -97,11 +97,11 @@
     @Presubmit
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
-        val component = ComponentMatcher("", "RecentTaskScreenshotSurface")
+        val component = ComponentNameMatcher("", "RecentTaskScreenshotSurface")
         testSpec.assertWm {
             this.visibleWindowsShownMoreThanOneConsecutiveEntry(
-                    ignoreWindows = listOf(ComponentMatcher.SPLASH_SCREEN,
-                        ComponentMatcher.SNAPSHOT,
+                    ignoreWindows = listOf(ComponentNameMatcher.SPLASH_SCREEN,
+                        ComponentNameMatcher.SNAPSHOT,
                         component)
             )
         }
@@ -111,9 +111,9 @@
     @Test
     fun launcherWindowBecomesInvisible() {
         testSpec.assertWm {
-            this.isAppWindowVisible(ComponentMatcher.LAUNCHER)
+            this.isAppWindowVisible(ComponentNameMatcher.LAUNCHER)
                     .then()
-                    .isAppWindowInvisible(ComponentMatcher.LAUNCHER)
+                    .isAppWindowInvisible(ComponentNameMatcher.LAUNCHER)
         }
     }
 
@@ -157,11 +157,11 @@
     fun imeLayerIsBecomesVisibleLegacy() {
         Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.assertLayers {
-            this.isVisible(ComponentMatcher.IME)
+            this.isVisible(ComponentNameMatcher.IME)
                     .then()
-                    .isInvisible(ComponentMatcher.IME)
+                    .isInvisible(ComponentNameMatcher.IME)
                     .then()
-                    .isVisible(ComponentMatcher.IME)
+                    .isVisible(ComponentNameMatcher.IME)
         }
     }
 
@@ -170,7 +170,7 @@
     fun imeLayerBecomesVisibleShellTransit() {
         Assume.assumeTrue(isShellTransitionsEnabled)
         testSpec.assertLayers {
-            this.isVisible(ComponentMatcher.IME)
+            this.isVisible(ComponentNameMatcher.IME)
         }
     }
 
@@ -178,9 +178,9 @@
     @Test
     fun appLayerReplacesLauncher() {
         testSpec.assertLayers {
-            this.isVisible(ComponentMatcher.LAUNCHER)
+            this.isVisible(ComponentNameMatcher.LAUNCHER)
                 .then()
-                .isVisible(ComponentMatcher.SNAPSHOT, isOptional = true)
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
                 .then()
                 .isVisible(testApp)
         }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
index be7b80e..4f47ec4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
@@ -31,7 +31,7 @@
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -177,20 +177,20 @@
     @Test
     open fun imeLayerIsVisibleWhenSwitchingToImeApp() {
         testSpec.assertLayersStart {
-            isVisible(ComponentMatcher.IME)
+            isVisible(ComponentNameMatcher.IME)
         }
         testSpec.assertLayersTag(TAG_IME_VISIBLE) {
-            isVisible(ComponentMatcher.IME)
+            isVisible(ComponentNameMatcher.IME)
         }
         testSpec.assertLayersEnd {
-            isVisible(ComponentMatcher.IME)
+            isVisible(ComponentNameMatcher.IME)
         }
     }
 
     @Test
     fun imeLayerIsInvisibleWhenSwitchingToTestApp() {
         testSpec.assertLayersTag(TAG_IME_INVISIBLE) {
-            isInvisible(ComponentMatcher.IME)
+            isInvisible(ComponentNameMatcher.IME)
         }
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
index a8c0a0b..5ac1712 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
@@ -24,7 +24,6 @@
 import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
-import com.android.server.wm.traces.common.ComponentMatcher
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
index 86b8e5f..33c280e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
@@ -27,7 +27,7 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -118,7 +118,7 @@
     @Test
     fun launcherWindowNotOnTop() {
         testSpec.assertWm {
-            this.isAppWindowNotOnTop(ComponentMatcher.LAUNCHER)
+            this.isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER)
         }
     }
 
@@ -128,7 +128,7 @@
     @Presubmit
     @Test
     fun launcherLayerNotVisible() {
-        testSpec.assertLayers { this.isInvisible(ComponentMatcher.LAUNCHER) }
+        testSpec.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) }
     }
 
     companion object {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
index 2d4d798..bece406 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
@@ -19,7 +19,7 @@
 import android.platform.test.annotations.Presubmit
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.replacesLayer
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Test
 
 /**
@@ -46,7 +46,7 @@
      */
     open fun appLayerReplacesLauncher() {
         testSpec.replacesLayer(
-            ComponentMatcher.LAUNCHER, testApp,
+            ComponentNameMatcher.LAUNCHER, testApp,
             ignoreEntriesWithRotationLayer = true, ignoreSnapshot = true,
             ignoreSplashscreen = true
         )
@@ -61,12 +61,12 @@
     @Test
     open fun appWindowReplacesLauncherAsTopWindow() {
         testSpec.assertWm {
-            this.isAppWindowOnTop(ComponentMatcher.LAUNCHER)
+            this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
                 .then()
                 .isAppWindowOnTop(
                     testApp
-                        .or(ComponentMatcher.SNAPSHOT)
-                        .or(ComponentMatcher.SPLASH_SCREEN)
+                        .or(ComponentNameMatcher.SNAPSHOT)
+                        .or(ComponentNameMatcher.SPLASH_SCREEN)
                 )
         }
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
index 220e4ca..bfc7b39 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
@@ -26,7 +26,6 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.navBarLayerPositionAtEnd
 import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
-import com.android.server.wm.traces.common.ComponentMatcher
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Ignore
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
index 9ed1bde..e517c2a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
@@ -27,7 +27,7 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.navBarLayerPositionAtEnd
 import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Ignore
@@ -86,9 +86,9 @@
         testSpec.assertWm {
             this.hasNoVisibleAppWindow()
                     .then()
-                    .isAppWindowOnTop(ComponentMatcher.SNAPSHOT, isOptional = true)
+                    .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
                     .then()
-                    .isAppWindowOnTop(ComponentMatcher.SPLASH_SCREEN, isOptional = true)
+                    .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
                     .then()
                     .isAppWindowOnTop(testApp)
         }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
index 2973059..75311ea 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
@@ -26,7 +26,7 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -88,7 +88,7 @@
         testSpec.assertWm {
             this.hasNoVisibleAppWindow()
                     .then()
-                    .isAppWindowOnTop(ComponentMatcher.SNAPSHOT, isOptional = true)
+                    .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
                     .then()
                     .isAppWindowOnTop(showWhenLockedApp)
         }
@@ -100,7 +100,7 @@
         testSpec.assertLayers {
             this.isInvisible(showWhenLockedApp)
                     .then()
-                    .isVisible(ComponentMatcher.SNAPSHOT, isOptional = true)
+                    .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
                     .then()
                     .isVisible(showWhenLockedApp)
         }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
index 1d8b0a6..ecc60b8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
@@ -22,7 +22,7 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.navBarLayerPositionAtEnd
 import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Assume
 import org.junit.Ignore
 import org.junit.Test
@@ -77,9 +77,9 @@
         testSpec.assertWm {
             this.hasNoVisibleAppWindow()
                     .then()
-                    .isAppWindowOnTop(ComponentMatcher.SNAPSHOT, isOptional = true)
+                    .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
                     .then()
-                    .isAppWindowOnTop(ComponentMatcher.SPLASH_SCREEN, isOptional = true)
+                    .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
                     .then()
                     .isAppWindowOnTop(testApp)
         }
@@ -152,7 +152,7 @@
     @Test
     fun statusBarLayerIsVisibleAtEnd() {
         testSpec.assertLayersEnd {
-            this.isVisible(ComponentMatcher.STATUS_BAR)
+            this.isVisible(ComponentNameMatcher.STATUS_BAR)
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
index 866e819..78baddf 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
@@ -34,7 +34,6 @@
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.taskBarLayerIsVisibleAtEnd
 import com.android.server.wm.flicker.taskBarWindowIsVisibleAtEnd
-import com.android.server.wm.traces.common.ComponentMatcher
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Ignore
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index 6476077..53be7d4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -29,7 +29,7 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Ignore
@@ -76,9 +76,9 @@
     fun navBarLayerVisibilityChanges() {
         Assume.assumeFalse(testSpec.isTablet)
         testSpec.assertLayers {
-            this.isInvisible(ComponentMatcher.NAV_BAR)
+            this.isInvisible(ComponentNameMatcher.NAV_BAR)
                 .then()
-                .isVisible(ComponentMatcher.NAV_BAR)
+                .isVisible(ComponentNameMatcher.NAV_BAR)
         }
     }
 
@@ -102,9 +102,9 @@
     fun navBarWindowsVisibilityChanges() {
         Assume.assumeFalse(testSpec.isTablet)
         testSpec.assertWm {
-            this.isNonAppWindowInvisible(ComponentMatcher.NAV_BAR)
+            this.isNonAppWindowInvisible(ComponentNameMatcher.NAV_BAR)
                 .then()
-                .isAboveAppWindowVisible(ComponentMatcher.NAV_BAR)
+                .isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR)
         }
     }
 
@@ -117,7 +117,7 @@
     fun taskBarLayerIsVisibleAtEnd() {
         Assume.assumeTrue(testSpec.isTablet)
         testSpec.assertLayersEnd {
-            this.isVisible(ComponentMatcher.TASK_BAR)
+            this.isVisible(ComponentNameMatcher.TASK_BAR)
         }
     }
 
@@ -130,7 +130,7 @@
     @Test
     override fun statusBarLayerIsVisibleAtStartAndEnd() {
         testSpec.assertLayersEnd {
-            this.isVisible(ComponentMatcher.STATUS_BAR)
+            this.isVisible(ComponentNameMatcher.STATUS_BAR)
         }
     }
 
@@ -180,7 +180,7 @@
     fun navBarLayerIsVisibleAtEnd() {
         Assume.assumeFalse(testSpec.isTablet)
         testSpec.assertLayersEnd {
-            this.isVisible(ComponentMatcher.NAV_BAR)
+            this.isVisible(ComponentNameMatcher.NAV_BAR)
         }
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
index 5d2b567..8658c03 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
@@ -24,7 +24,7 @@
 import com.android.server.wm.flicker.helpers.StandardAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Test
 
 /**
@@ -65,9 +65,9 @@
                 .then()
                 .isInvisible(testApp, isOptional = true)
                 .then()
-                .isVisible(ComponentMatcher.SNAPSHOT, isOptional = true)
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
                 .then()
-                .isVisible(ComponentMatcher.SPLASH_SCREEN, isOptional = true)
+                .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
                 .then()
                 .isVisible(testApp)
         }
@@ -77,9 +77,9 @@
         testSpec.assertLayers {
             this.isInvisible(testApp)
                 .then()
-                .isVisible(ComponentMatcher.SNAPSHOT, isOptional = true)
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
                 .then()
-                .isVisible(ComponentMatcher.SPLASH_SCREEN, isOptional = true)
+                .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
                 .then()
                 .isVisible(testApp)
         }
@@ -110,9 +110,9 @@
         testSpec.assertWm {
             this.isAppWindowInvisible(testApp)
                 .then()
-                .isAppWindowVisible(ComponentMatcher.SNAPSHOT, isOptional = true)
+                .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
                 .then()
-                .isAppWindowVisible(ComponentMatcher.SPLASH_SCREEN, isOptional = true)
+                .isAppWindowVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
                 .then()
                 .isAppWindowVisible(testApp)
         }
@@ -130,8 +130,8 @@
                 .then()
                 .isAppWindowOnTop(
                     testApp
-                        .or(ComponentMatcher.SNAPSHOT)
-                        .or(ComponentMatcher.SPLASH_SCREEN)
+                        .or(ComponentNameMatcher.SNAPSHOT)
+                        .or(ComponentNameMatcher.SPLASH_SCREEN)
                 )
         }
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index b482e5f..fe5e74b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -30,9 +30,9 @@
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.testapp.ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME
 import com.android.server.wm.flicker.testapp.ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME
-import com.android.server.wm.traces.common.ComponentMatcher
-import com.android.server.wm.traces.common.ComponentMatcher.Companion.SPLASH_SCREEN
-import com.android.server.wm.traces.common.ComponentMatcher.Companion.WALLPAPER_BBQ_WRAPPER
+import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.SPLASH_SCREEN
+import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
 import com.android.server.wm.traces.common.IComponentMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 import org.junit.FixMethodOrder
@@ -117,7 +117,7 @@
     @Test
     fun launcherWindowIsNeverVisible() {
         testSpec.assertWm {
-            this.isAppWindowInvisible(ComponentMatcher.LAUNCHER)
+            this.isAppWindowInvisible(ComponentNameMatcher.LAUNCHER)
         }
     }
 
@@ -130,7 +130,7 @@
     @Test
     fun launcherLayerIsNeverVisible() {
         testSpec.assertLayers {
-            this.isInvisible(ComponentMatcher.LAUNCHER)
+            this.isInvisible(ComponentNameMatcher.LAUNCHER)
         }
     }
 
@@ -140,7 +140,7 @@
     @Postsubmit
     @Test
     fun colorLayerIsVisibleDuringTransition() {
-        val bgColorLayer = ComponentMatcher("", "colorBackgroundLayer")
+        val bgColorLayer = ComponentNameMatcher("", "colorBackgroundLayer")
         val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
 
         testSpec.assertLayers {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index a9fb0f2..181767b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -30,7 +30,7 @@
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.common.Rect
 import org.junit.Assume
 import org.junit.Before
@@ -178,7 +178,7 @@
         testSpec.assertWm {
             this.isAppWindowInvisible(testApp1)
                 .then()
-                .isAppWindowVisible(ComponentMatcher.SNAPSHOT, isOptional = true)
+                .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
                 .then()
                 .isAppWindowVisible(testApp1)
         }
@@ -238,9 +238,9 @@
             this.isAppWindowVisible(testApp2)
                 .then()
                 // TODO: Do we actually want to test this? Seems too implementation specific...
-                .isAppWindowVisible(ComponentMatcher.LAUNCHER, isOptional = true)
+                .isAppWindowVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
                 .then()
-                .isAppWindowVisible(ComponentMatcher.SNAPSHOT, isOptional = true)
+                .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
                 .then()
                 .isAppWindowVisible(testApp1)
         }
@@ -257,9 +257,9 @@
         testSpec.assertLayers {
             this.isVisible(testApp2)
                 .then()
-                .isVisible(ComponentMatcher.LAUNCHER, isOptional = true)
+                .isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
                 .then()
-                .isVisible(ComponentMatcher.SNAPSHOT, isOptional = true)
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
                 .then()
                 .isVisible(testApp1)
         }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
index 2607ee5..461bae4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
@@ -24,7 +24,6 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
-import com.android.server.wm.traces.common.ComponentMatcher
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 3b60212..0f05622 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -31,7 +31,7 @@
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.common.Rect
 import org.junit.Assume
 import org.junit.Before
@@ -112,7 +112,7 @@
     @Test
     open fun startsWithApp1WindowsCoverFullScreen() {
         testSpec.assertWmStart {
-            this.visibleRegion(testApp1.or(ComponentMatcher.LETTERBOX))
+            this.visibleRegion(testApp1.or(ComponentNameMatcher.LETTERBOX))
                 .coversExactly(startDisplayBounds)
         }
     }
@@ -160,7 +160,7 @@
     @Test
     open fun endsWithApp2LayersCoveringFullScreen() {
         testSpec.assertLayersEnd {
-            this.visibleRegion(testApp2.or(ComponentMatcher.LETTERBOX))
+            this.visibleRegion(testApp2.or(ComponentNameMatcher.LETTERBOX))
                 .coversExactly(startDisplayBounds)
         }
     }
@@ -187,7 +187,7 @@
         testSpec.assertWm {
             this.isAppWindowInvisible(testApp2)
                     .then()
-                    .isAppWindowVisible(ComponentMatcher.SNAPSHOT, isOptional = true)
+                    .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
                     .then()
                     .isAppWindowVisible(testApp2)
         }
@@ -246,9 +246,9 @@
         testSpec.assertWm {
             this.isAppWindowVisible(testApp1)
                     .then()
-                    .isAppWindowVisible(ComponentMatcher.LAUNCHER, isOptional = true)
+                    .isAppWindowVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
                     .then()
-                    .isAppWindowVisible(ComponentMatcher.SNAPSHOT, isOptional = true)
+                    .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
                     .then()
                     .isAppWindowVisible(testApp2)
         }
@@ -265,9 +265,9 @@
         testSpec.assertLayers {
             this.isVisible(testApp1)
                     .then()
-                    .isVisible(ComponentMatcher.LAUNCHER, isOptional = true)
+                    .isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
                     .then()
-                    .isVisible(ComponentMatcher.SNAPSHOT, isOptional = true)
+                    .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
                     .then()
                     .isVisible(testApp2)
         }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
index 27ae125..f644b97 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
@@ -24,7 +24,6 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
-import com.android.server.wm.traces.common.ComponentMatcher
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index c79b552..d1f356c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -30,7 +30,7 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.common.Rect
 import org.junit.Assume
 import org.junit.FixMethodOrder
@@ -152,7 +152,7 @@
     @Test
     fun startsWithLauncherWindowsCoverFullScreen() {
         testSpec.assertWmStart {
-            this.visibleRegion(ComponentMatcher.LAUNCHER).coversExactly(startDisplayBounds)
+            this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
         }
     }
 
@@ -164,7 +164,7 @@
     @Test
     fun startsWithLauncherLayersCoverFullScreen() {
         testSpec.assertLayersStart {
-            this.visibleRegion(ComponentMatcher.LAUNCHER).coversExactly(startDisplayBounds)
+            this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
         }
     }
 
@@ -175,7 +175,7 @@
     @Test
     fun startsWithLauncherBeingOnTop() {
         testSpec.assertWmStart {
-            this.isAppWindowOnTop(ComponentMatcher.LAUNCHER)
+            this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
         }
     }
 
@@ -228,9 +228,9 @@
     @Test
     fun launcherWindowBecomesAndStaysInvisible() {
         testSpec.assertWm {
-            this.isAppWindowOnTop(ComponentMatcher.LAUNCHER)
+            this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
                     .then()
-                    .isAppWindowNotOnTop(ComponentMatcher.LAUNCHER)
+                    .isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER)
         }
     }
 
@@ -243,9 +243,9 @@
     @Test
     fun launcherLayerBecomesAndStaysInvisible() {
         testSpec.assertLayers {
-            this.isVisible(ComponentMatcher.LAUNCHER)
+            this.isVisible(ComponentNameMatcher.LAUNCHER)
                     .then()
-                    .isInvisible(ComponentMatcher.LAUNCHER)
+                    .isInvisible(ComponentNameMatcher.LAUNCHER)
         }
     }
 
@@ -258,9 +258,9 @@
     @Test
     fun appWindowIsVisibleOnceLauncherWindowIsInvisible() {
         testSpec.assertWm {
-            this.isAppWindowOnTop(ComponentMatcher.LAUNCHER)
+            this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
                     .then()
-                    .isAppWindowVisible(ComponentMatcher.SNAPSHOT, isOptional = true)
+                    .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
                     .then()
                     .isAppWindowVisible(testApp)
         }
@@ -275,9 +275,9 @@
     @Test
     fun appLayerIsVisibleOnceLauncherLayerIsInvisible() {
         testSpec.assertLayers {
-            this.isVisible(ComponentMatcher.LAUNCHER)
+            this.isVisible(ComponentNameMatcher.LAUNCHER)
                     .then()
-                    .isVisible(ComponentMatcher.SNAPSHOT, isOptional = true)
+                    .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
                     .then()
                     .isVisible(testApp)
         }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 5e80fab..4be8963 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -25,7 +25,7 @@
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -106,10 +106,10 @@
         testSpec.assertLayers {
             this.isVisible(testApp)
                 .then()
-                .isVisible(ComponentMatcher.ROTATION)
+                .isVisible(ComponentNameMatcher.ROTATION)
                 .then()
                 .isVisible(testApp)
-                .isInvisible(ComponentMatcher.ROTATION)
+                .isInvisible(ComponentNameMatcher.ROTATION)
         }
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index 36a1521..7e159d4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -22,7 +22,7 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.StandardAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.Test
 
 /**
@@ -55,9 +55,9 @@
         testSpec.assertLayers {
             this.visibleLayersShownMoreThanOneConsecutiveEntry(
                 ignoreLayers = listOf(
-                    ComponentMatcher.SPLASH_SCREEN,
-                    ComponentMatcher.SNAPSHOT,
-                    ComponentMatcher("", "SecondaryHomeHandle")
+                    ComponentNameMatcher.SPLASH_SCREEN,
+                    ComponentNameMatcher.SNAPSHOT,
+                    ComponentNameMatcher("", "SecondaryHomeHandle")
                 )
             )
         }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 1e3caa4..0912812 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -27,7 +27,7 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import org.junit.FixMethodOrder
 import org.junit.Ignore
 import org.junit.Test
@@ -186,7 +186,7 @@
     @Test
     fun statusBarWindowIsAlwaysInvisible() {
         testSpec.assertWm {
-            this.isAboveAppWindowInvisible(ComponentMatcher.STATUS_BAR)
+            this.isAboveAppWindowInvisible(ComponentNameMatcher.STATUS_BAR)
         }
     }
 
@@ -198,7 +198,7 @@
     @Test
     fun statusBarLayerIsAlwaysInvisible() {
         testSpec.assertLayers {
-            this.isInvisible(ComponentMatcher.STATUS_BAR)
+            this.isInvisible(ComponentNameMatcher.STATUS_BAR)
         }
     }
 
diff --git a/tests/testables/tests/AndroidManifest.xml b/tests/testables/tests/AndroidManifest.xml
index 2bfb04f..1731f6b 100644
--- a/tests/testables/tests/AndroidManifest.xml
+++ b/tests/testables/tests/AndroidManifest.xml
@@ -21,7 +21,7 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.MANAGE_USERS" />
 
-    <application android:debuggable="true">
+    <application android:debuggable="true" android:testOnly="true">
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/tests/testables/tests/AndroidTest.xml b/tests/testables/tests/AndroidTest.xml
new file mode 100644
index 0000000..de1165f
--- /dev/null
+++ b/tests/testables/tests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Runs Testable Tests.">
+    <option name="test-tag" value="TestablesTests" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="TestablesTests.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.testables"/>
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java
index 3b201f9..e4add80 100644
--- a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java
@@ -16,6 +16,9 @@
 
 package android.net.vcn.persistablebundleutils;
 
+import static android.net.vcn.persistablebundleutils.IkeSessionParamsUtils.IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION;
+import static android.net.vcn.persistablebundleutils.IkeSessionParamsUtils.IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES;
+import static android.net.vcn.persistablebundleutils.IkeSessionParamsUtils.isIkeOptionValid;
 import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.AF_INET6;
 import static android.telephony.TelephonyManager.APPTYPE_USIM;
@@ -134,15 +137,37 @@
         verifyPersistableBundleEncodeDecodeIsLossless(params);
     }
 
+    private static IkeSessionParams.Builder createBuilderMinimumWithEap() throws Exception {
+        final X509Certificate serverCaCert = createCertFromPemFile("self-signed-ca.pem");
+
+        final byte[] eapId = "test@android.net".getBytes(StandardCharsets.US_ASCII);
+        final int subId = 1;
+        final EapSessionConfig eapConfig =
+                new EapSessionConfig.Builder()
+                        .setEapIdentity(eapId)
+                        .setEapSimConfig(subId, APPTYPE_USIM)
+                        .setEapAkaConfig(subId, APPTYPE_USIM)
+                        .build();
+        return createBuilderMinimum().setAuthEap(serverCaCert, eapConfig);
+    }
+
     @Test
     public void testEncodeDecodeParamsWithIkeOptions() throws Exception {
-        final IkeSessionParams params =
-                createBuilderMinimum()
+        final IkeSessionParams.Builder builder =
+                createBuilderMinimumWithEap()
                         .addIkeOption(IkeSessionParams.IKE_OPTION_ACCEPT_ANY_REMOTE_ID)
+                        .addIkeOption(IkeSessionParams.IKE_OPTION_EAP_ONLY_AUTH)
                         .addIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE)
+                        .addIkeOption(IkeSessionParams.IKE_OPTION_FORCE_PORT_4500)
                         .addIkeOption(IkeSessionParams.IKE_OPTION_INITIAL_CONTACT)
-                        .build();
-        verifyPersistableBundleEncodeDecodeIsLossless(params);
+                        .addIkeOption(IkeSessionParams.IKE_OPTION_REKEY_MOBILITY);
+        if (isIkeOptionValid(IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION)) {
+            builder.addIkeOption(IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION);
+        }
+        if (isIkeOptionValid(IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES)) {
+            builder.addIkeOption(IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES);
+        }
+        verifyPersistableBundleEncodeDecodeIsLossless(builder.build());
     }
 
     private static InputStream openAssetsFile(String fileName) throws Exception {
@@ -176,19 +201,7 @@
 
     @Test
     public void testEncodeRecodeParamsWithEapAuth() throws Exception {
-        final X509Certificate serverCaCert = createCertFromPemFile("self-signed-ca.pem");
-
-        final byte[] eapId = "test@android.net".getBytes(StandardCharsets.US_ASCII);
-        final int subId = 1;
-        final EapSessionConfig eapConfig =
-                new EapSessionConfig.Builder()
-                        .setEapIdentity(eapId)
-                        .setEapSimConfig(subId, APPTYPE_USIM)
-                        .setEapAkaConfig(subId, APPTYPE_USIM)
-                        .build();
-
-        final IkeSessionParams params =
-                createBuilderMinimum().setAuthEap(serverCaCert, eapConfig).build();
+        final IkeSessionParams params = createBuilderMinimumWithEap().build();
         verifyPersistableBundleEncodeDecodeIsLossless(params);
     }
 }
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index 39ac24b..b3165d3 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -1981,10 +1981,11 @@
       if (ElementCast<Activity>(parent_stack[1])) {
         // Detects the presence of a particular type of activity.
         Activity* activity = ElementCast<Activity>(parent_stack[1]);
-        auto map = std::map<std::string, std::string>({
-            { "android.intent.action.MAIN" , "main" },
-            { "android.intent.action.VIDEO_CAMERA" , "camera" },
-            { "android.intent.action.STILL_IMAGE_CAMERA_SECURE" , "camera-secure" },
+        static const auto map = std::map<std::string, std::string>({
+            {"android.intent.action.MAIN", "main"},
+            {"android.media.action.VIDEO_CAMERA", "camera"},
+            {"android.media.action.STILL_IMAGE_CAMERA", "camera"},
+            {"android.media.action.STILL_IMAGE_CAMERA_SECURE", "camera-secure"},
         });
 
         auto entry = map.find(action);
@@ -2735,10 +2736,9 @@
   auto it = apk_->GetFileCollection()->Iterator();
   while (it->HasNext()) {
     auto file_path = it->Next()->GetSource().path;
-    size_t pos = file_path.find("lib/");
-    if (pos != std::string::npos) {
-      file_path = file_path.substr(pos + 4);
-      pos = file_path.find('/');
+    if (file_path.starts_with("lib/")) {
+      file_path = file_path.substr(4);
+      size_t pos = file_path.find('/');
       if (pos != std::string::npos) {
         file_path = file_path.substr(0, pos);
       }
diff --git a/tools/aapt2/integration-tests/DumpTest/components.apk b/tools/aapt2/integration-tests/DumpTest/components.apk
index deb55ea..a34ec83 100644
--- a/tools/aapt2/integration-tests/DumpTest/components.apk
+++ b/tools/aapt2/integration-tests/DumpTest/components.apk
Binary files differ
diff --git a/tools/aapt2/integration-tests/DumpTest/components_full_proto.txt b/tools/aapt2/integration-tests/DumpTest/components_full_proto.txt
index bd76736..8e733a5 100644
--- a/tools/aapt2/integration-tests/DumpTest/components_full_proto.txt
+++ b/tools/aapt2/integration-tests/DumpTest/components_full_proto.txt
@@ -1677,7 +1677,7 @@
                       attribute {
                         namespace_uri: "http://schemas.android.com/apk/res/android"
                         name: "name"
-                        value: "android.intent.action.VIDEO_CAMERA"
+                        value: "android.media.action.VIDEO_CAMERA"
                         resource_id: 16842755
                       }
                     }
@@ -1691,7 +1691,7 @@
                       attribute {
                         namespace_uri: "http://schemas.android.com/apk/res/android"
                         name: "name"
-                        value: "android.intent.action.STILL_IMAGE_CAMERA_SECURE"
+                        value: "android.media.action.STILL_IMAGE_CAMERA_SECURE"
                         resource_id: 16842755
                       }
                     }
diff --git a/tools/lint/Android.bp b/tools/lint/Android.bp
index 2601041..96618f4 100644
--- a/tools/lint/Android.bp
+++ b/tools/lint/Android.bp
@@ -51,3 +51,9 @@
         unit_test: true,
     },
 }
+
+python_binary_host {
+    name: "lint_fix",
+    main: "fix/lint_fix.py",
+    srcs: ["fix/lint_fix.py"],
+}
diff --git a/tools/lint/OWNERS b/tools/lint/OWNERS
index 7c04519..2c526a1 100644
--- a/tools/lint/OWNERS
+++ b/tools/lint/OWNERS
@@ -2,4 +2,5 @@
 jsharkey@google.com
 
 per-file *CallingSettingsNonUserGetterMethods* = file:/packages/SettingsProvider/OWNERS
+per-file *RegisterReceiverFlagDetector* = jacobhobbie@google.com
 
diff --git a/tools/lint/README.md b/tools/lint/README.md
index c674d36..99149c1 100644
--- a/tools/lint/README.md
+++ b/tools/lint/README.md
@@ -44,6 +44,10 @@
   environment variable with the id of the lint. For example:
   `ANDROID_LINT_CHECK=UnusedTokenOfOriginalCallingIdentity m out/[...]/lint-report.html`
 
+## How to apply automatic fixes suggested by lint
+
+See [lint_fix](fix/README.md)
+
 ## Create or update a baseline
 
 Baseline files can be used to silence known errors (and warnings) that are deemed to be safe. When
diff --git a/tools/lint/fix/README.md b/tools/lint/fix/README.md
new file mode 100644
index 0000000..367d0bc
--- /dev/null
+++ b/tools/lint/fix/README.md
@@ -0,0 +1,46 @@
+# Refactoring the platform with lint
+Inspiration: go/refactor-the-platform-with-lint\
+**Special Thanks: brufino@, azharaa@, for the prior work that made this all possible**
+
+## What is this?
+
+It's a python script that runs the framework linter,
+and then copies modified files back into the source tree.\
+Why python, you ask?  Because python is cool ¯\_(ツ)_/¯.
+
+## Why?
+
+Lint is not allowed to modify source files directly via lint's `--apply-suggestions` flag.
+As a compromise, soong zips up the (potentially) modified sources and leaves them in an intermediate
+directory.  This script runs the lint, unpacks those files, and copies them back into the tree.
+
+## How do I run it?
+**WARNING: You probably want to commit/stash any changes to your working tree before doing this...**
+
+From this directory, run `python lint_fix.py -h`.
+The script's help output explains things that are omitted here.
+
+Alternatively, there is a python binary target you can build to make this
+available anywhere in your tree:
+```
+m lint_fix
+lint_fix -h
+```
+
+**Gotcha**: You must have run `source build/envsetup.sh` and `lunch` first.
+
+Example: `lint_fix frameworks/base/services/core/services.core.unboosted UseEnforcePermissionAnnotation --dry-run`
+```shell
+(
+export ANDROID_LINT_CHECK=UseEnforcePermissionAnnotation;
+cd $ANDROID_BUILD_TOP;
+source build/envsetup.sh;
+rm out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/lint/lint-report.html;
+m out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/lint/lint-report.html;
+cd out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/lint;
+unzip suggested-fixes.zip -d suggested-fixes;
+cd suggested-fixes;
+find . -path ./out -prune -o -name '*.java' -print | xargs -n 1 sh -c 'cp $1 $ANDROID_BUILD_TOP/$1' --;
+rm -rf suggested-fixes
+)
+```
diff --git a/tools/lint/fix/lint_fix.py b/tools/lint/fix/lint_fix.py
new file mode 100644
index 0000000..3ff8131
--- /dev/null
+++ b/tools/lint/fix/lint_fix.py
@@ -0,0 +1,76 @@
+import argparse
+import os
+import sys
+
+ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP")
+PATH_PREFIX = "out/soong/.intermediates"
+PATH_SUFFIX = "android_common/lint"
+FIX_DIR = "suggested-fixes"
+
+parser = argparse.ArgumentParser(description="""
+This is a python script that applies lint fixes to the platform:
+1. Set up the environment, etc.
+2. Build the lint and run it.
+3. Unpack soong's intermediate zip containing source files modified by lint.
+4. Copy the modified files back into the tree.
+
+**Gotcha**: You must have run `source build/envsetup.sh` and `lunch` \
+so that the `ANDROID_BUILD_TOP` environment variable has been set.
+Alternatively, set it manually in your shell.
+""", formatter_class=argparse.RawTextHelpFormatter)
+
+parser.add_argument('build_path', metavar='build_path', type=str,
+                    help='The build module to run '
+                         '(e.g. frameworks/base/framework-minus-apex or '
+                         'frameworks/base/services/core/services.core.unboosted)')
+
+parser.add_argument('--check', metavar='check', type=str,
+                    help='Which lint to run. Passed to the ANDROID_LINT_CHECK environment variable.')
+
+parser.add_argument('--dry-run', dest='dry_run', action='store_true',
+                    help='Just print the resulting shell script instead of running it.')
+
+parser.add_argument('--no-fix', dest='no_fix', action='store_true',
+                    help='Just build and run the lint, do NOT apply the fixes.')
+
+args = parser.parse_args()
+
+path = f"{PATH_PREFIX}/{args.build_path}/{PATH_SUFFIX}"
+target = f"{path}/lint-report.html"
+
+commands = []
+
+if not args.dry_run:
+    commands += [f"export ANDROID_BUILD_TOP={ANDROID_BUILD_TOP}"]
+
+if args.check:
+    commands += [f"export ANDROID_LINT_CHECK={args.check}"]
+
+commands += [
+    "cd $ANDROID_BUILD_TOP",
+    "source build/envsetup.sh",
+    f"rm {target}",  # remove the file first so soong doesn't think there is no work to do
+    f"m {target}",
+]
+
+if not args.no_fix:
+    commands += [
+        f"cd {path}",
+        f"unzip {FIX_DIR}.zip -d {FIX_DIR}",
+        f"cd {FIX_DIR}",
+        # Find all the java files in the fix directory, excluding the ./out subdirectory,
+        # and copy them back into the same path within the tree.
+        f"find . -path ./out -prune -o -name '*.java' -print | xargs -n 1 sh -c 'cp $1 $ANDROID_BUILD_TOP/$1' --",
+        f"rm -rf {FIX_DIR}"
+    ]
+
+if args.dry_run:
+    print("(\n" + ";\n".join(commands) + "\n)")
+    sys.exit(0)
+
+with_echo = []
+for c in commands:
+    with_echo.append(f'echo "{c}"')
+    with_echo.append(c)
+
+os.system("(\n" + ";\n".join(with_echo) + "\n)")