Merge "Remove implicit process capability for certain foreground service state."
diff --git a/Android.bp b/Android.bp
index eae0d73..30b38d3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -72,7 +72,6 @@
         ":framework-keystore-sources",
         ":framework-identity-sources",
         ":framework-location-sources",
-        ":framework-lowpan-sources",
         ":framework-mca-effect-sources",
         ":framework-mca-filterfw-sources",
         ":framework-mca-filterpacks-sources",
@@ -167,7 +166,6 @@
             "identity/java",
             "keystore/java",
             "location/java",
-            "lowpan/java",
             "media/java",
             "media/mca/effect/java",
             "media/mca/filterfw/java",
@@ -277,7 +275,6 @@
             ":framework-keystore-sources",
             ":framework-identity-sources",
             ":framework-location-sources",
-            ":framework-lowpan-sources",
             ":framework-mca-effect-sources",
             ":framework-mca-filterfw-sources",
             ":framework-mca-filterpacks-sources",
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index f8aa7e9..1d92778 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -25,4 +25,6 @@
 
 hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclude.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
 
+ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/ktfmt_includes.txt ${PREUPLOAD_FILES}
+
 ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
index 1e2650d..bc8fc53 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
@@ -28,6 +28,7 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -50,9 +51,16 @@
     @Rule
     public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter();
 
-    @ManualBenchmarkState.ManualBenchmarkTest(
-            warmupDurationNs = WARMUP_DURATION_NS,
-            targetTestDurationNs = TARGET_TEST_DURATION_NS)
+    @Before
+    public void setUp() {
+        // Parse and load the preinstalled fonts in the test process so that:
+        // (1) Updated fonts do not affect test results.
+        // (2) Lazy-loading of fonts does not affect test results (esp. testSerializeFontMap).
+        Typeface.loadPreinstalledSystemFontMap();
+    }
+
+    // testSerializeFontMap uses the default targetTestDurationNs, which is much longer than
+    // TARGET_TEST_DURATION_NS, in order to stabilize test results.
     @Test
     public void testSerializeFontMap() throws Exception {
         Map<String, Typeface> systemFontMap = Typeface.getSystemFontMap();
@@ -61,8 +69,12 @@
         long elapsedTime = 0;
         while (state.keepRunning(elapsedTime)) {
             long startTime = System.nanoTime();
-            Typeface.serializeFontMap(systemFontMap);
+            SharedMemory sharedMemory = Typeface.serializeFontMap(systemFontMap);
             elapsedTime = System.nanoTime() - startTime;
+            sharedMemory.close();
+            android.util.Log.i(TAG,
+                    "testSerializeFontMap isWarmingUp=" + state.isWarmingUp()
+                            + " elapsedTime=" + elapsedTime);
         }
     }
 
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/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
index c92c634..fb62920 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
@@ -153,9 +153,9 @@
             final IWindowSession session = WindowManagerGlobal.getWindowSession();
             while (state.keepRunning()) {
                 session.relayout(mWindow, mParams, mWidth, mHeight,
-                        mViewVisibility.getAsInt(), mFlags, mOutFrames,
-                        mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState, mOutControls,
-                        new Bundle());
+                        mViewVisibility.getAsInt(), mFlags, 0 /* seq */, 0 /* lastSyncSeqId */,
+                        mOutFrames, mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState,
+                        mOutControls, new Bundle());
             }
         }
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index d8e25b6..c0a9e67 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -3078,15 +3078,15 @@
                 pw.decreaseIndent();
                 pw.println();
             } else {
-                if (mAppStateTracker != null) {
-                    mAppStateTracker.dump(pw);
-                    pw.println();
-                }
-
                 pw.println("App Standby Parole: " + mAppStandbyParole);
                 pw.println();
             }
 
+            if (mAppStateTracker != null) {
+                mAppStateTracker.dump(pw);
+                pw.println();
+            }
+
             final long nowELAPSED = mInjector.getElapsedRealtime();
             final long nowUPTIME = SystemClock.uptimeMillis();
             final long nowRTC = mInjector.getCurrentTimeMillis();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 73508c8..4d5eef2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -310,11 +310,25 @@
      */
     boolean mReportedActive;
 
+    /**
+     * Track the most recently completed jobs (that had been executing and were stopped for any
+     * reason, including successful completion).
+     */
     private int mLastCompletedJobIndex = 0;
     private final JobStatus[] mLastCompletedJobs = new JobStatus[NUM_COMPLETED_JOB_HISTORY];
     private final long[] mLastCompletedJobTimeElapsed = new long[NUM_COMPLETED_JOB_HISTORY];
 
     /**
+     * Track the most recently cancelled jobs (that had internal reason
+     * {@link JobParameters#INTERNAL_STOP_REASON_CANCELED}.
+     */
+    private int mLastCancelledJobIndex = 0;
+    private final JobStatus[] mLastCancelledJobs =
+            new JobStatus[DEBUG ? NUM_COMPLETED_JOB_HISTORY : 0];
+    private final long[] mLastCancelledJobTimeElapsed =
+            new long[DEBUG ? NUM_COMPLETED_JOB_HISTORY : 0];
+
+    /**
      * A mapping of which uids are currently in the foreground to their effective bias.
      */
     final SparseIntArray mUidBiasOverride = new SparseIntArray();
@@ -1398,6 +1412,12 @@
             startTrackingJobLocked(incomingJob, cancelled);
         }
         reportActiveLocked();
+        if (mLastCancelledJobs.length > 0
+                && internalReasonCode == JobParameters.INTERNAL_STOP_REASON_CANCELED) {
+            mLastCancelledJobs[mLastCancelledJobIndex] = cancelled;
+            mLastCancelledJobTimeElapsed[mLastCancelledJobIndex] = sElapsedRealtimeClock.millis();
+            mLastCancelledJobIndex = (mLastCancelledJobIndex + 1) % mLastCancelledJobs.length;
+        }
     }
 
     void updateUidState(int uid, int procState) {
@@ -1556,7 +1576,10 @@
 
         // Create the controllers.
         mControllers = new ArrayList<StateController>();
-        final FlexibilityController flexibilityController = new FlexibilityController(this);
+        mPrefetchController = new PrefetchController(this);
+        mControllers.add(mPrefetchController);
+        final FlexibilityController flexibilityController =
+                new FlexibilityController(this, mPrefetchController);
         mControllers.add(flexibilityController);
         final ConnectivityController connectivityController =
                 new ConnectivityController(this, flexibilityController);
@@ -1575,8 +1598,6 @@
         mControllers.add(new ContentObserverController(this));
         mDeviceIdleJobsController = new DeviceIdleJobsController(this);
         mControllers.add(mDeviceIdleJobsController);
-        mPrefetchController = new PrefetchController(this);
-        mControllers.add(mPrefetchController);
         mQuotaController =
                 new QuotaController(this, backgroundJobsController, connectivityController);
         mControllers.add(mQuotaController);
@@ -2554,9 +2575,9 @@
     }
 
     private boolean isComponentUsable(@NonNull JobStatus job) {
-        final ServiceInfo service = job.serviceInfo;
+        final String processName = job.serviceProcessName;
 
-        if (service == null) {
+        if (processName == null) {
             if (DEBUG) {
                 Slog.v(TAG, "isComponentUsable: " + job.toShortString()
                         + " component not present");
@@ -2565,8 +2586,7 @@
         }
 
         // Everything else checked out so far, so this is the final yes/no check
-        final boolean appIsBad = mActivityManagerInternal.isAppBad(
-                service.processName, service.applicationInfo.uid);
+        final boolean appIsBad = mActivityManagerInternal.isAppBad(processName, job.getUid());
         if (DEBUG && appIsBad) {
             Slog.i(TAG, "App is bad for " + job.toShortString() + " so not runnable");
         }
@@ -3847,6 +3867,38 @@
             pw.decreaseIndent();
             pw.println();
 
+            boolean recentCancellationsPrinted = false;
+            for (int r = 1; r <= mLastCancelledJobs.length; ++r) {
+                // Print most recent first
+                final int idx = (mLastCancelledJobIndex + mLastCancelledJobs.length - r)
+                        % mLastCancelledJobs.length;
+                job = mLastCancelledJobs[idx];
+                if (job != null) {
+                    if (!predicate.test(job)) {
+                        continue;
+                    }
+                    if (!recentCancellationsPrinted) {
+                        pw.println();
+                        pw.println("Recently cancelled jobs:");
+                        pw.increaseIndent();
+                        recentCancellationsPrinted = true;
+                    }
+                    TimeUtils.formatDuration(mLastCancelledJobTimeElapsed[idx], nowElapsed, pw);
+                    pw.println();
+                    // Double indent for readability
+                    pw.increaseIndent();
+                    pw.increaseIndent();
+                    pw.println(job.toShortString());
+                    job.dump(pw, true, nowElapsed);
+                    pw.decreaseIndent();
+                    pw.decreaseIndent();
+                }
+            }
+            if (!recentCancellationsPrinted) {
+                pw.decreaseIndent();
+                pw.println();
+            }
+
             if (filterUid == -1) {
                 pw.println();
                 pw.print("mReadyToRock="); pw.println(mReadyToRock);
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/ComponentController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
index aca381f..9b59560 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
@@ -93,7 +93,12 @@
         }
     };
 
-    private final SparseArrayMap<ComponentName, ServiceInfo> mServiceInfoCache =
+    /**
+     * Cache containing the processName of the ServiceInfo (see {@link ServiceInfo#processName})
+     * if the Service exists and is available.
+     * {@code null} will be stored if the service is currently unavailable.
+     */
+    private final SparseArrayMap<ComponentName, String> mServiceProcessCache =
             new SparseArrayMap<>();
 
     private final ComponentStateUpdateFunctor mComponentStateUpdateFunctor =
@@ -135,18 +140,18 @@
     @Override
     @GuardedBy("mLock")
     public void onUserRemovedLocked(int userId) {
-        mServiceInfoCache.delete(userId);
+        mServiceProcessCache.delete(userId);
     }
 
     @Nullable
     @GuardedBy("mLock")
-    private ServiceInfo getServiceInfoLocked(JobStatus jobStatus) {
+    private String getServiceProcessLocked(JobStatus jobStatus) {
         final ComponentName service = jobStatus.getServiceComponent();
         final int userId = jobStatus.getUserId();
-        if (mServiceInfoCache.contains(userId, service)) {
+        if (mServiceProcessCache.contains(userId, service)) {
             // Return whatever is in the cache, even if it's null. When something changes, we
             // clear the cache.
-            return mServiceInfoCache.get(userId, service);
+            return mServiceProcessCache.get(userId, service);
         }
 
         ServiceInfo si;
@@ -165,30 +170,31 @@
             // Write null to the cache so we don't keep querying PM.
             si = null;
         }
-        mServiceInfoCache.add(userId, service, si);
+        final String processName = si == null ? null : si.processName;
+        mServiceProcessCache.add(userId, service, processName);
 
-        return si;
+        return processName;
     }
 
     @GuardedBy("mLock")
     private boolean updateComponentEnabledStateLocked(JobStatus jobStatus) {
-        final ServiceInfo service = getServiceInfoLocked(jobStatus);
+        final String processName = getServiceProcessLocked(jobStatus);
 
-        if (DEBUG && service == null) {
+        if (DEBUG && processName == null) {
             Slog.v(TAG, jobStatus.toShortString() + " component not present");
         }
-        final ServiceInfo ogService = jobStatus.serviceInfo;
-        jobStatus.serviceInfo = service;
-        return !Objects.equals(ogService, service);
+        final String ogProcess = jobStatus.serviceProcessName;
+        jobStatus.serviceProcessName = processName;
+        return !Objects.equals(ogProcess, processName);
     }
 
     @GuardedBy("mLock")
     private void clearComponentsForPackageLocked(final int userId, final String pkg) {
-        final int uIdx = mServiceInfoCache.indexOfKey(userId);
-        for (int c = mServiceInfoCache.numElementsForKey(userId) - 1; c >= 0; --c) {
-            final ComponentName cn = mServiceInfoCache.keyAt(uIdx, c);
+        final int uIdx = mServiceProcessCache.indexOfKey(userId);
+        for (int c = mServiceProcessCache.numElementsForKey(userId) - 1; c >= 0; --c) {
+            final ComponentName cn = mServiceProcessCache.keyAt(uIdx, c);
             if (cn.getPackageName().equals(pkg)) {
-                mServiceInfoCache.delete(userId, cn);
+                mServiceProcessCache.delete(userId, cn);
             }
         }
     }
@@ -207,7 +213,7 @@
 
     private void updateComponentStateForUser(final int userId) {
         synchronized (mLock) {
-            mServiceInfoCache.delete(userId);
+            mServiceProcessCache.delete(userId);
             updateComponentStatesLocked(jobStatus -> {
                 // Using user ID instead of source user ID because the service will run under the
                 // user ID, not source user ID.
@@ -247,15 +253,15 @@
     @Override
     @GuardedBy("mLock")
     public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
-        for (int u = 0; u < mServiceInfoCache.numMaps(); ++u) {
-            final int userId = mServiceInfoCache.keyAt(u);
-            for (int p = 0; p < mServiceInfoCache.numElementsForKey(userId); ++p) {
-                final ComponentName componentName = mServiceInfoCache.keyAt(u, p);
+        for (int u = 0; u < mServiceProcessCache.numMaps(); ++u) {
+            final int userId = mServiceProcessCache.keyAt(u);
+            for (int p = 0; p < mServiceProcessCache.numElementsForKey(userId); ++p) {
+                final ComponentName componentName = mServiceProcessCache.keyAt(u, p);
                 pw.print(userId);
                 pw.print("-");
                 pw.print(componentName);
                 pw.print(": ");
-                pw.print(mServiceInfoCache.valueAt(u, p));
+                pw.print(mServiceProcessCache.valueAt(u, p));
                 pw.println();
             }
         }
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 f4ee0ae..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;
 
@@ -23,6 +24,7 @@
 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_CONNECTIVITY;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
 
 import android.annotation.ElapsedRealtimeLong;
@@ -30,12 +32,14 @@
 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;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
+import android.util.SparseArrayMap;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -44,14 +48,13 @@
 import com.android.server.utils.AlarmQueue;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.function.Predicate;
 
 /**
  * Controller that tracks the number of flexible constraints being actively satisfied.
  * Drops constraint for TOP apps and lowers number of required constraints with time.
- *
- * TODO(b/238887951): handle prefetch
  */
 public final class FlexibilityController extends StateController {
     private static final String TAG = "JobScheduler.Flexibility";
@@ -68,24 +71,46 @@
     private static final int FLEXIBLE_CONSTRAINTS =
             JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS | SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
 
-    @VisibleForTesting
-    static final int NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS =
+    private static final int NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS =
             Integer.bitCount(JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS);
 
     static final int NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS =
             Integer.bitCount(SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS);
 
-    @VisibleForTesting
     static final int NUM_FLEXIBLE_CONSTRAINTS = Integer.bitCount(FLEXIBLE_CONSTRAINTS);
 
-    /** Hard cutoff to remove flexible constraints. */
-    private static final long DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
+    private static final long NO_LIFECYCLE_END = Long.MAX_VALUE;
 
     /**
      * The default deadline that all flexible constraints should be dropped by if a job lacks
      * a deadline.
      */
-    private static final long DEFAULT_FLEXIBILITY_DEADLINE = 72 * HOUR_IN_MILLIS;
+    private long mFallbackFlexibilityDeadlineMs =
+            FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
+
+    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,
+     * the controller should have i+1 constraints dropped.
+     */
+    private int[] mPercentToDropConstraints;
+
+    @VisibleForTesting
+    boolean mDeviceSupportsFlexConstraints;
 
     /**
      * Keeps track of what flexible constraints are satisfied at the moment.
@@ -94,30 +119,75 @@
     @VisibleForTesting
     @GuardedBy("mLock")
     int mSatisfiedFlexibleConstraints;
-    @GuardedBy("mLock")
-    private boolean mFlexibilityEnabled = FcConstants.DEFAULT_FLEXIBILITY_ENABLED;
 
     @VisibleForTesting
     @GuardedBy("mLock")
     final FlexibilityTracker mFlexibilityTracker;
-    private final FcConstants mFcConstants;
-
-    private final FlexibilityAlarmQueue mFlexibilityAlarmQueue;
-    private static final long MIN_TIME_BETWEEN_ALARMS_MS = MINUTE_IN_MILLIS;
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    final FlexibilityAlarmQueue mFlexibilityAlarmQueue;
+    @VisibleForTesting
+    final FcConfig mFcConfig;
+    @VisibleForTesting
+    final PrefetchController mPrefetchController;
 
     /**
-     * The percent of a Jobs lifecycle to drop number of required constraints.
-     * PERCENT_TO_DROP_CONSTRAINTS[i] denotes that at x% of a Jobs lifecycle,
-     * the controller should have i+1 constraints dropped.
+     * Stores the beginning of prefetch jobs lifecycle per app as a maximum of
+     * the last time the app was used and the last time the launch time was updated.
      */
-    private static final int[] PERCENT_TO_DROP_CONSTRAINTS = {50, 60, 70, 80};
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    final SparseArrayMap<String, Long> mPrefetchLifeCycleStart = new SparseArrayMap<>();
 
-    public FlexibilityController(JobSchedulerService service) {
+    @VisibleForTesting
+    final PrefetchController.PrefetchChangedListener mPrefetchChangedListener =
+            new PrefetchController.PrefetchChangedListener() {
+                @Override
+                public void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId,
+                        String pkgName, long prevEstimatedLaunchTime,
+                        long newEstimatedLaunchTime, long nowElapsed) {
+                    synchronized (mLock) {
+                        final long prefetchThreshold =
+                                mPrefetchController.getLaunchTimeThresholdMs();
+                        boolean jobWasInPrefetchWindow  = prevEstimatedLaunchTime
+                                - prefetchThreshold < nowElapsed;
+                        boolean jobIsInPrefetchWindow  = newEstimatedLaunchTime
+                                - prefetchThreshold < nowElapsed;
+                        if (jobIsInPrefetchWindow != jobWasInPrefetchWindow) {
+                            // If the job was in the window previously then changing the start
+                            // of the lifecycle to the current moment without a large change in the
+                            // end would squeeze the window too tight fail to drop constraints.
+                            mPrefetchLifeCycleStart.add(userId, pkgName, Math.max(nowElapsed,
+                                    mPrefetchLifeCycleStart.getOrDefault(userId, pkgName, 0L)));
+                        }
+                        for (int i = 0; i < jobs.size(); i++) {
+                            JobStatus js = jobs.valueAt(i);
+                            if (!js.hasFlexibilityConstraint()) {
+                                continue;
+                            }
+                            mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
+                            mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed);
+                        }
+                    }
+                }
+            };
+
+    public FlexibilityController(
+            JobSchedulerService service, PrefetchController prefetchController) {
         super(service);
+        mDeviceSupportsFlexConstraints = !mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUTOMOTIVE);
+        mFlexibilityEnabled &= mDeviceSupportsFlexConstraints;
         mFlexibilityTracker = new FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS);
-        mFcConstants = new FcConstants();
+        mFcConfig = new FcConfig();
         mFlexibilityAlarmQueue = new FlexibilityAlarmQueue(
                 mContext, JobSchedulerBackgroundThread.get().getLooper());
+        mPercentToDropConstraints =
+                mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+        mPrefetchController = prefetchController;
+        if (mFlexibilityEnabled) {
+            mPrefetchController.registerPrefetchChangedListener(mPrefetchChangedListener);
+        }
     }
 
     /**
@@ -127,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.addAlarm(js, getNextConstraintDropTimeElapsed(js));
+            mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed);
         }
     }
 
@@ -144,6 +218,19 @@
         }
     }
 
+    @Override
+    @GuardedBy("mLock")
+    public void onAppRemovedLocked(String packageName, int uid) {
+        final int userId = UserHandle.getUserId(uid);
+        mPrefetchLifeCycleStart.delete(userId, packageName);
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    public void onUserRemovedLocked(int userId) {
+        mPrefetchLifeCycleStart.delete(userId);
+    }
+
     /** Checks if the flexibility constraint is actively satisfied for a given job. */
     @GuardedBy("mLock")
     boolean isFlexibilitySatisfiedLocked(JobStatus js) {
@@ -165,7 +252,8 @@
      * Sets the controller's constraint to a given state.
      * Changes flexibility constraint satisfaction for affected jobs.
      */
-    void setConstraintSatisfied(int constraint, boolean state) {
+    @VisibleForTesting
+    void setConstraintSatisfied(int constraint, boolean state, long nowElapsed) {
         synchronized (mLock) {
             final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0;
             if (old == state) {
@@ -181,27 +269,24 @@
             // 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.
             for (int j = 0; j <= NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS; j++) {
-                final ArraySet<JobStatus> jobs = mFlexibilityTracker
+                final ArraySet<JobStatus> jobsByNumConstraints = mFlexibilityTracker
                         .getJobsByNumRequiredConstraints(numConstraintsToUpdate + j);
 
-                if (jobs == null) {
+                if (jobsByNumConstraints == null) {
                     // If there are no more jobs to iterate through we can just return.
                     return;
                 }
 
-                for (int i = 0; i < jobs.size(); i++) {
-                    JobStatus js = jobs.valueAt(i);
+                for (int i = 0; i < jobsByNumConstraints.size(); i++) {
+                    JobStatus js = jobsByNumConstraints.valueAt(i);
                     js.setFlexibilityConstraintSatisfied(
                             nowElapsed, isFlexibilitySatisfiedLocked(js));
                 }
             }
-
         }
     }
 
@@ -211,16 +296,88 @@
         return (mSatisfiedFlexibleConstraints & constraint) != 0;
     }
 
-    /** The elapsed time that marks when the next constraint should be dropped. */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    long getLifeCycleBeginningElapsedLocked(JobStatus js) {
+        if (js.getJob().isPrefetch()) {
+            final long earliestRuntime = Math.max(js.enqueueTime, js.getEarliestRunTime());
+            final long estimatedLaunchTime =
+                    mPrefetchController.getNextEstimatedLaunchTimeLocked(js);
+            long prefetchWindowStart = mPrefetchLifeCycleStart.getOrDefault(
+                    js.getSourceUserId(), js.getSourcePackageName(), 0L);
+            if (estimatedLaunchTime != Long.MAX_VALUE) {
+                prefetchWindowStart = Math.max(prefetchWindowStart,
+                        estimatedLaunchTime - mPrefetchController.getLaunchTimeThresholdMs());
+            }
+            return Math.max(prefetchWindowStart, earliestRuntime);
+        }
+        return js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME
+                ? js.enqueueTime : js.getEarliestRunTime();
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    long getLifeCycleEndElapsedLocked(JobStatus js, long earliest) {
+        if (js.getJob().isPrefetch()) {
+            final long estimatedLaunchTime =
+                    mPrefetchController.getNextEstimatedLaunchTimeLocked(js);
+            // Prefetch jobs aren't supposed to have deadlines after T.
+            // But some legacy apps might still schedule them with deadlines.
+            if (js.getLatestRunTimeElapsed() != JobStatus.NO_LATEST_RUNTIME) {
+                // If there is a deadline, the earliest time is the end of the lifecycle.
+                return Math.min(
+                        estimatedLaunchTime - mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
+                        js.getLatestRunTimeElapsed());
+            }
+            if (estimatedLaunchTime != Long.MAX_VALUE) {
+                return estimatedLaunchTime - mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS;
+            }
+            // 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, long nowElapsed) {
+        final long earliest = getLifeCycleBeginningElapsedLocked(js);
+        final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+        if (latest == NO_LIFECYCLE_END || earliest >= nowElapsed) {
+            return 0;
+        }
+        if (nowElapsed > latest || latest == earliest) {
+            return 100;
+        }
+        final int percentInTime = (int) ((nowElapsed - earliest) * 100 / (latest - earliest));
+        return percentInTime;
+    }
+
     @VisibleForTesting
     @ElapsedRealtimeLong
-    long getNextConstraintDropTimeElapsed(JobStatus js) {
-        final long earliest = js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME
-                ? js.enqueueTime : js.getEarliestRunTime();
-        final long latest = js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME
-                ? earliest + DEFAULT_FLEXIBILITY_DEADLINE
-                : js.getLatestRunTimeElapsed();
-        final int percent = PERCENT_TO_DROP_CONSTRAINTS[js.getNumDroppedFlexibleConstraints()];
+    @GuardedBy("mLock")
+    long getNextConstraintDropTimeElapsedLocked(JobStatus js) {
+        final long earliest = getLifeCycleBeginningElapsedLocked(js);
+        final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+        return getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
+    }
+
+    /** The elapsed time that marks when the next constraint should be dropped. */
+    @ElapsedRealtimeLong
+    @GuardedBy("mLock")
+    long getNextConstraintDropTimeElapsedLocked(JobStatus js, long earliest, long latest) {
+        if (latest == NO_LIFECYCLE_END
+                || js.getNumDroppedFlexibleConstraints() == mPercentToDropConstraints.length) {
+            return NO_LIFECYCLE_END;
+        }
+        final int percent = mPercentToDropConstraints[js.getNumDroppedFlexibleConstraints()];
         final long percentInTime = ((latest - earliest) * percent) / 100;
         return earliest + percentInTime;
     }
@@ -233,10 +390,28 @@
         }
         final long nowElapsed = sElapsedRealtimeClock.millis();
         List<JobStatus> jobsByUid = mService.getJobStore().getJobsByUid(uid);
+        boolean hasPrefetch = false;
         for (int i = 0; i < jobsByUid.size(); i++) {
             JobStatus js = jobsByUid.get(i);
             if (js.hasFlexibilityConstraint()) {
                 js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
+                hasPrefetch |= js.getJob().isPrefetch();
+            }
+        }
+
+        // Prefetch jobs can't run when the app is TOP, so it should not be included in their
+        // lifecycle, and marks the beginning of a new lifecycle.
+        if (hasPrefetch && prevBias == JobInfo.BIAS_TOP_APP) {
+            final int userId = UserHandle.getUserId(uid);
+            final ArraySet<String> pkgs = mService.getPackagesForUidLocked(uid);
+            if (pkgs == null) {
+                return;
+            }
+            for (int i = 0; i < pkgs.size(); i++) {
+                String pkg = pkgs.valueAt(i);
+                mPrefetchLifeCycleStart.add(userId, pkg,
+                        Math.max(mPrefetchLifeCycleStart.getOrDefault(userId, pkg, 0L),
+                                nowElapsed));
             }
         }
     }
@@ -244,17 +419,18 @@
     @Override
     @GuardedBy("mLock")
     public void onConstantsUpdatedLocked() {
-        if (mFcConstants.mShouldReevaluateConstraints) {
-            // Update job bookkeeping out of band.
+        if (mFcConfig.mShouldReevaluateConstraints) {
             JobSchedulerBackgroundThread.getHandler().post(() -> {
                 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, nowElapsed);
+                            mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed);
                             if (js.setFlexibilityConstraintSatisfied(
                                     nowElapsed, isFlexibilitySatisfiedLocked(js))) {
                                 changedJobs.add(js);
@@ -272,7 +448,13 @@
     @Override
     @GuardedBy("mLock")
     public void prepareForUpdatedConstantsLocked() {
-        mFcConstants.mShouldReevaluateConstraints = false;
+        mFcConfig.mShouldReevaluateConstraints = false;
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    public void processConstantLocked(DeviceConfig.Properties properties, String key) {
+        mFcConfig.processConstantLocked(properties, key);
     }
 
     @VisibleForTesting
@@ -293,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. */
@@ -309,7 +491,21 @@
             if (js.getNumRequiredFlexibleConstraints() == 0) {
                 return;
             }
-            mTrackedJobs.get(js.getNumRequiredFlexibleConstraints() - 1).remove(js);
+            mTrackedJobs.get(js.getNumRequiredFlexibleConstraints()).remove(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);
+            for (int i = 0; i < jsMaxFlexibleConstraints; i++) {
+                if (curPercent >= mPercentToDropConstraints[i]) {
+                    toDrop++;
+                }
+            }
+            adjustJobsRequiredConstraints(
+                    js, js.getNumDroppedFlexibleConstraints() - toDrop, nowElapsed);
         }
 
         /** Returns all tracked jobs. */
@@ -320,19 +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) {
-            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;
+        public boolean adjustJobsRequiredConstraints(JobStatus js, int adjustBy, long nowElapsed) {
+            if (adjustBy != 0) {
+                remove(js);
+                js.adjustNumRequiredFlexibleConstraints(adjustBy);
+                js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
+                add(js);
             }
-            add(js);
-            return true;
+            return js.getNumRequiredFlexibleConstraints() > 0;
         }
 
         public int size() {
@@ -342,7 +534,7 @@
         public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
             for (int i = 0; i < mTrackedJobs.size(); i++) {
                 ArraySet<JobStatus> jobs = mTrackedJobs.get(i);
-                for (int j = 0; j < mTrackedJobs.size(); j++) {
+                for (int j = 0; j < jobs.size(); j++) {
                     final JobStatus js = jobs.valueAt(j);
                     if (!predicate.test(js)) {
                         continue;
@@ -351,17 +543,20 @@
                     js.printUniqueId(pw);
                     pw.print(" from ");
                     UserHandle.formatUid(pw, js.getSourceUid());
+                    pw.print(" Num Required Constraints: ");
+                    pw.print(js.getNumRequiredFlexibleConstraints());
                     pw.println();
                 }
             }
         }
     }
 
-    private class FlexibilityAlarmQueue extends AlarmQueue<JobStatus> {
+    @VisibleForTesting
+    class FlexibilityAlarmQueue extends AlarmQueue<JobStatus> {
         private FlexibilityAlarmQueue(Context context, Looper looper) {
             super(context, looper, "*job.flexibility_check*",
                     "Flexible Constraint Check", false,
-                    MIN_TIME_BETWEEN_ALARMS_MS);
+                    mMinTimeBetweenFlexibilityAlarmsMs);
         }
 
         @Override
@@ -369,52 +564,201 @@
             return js.getSourceUserId() == userId;
         }
 
+        public void scheduleDropNumConstraintsAlarm(JobStatus js, long nowElapsed) {
+            synchronized (mLock) {
+                final long earliest = getLifeCycleBeginningElapsedLocked(js);
+                final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+                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) {
+                    addAlarm(js, latest - mDeadlineProximityLimitMs);
+                    return;
+                }
+                addAlarm(js, nextTimeElapsed);
+            }
+        }
+
         @Override
         protected void processExpiredAlarms(@NonNull ArraySet<JobStatus> expired) {
             synchronized (mLock) {
-                JobStatus js;
+                ArraySet<JobStatus> changedJobs = new ArraySet<>();
+                final long nowElapsed = sElapsedRealtimeClock.millis();
                 for (int i = 0; i < expired.size(); i++) {
-                    js = expired.valueAt(i);
-                    long time = getNextConstraintDropTimeElapsed(js);
-                    int toDecrease =
-                            js.getLatestRunTimeElapsed() - time < DEADLINE_PROXIMITY_LIMIT_MS
-                            ? -js.getNumRequiredFlexibleConstraints() : -1;
-                    if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, toDecrease)) {
-                        mFlexibilityAlarmQueue.addAlarm(js, time);
+                    JobStatus js = expired.valueAt(i);
+                    boolean wasFlexibilitySatisfied = js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE);
+                    if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1, nowElapsed)) {
+                        scheduleDropNumConstraintsAlarm(js, nowElapsed);
+                    }
+                    if (wasFlexibilitySatisfied != js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE)) {
+                        changedJobs.add(js);
                     }
                 }
+                mStateChangedListener.onControllerStateChanged(changedJobs);
             }
         }
     }
 
     @VisibleForTesting
-    class FcConstants {
+    class FcConfig {
         private boolean mShouldReevaluateConstraints = false;
 
-        private static final boolean DEFAULT_FLEXIBILITY_ENABLED = false;
-
-        public boolean FLEXIBILITY_ENABLED = DEFAULT_FLEXIBILITY_ENABLED;
-
         /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
-        private static final String FC_CONSTANT_PREFIX = "fc_";
+        private static final String FC_CONFIG_PREFIX = "fc_";
 
-        static final String KEY_FLEXIBILITY_ENABLED = FC_CONSTANT_PREFIX + "enable_flexibility";
+        static final String KEY_FLEXIBILITY_ENABLED = FC_CONFIG_PREFIX + "enable_flexibility";
+        static final String KEY_DEADLINE_PROXIMITY_LIMIT =
+                FC_CONFIG_PREFIX + "flexibility_deadline_proximity_limit_ms";
+        static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE =
+                FC_CONFIG_PREFIX + "fallback_flexibility_deadline_ms";
+        static final String KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS =
+                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";
 
-        // TODO(b/239925946): properly handle DeviceConfig and changing variables
+        private static final boolean DEFAULT_FLEXIBILITY_ENABLED = false;
+        @VisibleForTesting
+        static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
+        @VisibleForTesting
+        static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 72 * HOUR_IN_MILLIS;
+        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
+         * and the flexibility constraint will always be satisfied.
+         */
+        public boolean FLEXIBILITY_ENABLED = DEFAULT_FLEXIBILITY_ENABLED;
+        /** How close to a jobs' deadline all flexible constraints will be dropped. */
+        public long DEADLINE_PROXIMITY_LIMIT_MS = DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS;
+        /** For jobs that lack a deadline, the time that will be used to drop all constraints by. */
+        public long FALLBACK_FLEXIBILITY_DEADLINE_MS = DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
+        public long MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS =
+                DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS;
+        /** 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;
+                        if (mFlexibilityEnabled) {
+                            mPrefetchController
+                                    .registerPrefetchChangedListener(mPrefetchChangedListener);
+                        } else {
+                            mPrefetchController
+                                    .unRegisterPrefetchChangedListener(mPrefetchChangedListener);
+                        }
+                    }
+                    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);
+                    if (mDeadlineProximityLimitMs != DEADLINE_PROXIMITY_LIMIT_MS) {
+                        mDeadlineProximityLimitMs = DEADLINE_PROXIMITY_LIMIT_MS;
+                        mShouldReevaluateConstraints = true;
+                    }
+                    break;
+                case KEY_FALLBACK_FLEXIBILITY_DEADLINE:
+                    FALLBACK_FLEXIBILITY_DEADLINE_MS =
+                            properties.getLong(key, DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS);
+                    if (mFallbackFlexibilityDeadlineMs != FALLBACK_FLEXIBILITY_DEADLINE_MS) {
+                        mFallbackFlexibilityDeadlineMs = FALLBACK_FLEXIBILITY_DEADLINE_MS;
+                        mShouldReevaluateConstraints = true;
+                    }
+                    break;
+                case KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS:
+                    MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS =
+                            properties.getLong(key, DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS);
+                    if (mMinTimeBetweenFlexibilityAlarmsMs
+                            != MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS) {
+                        mMinTimeBetweenFlexibilityAlarmsMs = MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS;
+                        mShouldReevaluateConstraints = true;
+                    }
+                    break;
+                case KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS:
+                    String dropPercentString = properties.getString(key, "");
+                    PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
+                            parsePercentToDropString(dropPercentString);
+                    if (PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS != null
+                            && !Arrays.equals(mPercentToDropConstraints,
+                            PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS)) {
+                        mPercentToDropConstraints = PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS;
+                        mShouldReevaluateConstraints = true;
                     }
                     break;
             }
         }
 
+        private int[] parsePercentToDropString(String s) {
+            String[] dropPercentString = s.split(",");
+            int[] dropPercentInt = new int[NUM_FLEXIBLE_CONSTRAINTS];
+            if (dropPercentInt.length != dropPercentString.length) {
+                return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+            }
+            int prevPercent = 0;
+            for (int i = 0; i < dropPercentString.length; i++) {
+                try {
+                    dropPercentInt[i] =
+                            Integer.parseInt(dropPercentString[i]);
+                } catch (NumberFormatException ex) {
+                    Slog.e(TAG, "Provided string was improperly formatted.", ex);
+                    return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+                }
+                if (dropPercentInt[i] < prevPercent) {
+                    Slog.wtf(TAG, "Percents to drop constraints were not in increasing order.");
+                    return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+                }
+                prevPercent = dropPercentInt[i];
+            }
+
+            return dropPercentInt;
+        }
+
         private void dump(IndentingPrintWriter pw) {
             pw.println();
             pw.print(FlexibilityController.class.getSimpleName());
@@ -422,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();
         }
@@ -429,17 +781,23 @@
 
     @VisibleForTesting
     @NonNull
-    FcConstants getFcConstants() {
-        return mFcConstants;
+    FcConfig getFcConfig() {
+        return mFcConfig;
     }
 
     @Override
     @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);
-        mFcConstants.dump(pw);
+        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 52882fe..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
@@ -35,7 +35,6 @@
 import android.app.job.JobWorkItem;
 import android.content.ClipData;
 import android.content.ComponentName;
-import android.content.pm.ServiceInfo;
 import android.net.Network;
 import android.net.NetworkRequest;
 import android.net.Uri;
@@ -51,6 +50,7 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
@@ -355,7 +355,7 @@
     public ArraySet<Uri> changedUris;
     public ArraySet<String> changedAuthorities;
     public Network network;
-    public ServiceInfo serviceInfo;
+    public String serviceProcessName;
 
     /** The evaluated bias of the job when it started running. */
     public int lastEvaluatedBias;
@@ -572,9 +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
-                && !job.isPrefetch()
+                && numFailures != 1
                 && lacksSomeFlexibleConstraints) {
             mNumRequiredFlexibleConstraints =
                     NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0);
@@ -1669,7 +1671,8 @@
         return readinessStatusWithConstraint(constraint, true);
     }
 
-    private boolean readinessStatusWithConstraint(int constraint, boolean value) {
+    @VisibleForTesting
+    boolean readinessStatusWithConstraint(int constraint, boolean value) {
         boolean oldValue = false;
         int satisfied = mSatisfiedConstraintsOfInterest;
         switch (constraint) {
@@ -1705,6 +1708,15 @@
                 break;
         }
 
+        // The flexibility constraint relies on other constraints to be satisfied.
+        // This function lacks the information to determine if flexibility will be satisfied.
+        // But for the purposes of this function it is still useful to know the jobs' readiness
+        // not including the flexibility constraint. If flexibility is the constraint in question
+        // we can proceed as normal.
+        if (constraint != CONSTRAINT_FLEXIBLE) {
+            satisfied |= CONSTRAINT_FLEXIBLE;
+        }
+
         boolean toReturn = isReady(satisfied);
 
         switch (constraint) {
@@ -1747,7 +1759,7 @@
         // run if its constraints are satisfied).
         // DeviceNotDozing implicit constraint must be satisfied
         // NotRestrictedInBackground implicit constraint must be satisfied
-        return mReadyNotDozing && mReadyNotRestrictedInBg && (serviceInfo != null)
+        return mReadyNotDozing && mReadyNotRestrictedInBg && (serviceProcessName != null)
                 && (mReadyDeadlineSatisfied || isConstraintsSatisfied(satisfiedConstraints));
     }
 
@@ -1919,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) {
@@ -2233,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();
@@ -2286,7 +2309,7 @@
             pw.println(mReadyDynamicSatisfied);
         }
         pw.print("readyComponentEnabled: ");
-        pw.println(serviceInfo != null);
+        pw.println(serviceProcessName != null);
         if ((getFlags() & JobInfo.FLAG_EXPEDITED) != 0) {
             pw.print("expeditedQuotaApproved: ");
             pw.print(mExpeditedQuotaApproved);
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 0f385ef..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
@@ -81,6 +81,8 @@
      */
     @GuardedBy("mLock")
     private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>();
+    @GuardedBy("mLock")
+    private final ArraySet<PrefetchChangedListener> mPrefetchChangedListeners = new ArraySet<>();
     private final ThresholdAlarmListener mThresholdAlarmListener;
 
     /**
@@ -99,6 +101,13 @@
     @GuardedBy("mLock")
     private long mLaunchTimeAllowanceMs = PcConstants.DEFAULT_LAUNCH_TIME_ALLOWANCE_MS;
 
+    /** Called by Prefetch Controller after local cache has been updated */
+    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 nowElapsed);
+    }
+
     @SuppressWarnings("FieldCanBeLocal")
     private final EstimatedLaunchTimeChangedListener mEstimatedLaunchTimeChangedListener =
             new EstimatedLaunchTimeChangedListener() {
@@ -291,12 +300,18 @@
                 // Don't bother caching the value unless the app has scheduled prefetch jobs
                 // before. This is based on the assumption that if an app has scheduled a
                 // prefetch job before, then it will probably schedule another one again.
+                final long prevEstimatedLaunchTime = mEstimatedLaunchTimes.get(userId, pkgName);
                 mEstimatedLaunchTimes.add(userId, pkgName, newEstimatedLaunchTime);
 
                 if (!jobs.isEmpty()) {
                     final long now = sSystemClock.millis();
                     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, nowElapsed);
+                    }
                     if (maybeUpdateConstraintForPkgLocked(now, nowElapsed, userId, pkgName)) {
                         mStateChangedListener.onControllerStateChanged(jobs);
                     }
@@ -448,6 +463,18 @@
         }
     }
 
+    void registerPrefetchChangedListener(PrefetchChangedListener listener) {
+        synchronized (mLock) {
+            mPrefetchChangedListeners.add(listener);
+        }
+    }
+
+    void unRegisterPrefetchChangedListener(PrefetchChangedListener listener) {
+        synchronized (mLock) {
+            mPrefetchChangedListeners.remove(listener);
+        }
+    }
+
     private class PcHandler extends Handler {
         PcHandler(Looper looper) {
             super(looper);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index 8b8a57d..4fe021a 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -19,6 +19,8 @@
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 
 import static com.android.server.tare.EconomicPolicy.REGULATION_BASIC_INCOME;
+import static com.android.server.tare.EconomicPolicy.REGULATION_BG_RESTRICTED;
+import static com.android.server.tare.EconomicPolicy.REGULATION_BG_UNRESTRICTED;
 import static com.android.server.tare.EconomicPolicy.REGULATION_BIRTHRIGHT;
 import static com.android.server.tare.EconomicPolicy.REGULATION_DEMOTION;
 import static com.android.server.tare.EconomicPolicy.REGULATION_PROMOTION;
@@ -34,8 +36,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -46,6 +46,7 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArrayMap;
+import android.util.SparseSetArray;
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.GuardedBy;
@@ -284,6 +285,7 @@
 
         for (int i = 0; i < pkgNames.size(); ++i) {
             final String pkgName = pkgNames.valueAt(i);
+            final boolean isVip = mIrs.isVip(userId, pkgName);
             SparseArrayMap<String, OngoingEvent> ongoingEvents =
                     mCurrentOngoingEvents.get(userId, pkgName);
             if (ongoingEvents != null) {
@@ -298,8 +300,8 @@
                     for (int n = 0; n < size; ++n) {
                         final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
                         note.recalculateCosts(economicPolicy, userId, pkgName);
-                        final boolean isAffordable =
-                                isAffordableLocked(newBalance,
+                        final boolean isAffordable = isVip
+                                || isAffordableLocked(newBalance,
                                         note.getCachedModifiedPrice(), note.getCtp());
                         if (note.isCurrentlyAffordable() != isAffordable) {
                             note.setNewAffordability(isAffordable);
@@ -313,6 +315,51 @@
     }
 
     @GuardedBy("mLock")
+    void onVipStatusChangedLocked(final int userId, @NonNull String pkgName) {
+        final long now = getCurrentTimeMillis();
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
+
+        final boolean isVip = mIrs.isVip(userId, pkgName);
+        SparseArrayMap<String, OngoingEvent> ongoingEvents =
+                mCurrentOngoingEvents.get(userId, pkgName);
+        if (ongoingEvents != null) {
+            mOngoingEventUpdater.reset(userId, pkgName, now, nowElapsed);
+            ongoingEvents.forEach(mOngoingEventUpdater);
+        }
+        final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
+                mActionAffordabilityNotes.get(userId, pkgName);
+        if (actionAffordabilityNotes != null) {
+            final int size = actionAffordabilityNotes.size();
+            final long newBalance =
+                    mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
+            for (int n = 0; n < size; ++n) {
+                final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
+                note.recalculateCosts(economicPolicy, userId, pkgName);
+                final boolean isAffordable = isVip
+                        || isAffordableLocked(newBalance,
+                        note.getCachedModifiedPrice(), note.getCtp());
+                if (note.isCurrentlyAffordable() != isAffordable) {
+                    note.setNewAffordability(isAffordable);
+                    mIrs.postAffordabilityChanged(userId, pkgName, note);
+                }
+            }
+        }
+        scheduleBalanceCheckLocked(userId, pkgName);
+    }
+
+    @GuardedBy("mLock")
+    void onVipStatusChangedLocked(@NonNull SparseSetArray<String> pkgs) {
+        for (int u = pkgs.size() - 1; u >= 0; --u) {
+            final int userId = pkgs.keyAt(u);
+
+            for (int p = pkgs.sizeAt(u) - 1; p >= 0; --p) {
+                onVipStatusChangedLocked(userId, pkgs.valueAt(u, p));
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
     private void onAnythingChangedLocked(final boolean updateOngoingEvents) {
         final long now = getCurrentTimeMillis();
         final long nowElapsed = SystemClock.elapsedRealtime();
@@ -349,11 +396,12 @@
                 if (actionAffordabilityNotes != null) {
                     final int size = actionAffordabilityNotes.size();
                     final long newBalance = getBalanceLocked(userId, pkgName);
+                    final boolean isVip = mIrs.isVip(userId, pkgName);
                     for (int n = 0; n < size; ++n) {
                         final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
                         note.recalculateCosts(economicPolicy, userId, pkgName);
-                        final boolean isAffordable =
-                                isAffordableLocked(newBalance,
+                        final boolean isAffordable = isVip
+                                || isAffordableLocked(newBalance,
                                         note.getCachedModifiedPrice(), note.getCtp());
                         if (note.isCurrentlyAffordable() != isAffordable) {
                             note.setNewAffordability(isAffordable);
@@ -454,14 +502,22 @@
                     "Tried to adjust system balance for " + appToString(userId, pkgName));
             return;
         }
+        if (mIrs.isVip(userId, pkgName)) {
+            // This could happen if the app was made a VIP after it started performing actions.
+            // Continue recording the transaction for debugging purposes, but don't let it change
+            // any numbers.
+            transaction = new Ledger.Transaction(
+                    transaction.startTimeMs, transaction.endTimeMs,
+                    transaction.eventId, transaction.tag, 0 /* delta */, transaction.ctp);
+        }
         final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
         final long originalBalance = ledger.getCurrentBalance();
+        final long maxBalance = economicPolicy.getMaxSatiatedBalance(userId, pkgName);
         if (transaction.delta > 0
-                && originalBalance + transaction.delta > economicPolicy.getMaxSatiatedBalance()) {
+                && originalBalance + transaction.delta > maxBalance) {
             // Set lower bound at 0 so we don't accidentally take away credits when we were trying
             // to _give_ the app credits.
-            final long newDelta =
-                    Math.max(0, economicPolicy.getMaxSatiatedBalance() - originalBalance);
+            final long newDelta = Math.max(0, maxBalance - originalBalance);
             Slog.i(TAG, "Would result in becoming too rich. Decreasing transaction "
                     + eventToString(transaction.eventId)
                     + (transaction.tag == null ? "" : ":" + transaction.tag)
@@ -479,10 +535,11 @@
                     mActionAffordabilityNotes.get(userId, pkgName);
             if (actionAffordabilityNotes != null) {
                 final long newBalance = ledger.getCurrentBalance();
+                final boolean isVip = mIrs.isVip(userId, pkgName);
                 for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
                     final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
-                    final boolean isAffordable =
-                            isAffordableLocked(newBalance,
+                    final boolean isAffordable = isVip
+                            || isAffordableLocked(newBalance,
                                     note.getCachedModifiedPrice(), note.getCtp());
                     if (note.isCurrentlyAffordable() != isAffordable) {
                         note.setNewAffordability(isAffordable);
@@ -605,16 +662,57 @@
         }
     }
 
+    /**
+     * Reclaim all ARCs from an app that was just restricted.
+     */
+    @GuardedBy("mLock")
+    void onAppRestrictedLocked(final int userId, @NonNull final String pkgName) {
+        final long curBalance = getBalanceLocked(userId, pkgName);
+        final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName);
+        if (curBalance <= minBalance) {
+            return;
+        }
+        if (DEBUG) {
+            Slog.i(TAG, "App restricted! Taking " + curBalance
+                    + " from " + appToString(userId, pkgName));
+        }
+
+        final long now = getCurrentTimeMillis();
+        final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
+        recordTransactionLocked(userId, pkgName, ledger,
+                new Ledger.Transaction(now, now, REGULATION_BG_RESTRICTED, null, -curBalance, 0),
+                true);
+    }
+
+    /**
+     * Give an app that was just unrestricted some ARCs.
+     */
+    @GuardedBy("mLock")
+    void onAppUnrestrictedLocked(final int userId, @NonNull final String pkgName) {
+        final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
+        if (ledger.getCurrentBalance() > 0) {
+            Slog.wtf(TAG, "App " + pkgName + " had credits while it was restricted");
+            // App already got credits somehow. Move along.
+            return;
+        }
+
+        final long now = getCurrentTimeMillis();
+
+        recordTransactionLocked(userId, pkgName, ledger,
+                new Ledger.Transaction(now, now, REGULATION_BG_UNRESTRICTED, null,
+                        mIrs.getMinBalanceLocked(userId, pkgName), 0), true);
+    }
+
     /** Returns true if an app should be given credits in the general distributions. */
-    private boolean shouldGiveCredits(@NonNull PackageInfo packageInfo) {
-        final ApplicationInfo applicationInfo = packageInfo.applicationInfo;
+    private boolean shouldGiveCredits(@NonNull InstalledPackageInfo packageInfo) {
         // Skip apps that wouldn't be doing any work. Giving them ARCs would be wasteful.
-        if (applicationInfo == null || !applicationInfo.hasCode()) {
+        if (!packageInfo.hasCode) {
             return false;
         }
-        final int userId = UserHandle.getUserId(packageInfo.applicationInfo.uid);
+        final int userId = UserHandle.getUserId(packageInfo.uid);
         // No point allocating ARCs to the system. It can do whatever it wants.
-        return !mIrs.isSystem(userId, packageInfo.packageName);
+        return !mIrs.isSystem(userId, packageInfo.packageName)
+                && !mIrs.isPackageRestricted(userId, packageInfo.packageName);
     }
 
     void onCreditSupplyChanged() {
@@ -623,25 +721,28 @@
 
     @GuardedBy("mLock")
     void distributeBasicIncomeLocked(int batteryLevel) {
-        List<PackageInfo> pkgs = mIrs.getInstalledPackages();
+        final SparseArrayMap<String, InstalledPackageInfo> pkgs = mIrs.getInstalledPackages();
 
         final long now = getCurrentTimeMillis();
-        for (int i = 0; i < pkgs.size(); ++i) {
-            final PackageInfo pkgInfo = pkgs.get(i);
-            if (!shouldGiveCredits(pkgInfo)) {
-                continue;
-            }
-            final int userId = UserHandle.getUserId(pkgInfo.applicationInfo.uid);
-            final String pkgName = pkgInfo.packageName;
-            final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
-            final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName);
-            final double perc = batteryLevel / 100d;
-            // TODO: maybe don't give credits to bankrupt apps until battery level >= 50%
-            final long shortfall = minBalance - ledger.getCurrentBalance();
-            if (shortfall > 0) {
-                recordTransactionLocked(userId, pkgName, ledger,
-                        new Ledger.Transaction(now, now, REGULATION_BASIC_INCOME,
-                                null, (long) (perc * shortfall), 0), true);
+        for (int uIdx = pkgs.numMaps() - 1; uIdx >= 0; --uIdx) {
+            final int userId = pkgs.keyAt(uIdx);
+
+            for (int pIdx = pkgs.numElementsForKeyAt(uIdx) - 1; pIdx >= 0; --pIdx) {
+                final InstalledPackageInfo pkgInfo = pkgs.valueAt(uIdx, pIdx);
+                if (!shouldGiveCredits(pkgInfo)) {
+                    continue;
+                }
+                final String pkgName = pkgInfo.packageName;
+                final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
+                final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName);
+                final double perc = batteryLevel / 100d;
+                // TODO: maybe don't give credits to bankrupt apps until battery level >= 50%
+                final long shortfall = minBalance - ledger.getCurrentBalance();
+                if (shortfall > 0) {
+                    recordTransactionLocked(userId, pkgName, ledger,
+                            new Ledger.Transaction(now, now, REGULATION_BASIC_INCOME,
+                                    null, (long) (perc * shortfall), 0), true);
+                }
             }
         }
     }
@@ -659,11 +760,11 @@
 
     @GuardedBy("mLock")
     void grantBirthrightsLocked(final int userId) {
-        final List<PackageInfo> pkgs = mIrs.getInstalledPackages(userId);
+        final List<InstalledPackageInfo> pkgs = mIrs.getInstalledPackages(userId);
         final long now = getCurrentTimeMillis();
 
         for (int i = 0; i < pkgs.size(); ++i) {
-            final PackageInfo packageInfo = pkgs.get(i);
+            final InstalledPackageInfo packageInfo = pkgs.get(i);
             if (!shouldGiveCredits(packageInfo)) {
                 continue;
             }
@@ -869,7 +970,7 @@
     private void scheduleBalanceCheckLocked(final int userId, @NonNull final String pkgName) {
         SparseArrayMap<String, OngoingEvent> ongoingEvents =
                 mCurrentOngoingEvents.get(userId, pkgName);
-        if (ongoingEvents == null) {
+        if (ongoingEvents == null || mIrs.isVip(userId, pkgName)) {
             // No ongoing transactions. No reason to schedule
             mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
             return;
@@ -1062,9 +1163,10 @@
                 note.setNewAffordability(true);
                 return;
             }
+            final boolean isVip = mIrs.isVip(userId, pkgName);
             note.recalculateCosts(economicPolicy, userId, pkgName);
-            note.setNewAffordability(
-                    isAffordableLocked(getBalanceLocked(userId, pkgName),
+            note.setNewAffordability(isVip
+                    || isAffordableLocked(getBalanceLocked(userId, pkgName),
                             note.getCachedModifiedPrice(), note.getCtp()));
             mIrs.postAffordabilityChanged(userId, pkgName, note);
             // Update ongoing alarm
@@ -1203,11 +1305,12 @@
                         if (actionAffordabilityNotes != null
                                 && actionAffordabilityNotes.size() > 0) {
                             final long newBalance = getBalanceLocked(userId, pkgName);
+                            final boolean isVip = mIrs.isVip(userId, pkgName);
 
                             for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
                                 final ActionAffordabilityNote note =
                                         actionAffordabilityNotes.valueAt(i);
-                                final boolean isAffordable = isAffordableLocked(
+                                final boolean isAffordable = isVip || isAffordableLocked(
                                         newBalance, note.getCachedModifiedPrice(), note.getCtp());
                                 if (note.isCurrentlyAffordable() != isAffordable) {
                                     note.setNewAffordability(isAffordable);
@@ -1225,7 +1328,6 @@
 
     @GuardedBy("mLock")
     void dumpLocked(IndentingPrintWriter pw) {
-        pw.println();
         mBalanceThresholdAlarmQueue.dump(pw);
 
         pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
index a46430f..e791e98 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
@@ -149,7 +149,6 @@
     private long mHardSatiatedConsumptionLimit;
 
     private final KeyValueListParser mParser = new KeyValueListParser(',');
-    private final InternalResourceService mInternalResourceService;
     private final Injector mInjector;
 
     private final SparseArray<Action> mActions = new SparseArray<>();
@@ -157,7 +156,6 @@
 
     AlarmManagerEconomicPolicy(InternalResourceService irs, Injector injector) {
         super(irs);
-        mInternalResourceService = irs;
         mInjector = injector;
         loadConstants("", null);
     }
@@ -165,14 +163,17 @@
     @Override
     void setup(@NonNull DeviceConfig.Properties properties) {
         super.setup(properties);
-        ContentResolver resolver = mInternalResourceService.getContext().getContentResolver();
+        ContentResolver resolver = mIrs.getContext().getContentResolver();
         loadConstants(mInjector.getSettingsGlobalString(resolver, TARE_ALARM_MANAGER_CONSTANTS),
                 properties);
     }
 
     @Override
     long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) {
-        if (mInternalResourceService.isPackageExempted(userId, pkgName)) {
+        if (mIrs.isPackageRestricted(userId, pkgName)) {
+            return 0;
+        }
+        if (mIrs.isPackageExempted(userId, pkgName)) {
             return mMinSatiatedBalanceExempted;
         }
         // TODO: take other exemptions into account
@@ -180,7 +181,10 @@
     }
 
     @Override
-    long getMaxSatiatedBalance() {
+    long getMaxSatiatedBalance(int userId, @NonNull String pkgName) {
+        if (mIrs.isPackageRestricted(userId, pkgName)) {
+            return 0;
+        }
         // TODO(230501287): adjust balance based on whether the app has the SCHEDULE_EXACT_ALARM
         // permission granted. Apps without the permission granted shouldn't need a high balance
         // since they won't be able to use exact alarms. Apps with the permission granted could
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
index 5d9cce8..625f99d 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
@@ -36,7 +36,6 @@
     /** Lazily populated set of rewards covered by this policy. */
     private final SparseArray<Reward> mRewards = new SparseArray<>();
     private final int[] mCostModifiers;
-    private long mMaxSatiatedBalance;
     private long mInitialConsumptionLimit;
     private long mHardConsumptionLimit;
 
@@ -80,16 +79,13 @@
     }
 
     private void updateLimits() {
-        long maxSatiatedBalance = 0;
         long initialConsumptionLimit = 0;
         long hardConsumptionLimit = 0;
         for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
             final EconomicPolicy economicPolicy = mEnabledEconomicPolicies.valueAt(i);
-            maxSatiatedBalance += economicPolicy.getMaxSatiatedBalance();
             initialConsumptionLimit += economicPolicy.getInitialSatiatedConsumptionLimit();
             hardConsumptionLimit += economicPolicy.getHardSatiatedConsumptionLimit();
         }
-        mMaxSatiatedBalance = maxSatiatedBalance;
         mInitialConsumptionLimit = initialConsumptionLimit;
         mHardConsumptionLimit = hardConsumptionLimit;
     }
@@ -104,8 +100,12 @@
     }
 
     @Override
-    long getMaxSatiatedBalance() {
-        return mMaxSatiatedBalance;
+    long getMaxSatiatedBalance(int userId, @NonNull String pkgName) {
+        long max = 0;
+        for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
+            max += mEnabledEconomicPolicies.valueAt(i).getMaxSatiatedBalance(userId, pkgName);
+        }
+        return max;
     }
 
     @Override
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
index 0937e7b..22a2f51 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
@@ -67,6 +67,9 @@
     static final int REGULATION_WEALTH_RECLAMATION = TYPE_REGULATION | 2;
     static final int REGULATION_PROMOTION = TYPE_REGULATION | 3;
     static final int REGULATION_DEMOTION = TYPE_REGULATION | 4;
+    /** App is fully restricted from running in the background. */
+    static final int REGULATION_BG_RESTRICTED = TYPE_REGULATION | 5;
+    static final int REGULATION_BG_UNRESTRICTED = TYPE_REGULATION | 6;
 
     static final int REWARD_NOTIFICATION_SEEN = TYPE_REWARD | 0;
     static final int REWARD_NOTIFICATION_INTERACTION = TYPE_REWARD | 1;
@@ -169,9 +172,11 @@
         }
     }
 
+    protected final InternalResourceService mIrs;
     private static final Modifier[] COST_MODIFIER_BY_INDEX = new Modifier[NUM_COST_MODIFIERS];
 
     EconomicPolicy(@NonNull InternalResourceService irs) {
+        mIrs = irs;
         for (int mId : getCostModifiers()) {
             initModifier(mId, irs);
         }
@@ -208,7 +213,7 @@
      * exists to ensure that no single app accumulate all available resources and increases fairness
      * for all apps.
      */
-    abstract long getMaxSatiatedBalance();
+    abstract long getMaxSatiatedBalance(int userId, @NonNull String pkgName);
 
     /**
      * Returns the maximum number of cakes that should be consumed during a full 100% discharge
@@ -240,7 +245,7 @@
     @NonNull
     final Cost getCostOfAction(int actionId, int userId, @NonNull String pkgName) {
         final Action action = getAction(actionId);
-        if (action == null) {
+        if (action == null || mIrs.isVip(userId, pkgName)) {
             return new Cost(0, 0);
         }
         long ctp = action.costToProduce;
@@ -400,6 +405,10 @@
                 return "PROMOTION";
             case REGULATION_DEMOTION:
                 return "DEMOTION";
+            case REGULATION_BG_RESTRICTED:
+                return "BG_RESTRICTED";
+            case REGULATION_BG_UNRESTRICTED:
+                return "BG_UNRESTRICTED";
         }
         return "UNKNOWN_REGULATION:" + Integer.toHexString(eventId);
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
new file mode 100644
index 0000000..da544bb
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.tare;
+
+import android.annotation.NonNull;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.os.UserHandle;
+
+/** POJO to cache only the information about installed packages that TARE cares about. */
+class InstalledPackageInfo {
+    static final int NO_UID = -1;
+
+    public final int uid;
+    public final String packageName;
+    public final boolean hasCode;
+
+    InstalledPackageInfo(@NonNull PackageInfo packageInfo) {
+        final ApplicationInfo applicationInfo = packageInfo.applicationInfo;
+        this.uid = applicationInfo == null ? NO_UID : applicationInfo.uid;
+        this.packageName = packageInfo.packageName;
+        this.hasCode = applicationInfo != null && applicationInfo.hasCode();
+    }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 6d5c160..448a808 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -29,6 +29,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AlarmManager;
+import android.app.AppOpsManager;
 import android.app.tare.IEconomyManager;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
@@ -48,6 +49,7 @@
 import android.os.IDeviceIdleController;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ParcelFileDescriptor;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -63,6 +65,8 @@
 import android.util.SparseSetArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
@@ -118,6 +122,7 @@
     private final PackageManager mPackageManager;
     private final PackageManagerInternal mPackageManagerInternal;
 
+    private IAppOpsService mAppOpsService;
     private IDeviceIdleController mDeviceIdleController;
 
     private final Agent mAgent;
@@ -131,7 +136,7 @@
 
     @NonNull
     @GuardedBy("mLock")
-    private final List<PackageInfo> mPkgCache = new ArrayList<>();
+    private final SparseArrayMap<String, InstalledPackageInfo> mPkgCache = new SparseArrayMap<>();
 
     /** Cached mapping of UIDs (for all users) to a list of packages in the UID. */
     @GuardedBy("mLock")
@@ -144,11 +149,20 @@
     private final CopyOnWriteArraySet<TareStateChangeListener> mStateChangeListeners =
             new CopyOnWriteArraySet<>();
 
+    /**
+     * List of packages that are fully restricted and shouldn't be allowed to run in the background.
+     */
+    @GuardedBy("mLock")
+    private final SparseSetArray<String> mRestrictedApps = new SparseSetArray<>();
+
     /** List of packages that are "exempted" from battery restrictions. */
     // TODO(144864180): include userID
     @GuardedBy("mLock")
     private ArraySet<String> mExemptedApps = new ArraySet<>();
 
+    @GuardedBy("mLock")
+    private final SparseArrayMap<String, Boolean> mVipOverrides = new SparseArrayMap<>();
+
     private volatile boolean mIsEnabled;
     private volatile int mBootPhase;
     private volatile boolean mExemptListLoaded;
@@ -156,6 +170,30 @@
     @GuardedBy("mLock")
     private int mCurrentBatteryLevel;
 
+    private final IAppOpsCallback mApbListener = new IAppOpsCallback.Stub() {
+        @Override
+        public void opChanged(int op, int uid, String packageName) {
+            boolean restricted = false;
+            try {
+                restricted = mAppOpsService.checkOperation(
+                        AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName)
+                        != AppOpsManager.MODE_ALLOWED;
+            } catch (RemoteException e) {
+                // Shouldn't happen
+            }
+            final int userId = UserHandle.getUserId(uid);
+            synchronized (mLock) {
+                if (restricted) {
+                    if (mRestrictedApps.add(userId, packageName)) {
+                        mAgent.onAppRestrictedLocked(userId, packageName);
+                    }
+                } else if (mRestrictedApps.remove(UserHandle.getUserId(uid), packageName)) {
+                    mAgent.onAppUnrestrictedLocked(userId, packageName);
+                }
+            }
+        }
+    };
+
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Nullable
         private String getPackageName(Intent intent) {
@@ -276,9 +314,11 @@
 
         switch (phase) {
             case PHASE_SYSTEM_SERVICES_READY:
-                mConfigObserver.start();
+                mAppOpsService = IAppOpsService.Stub.asInterface(
+                        ServiceManager.getService(Context.APP_OPS_SERVICE));
                 mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
                         ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+                mConfigObserver.start();
                 onBootPhaseSystemServicesReady();
                 break;
             case PHASE_THIRD_PARTY_APPS_CAN_START:
@@ -303,7 +343,7 @@
     }
 
     @NonNull
-    List<PackageInfo> getInstalledPackages() {
+    SparseArrayMap<String, InstalledPackageInfo> getInstalledPackages() {
         synchronized (mLock) {
             return mPkgCache;
         }
@@ -311,15 +351,16 @@
 
     /** Returns the installed packages for the specified user. */
     @NonNull
-    List<PackageInfo> getInstalledPackages(final int userId) {
-        final List<PackageInfo> userPkgs = new ArrayList<>();
+    List<InstalledPackageInfo> getInstalledPackages(final int userId) {
+        final List<InstalledPackageInfo> userPkgs = new ArrayList<>();
         synchronized (mLock) {
-            for (int i = 0; i < mPkgCache.size(); ++i) {
-                final PackageInfo packageInfo = mPkgCache.get(i);
-                if (packageInfo.applicationInfo != null
-                        && UserHandle.getUserId(packageInfo.applicationInfo.uid) == userId) {
-                    userPkgs.add(packageInfo);
-                }
+            final int uIdx = mPkgCache.indexOfKey(userId);
+            if (uIdx < 0) {
+                return userPkgs;
+            }
+            for (int p = mPkgCache.numElementsForKeyAt(uIdx) - 1; p >= 0; --p) {
+                final InstalledPackageInfo packageInfo = mPkgCache.valueAt(uIdx, p);
+                userPkgs.add(packageInfo);
             }
         }
         return userPkgs;
@@ -362,6 +403,12 @@
         }
     }
 
+    boolean isPackageRestricted(final int userId, @NonNull String pkgName) {
+        synchronized (mLock) {
+            return mRestrictedApps.contains(userId, pkgName);
+        }
+    }
+
     boolean isSystem(final int userId, @NonNull String pkgName) {
         if ("android".equals(pkgName)) {
             return true;
@@ -369,6 +416,21 @@
         return UserHandle.isCore(getUid(userId, pkgName));
     }
 
+    boolean isVip(final int userId, @NonNull String pkgName) {
+        synchronized (mLock) {
+            final Boolean override = mVipOverrides.get(userId, pkgName);
+            if (override != null) {
+                return override;
+            }
+        }
+        if (isSystem(userId, pkgName)) {
+            // The government, I mean the system, can create ARCs as it needs to in order to
+            // operate.
+            return true;
+        }
+        return false;
+    }
+
     void onBatteryLevelChanged() {
         synchronized (mLock) {
             final int newBatteryLevel = getCurrentBatteryLevel();
@@ -451,7 +513,7 @@
             mPackageToUidCache.add(userId, pkgName, uid);
         }
         synchronized (mLock) {
-            mPkgCache.add(packageInfo);
+            mPkgCache.add(userId, pkgName, new InstalledPackageInfo(packageInfo));
             mUidToPackageCache.add(uid, pkgName);
             // TODO: only do this when the user first launches the app (app leaves stopped state)
             mAgent.grantBirthrightLocked(userId, pkgName);
@@ -471,14 +533,8 @@
         }
         synchronized (mLock) {
             mUidToPackageCache.remove(uid, pkgName);
-            for (int i = 0; i < mPkgCache.size(); ++i) {
-                PackageInfo pkgInfo = mPkgCache.get(i);
-                if (UserHandle.getUserId(pkgInfo.applicationInfo.uid) == userId
-                        && pkgName.equals(pkgInfo.packageName)) {
-                    mPkgCache.remove(i);
-                    break;
-                }
-            }
+            mVipOverrides.delete(userId, pkgName);
+            mPkgCache.delete(userId, pkgName);
             mAgent.onPackageRemovedLocked(userId, pkgName);
         }
     }
@@ -496,24 +552,29 @@
 
     void onUserAdded(final int userId) {
         synchronized (mLock) {
-            mPkgCache.addAll(
-                    mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId));
+            final List<PackageInfo> pkgs =
+                    mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
+            for (int i = pkgs.size() - 1; i >= 0; --i) {
+                final InstalledPackageInfo ipo = new InstalledPackageInfo(pkgs.get(i));
+                mPkgCache.add(userId, ipo.packageName, ipo);
+            }
             mAgent.grantBirthrightsLocked(userId);
         }
     }
 
     void onUserRemoved(final int userId) {
         synchronized (mLock) {
+            mVipOverrides.delete(userId);
             ArrayList<String> removedPkgs = new ArrayList<>();
-            for (int i = mPkgCache.size() - 1; i >= 0; --i) {
-                PackageInfo pkgInfo = mPkgCache.get(i);
-                if (UserHandle.getUserId(pkgInfo.applicationInfo.uid) == userId) {
+            final int uIdx = mPkgCache.indexOfKey(userId);
+            if (uIdx >= 0) {
+                for (int p = mPkgCache.numElementsForKeyAt(uIdx) - 1; p >= 0; --p) {
+                    final InstalledPackageInfo pkgInfo = mPkgCache.valueAt(uIdx, p);
                     removedPkgs.add(pkgInfo.packageName);
-                    mUidToPackageCache.remove(pkgInfo.applicationInfo.uid);
-                    mPkgCache.remove(i);
-                    break;
+                    mUidToPackageCache.remove(pkgInfo.uid);
                 }
             }
+            mPkgCache.delete(userId);
             mAgent.onUserRemovedLocked(userId, removedPkgs);
         }
     }
@@ -659,8 +720,12 @@
                 LocalServices.getService(UserManagerInternal.class);
         final int[] userIds = userManagerInternal.getUserIds();
         for (int userId : userIds) {
-            mPkgCache.addAll(
-                    mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId));
+            final List<PackageInfo> pkgs =
+                    mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
+            for (int i = pkgs.size() - 1; i >= 0; --i) {
+                final InstalledPackageInfo ipo = new InstalledPackageInfo(pkgs.get(i));
+                mPkgCache.add(userId, ipo.packageName, ipo);
+            }
         }
     }
 
@@ -685,6 +750,13 @@
 
         UsageStatsManagerInternal usmi = LocalServices.getService(UsageStatsManagerInternal.class);
         usmi.registerListener(mSurveillanceAgent);
+
+        try {
+            mAppOpsService
+                    .startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null, mApbListener);
+        } catch (RemoteException e) {
+            // shouldn't happen.
+        }
     }
 
     /** Perform long-running and/or heavy setup work. This should be called off the main thread. */
@@ -708,6 +780,9 @@
                     // Reset the consumption limit since several factors may have changed.
                     mScribe.setConsumptionLimitLocked(
                             mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
+                } else {
+                    // Adjust the supply in case battery level changed while the device was off.
+                    adjustCreditSupplyLocked(true);
                 }
             }
             scheduleUnusedWealthReclamationLocked();
@@ -789,6 +864,11 @@
             UsageStatsManagerInternal usmi =
                     LocalServices.getService(UsageStatsManagerInternal.class);
             usmi.unregisterListener(mSurveillanceAgent);
+            try {
+                mAppOpsService.stopWatchingMode(mApbListener);
+            } catch (RemoteException e) {
+                // shouldn't happen.
+            }
         }
         synchronized (mPackageToUidCache) {
             mPackageToUidCache.clear();
@@ -881,6 +961,15 @@
                 Binder.restoreCallingIdentity(identityToken);
             }
         }
+
+        @Override
+        public int handleShellCommand(@NonNull ParcelFileDescriptor in,
+                @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
+                @NonNull String[] args) {
+            return (new TareShellCommand(InternalResourceService.this)).exec(
+                    this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(),
+                    args);
+        }
     }
 
     private final class LocalService implements EconomyManagerInternal {
@@ -932,9 +1021,9 @@
             if (!mIsEnabled) {
                 return true;
             }
-            if (isSystem(userId, pkgName)) {
+            if (isVip(userId, pkgName)) {
                 // The government, I mean the system, can create ARCs as it needs to in order to
-                // operate.
+                // allow VIPs to operate.
                 return true;
             }
             // TODO: take temp-allowlist into consideration
@@ -960,7 +1049,7 @@
             if (!mIsEnabled) {
                 return FOREVER_MS;
             }
-            if (isSystem(userId, pkgName)) {
+            if (isVip(userId, pkgName)) {
                 return FOREVER_MS;
             }
             long totalCostPerSecond = 0;
@@ -1093,7 +1182,6 @@
             // User setting should override DeviceConfig setting.
             // NOTE: There's currently no way for a user to reset the value (via UI), so if a user
             // manually toggles TARE via UI, we'll always defer to the user's current setting
-            // TODO: add a "reset" value if the user toggle is an issue
             final boolean isTareEnabledDC = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TARE,
                     KEY_DC_ENABLE_TARE, Settings.Global.DEFAULT_ENABLE_TARE == 1);
             final boolean isTareEnabled = Settings.Global.getInt(mContentResolver,
@@ -1131,6 +1219,47 @@
         }
     }
 
+    // Shell command infrastructure
+    int executeClearVip(@NonNull PrintWriter pw) {
+        synchronized (mLock) {
+            final SparseSetArray<String> changedPkgs = new SparseSetArray<>();
+            for (int u = mVipOverrides.numMaps() - 1; u >= 0; --u) {
+                final int userId = mVipOverrides.keyAt(u);
+
+                for (int p = mVipOverrides.numElementsForKeyAt(u) - 1; p >= 0; --p) {
+                    changedPkgs.add(userId, mVipOverrides.keyAt(u, p));
+                }
+            }
+            mVipOverrides.clear();
+            if (mIsEnabled) {
+                mAgent.onVipStatusChangedLocked(changedPkgs);
+            }
+        }
+        pw.println("Cleared all VIP statuses");
+        return TareShellCommand.COMMAND_SUCCESS;
+    }
+
+    int executeSetVip(@NonNull PrintWriter pw,
+            int userId, @NonNull String pkgName, @Nullable Boolean newVipState) {
+        final boolean changed;
+        synchronized (mLock) {
+            final boolean wasVip = isVip(userId, pkgName);
+            if (newVipState == null) {
+                mVipOverrides.delete(userId, pkgName);
+            } else {
+                mVipOverrides.add(userId, pkgName, newVipState);
+            }
+            changed = isVip(userId, pkgName) != wasVip;
+            if (mIsEnabled && changed) {
+                mAgent.onVipStatusChangedLocked(userId, pkgName);
+            }
+        }
+        pw.println(appToString(userId, pkgName) + " VIP status set to " + newVipState + "."
+                + " Final VIP state changed? " + changed);
+        return TareShellCommand.COMMAND_SUCCESS;
+    }
+
+    // Dump infrastructure
     private static void dumpHelp(PrintWriter pw) {
         pw.println("Resource Economy (economy) dump options:");
         pw.println("  [-h|--help] [package] ...");
@@ -1168,6 +1297,29 @@
             pw.print("Exempted apps", mExemptedApps);
             pw.println();
 
+            boolean printedVips = false;
+            pw.println();
+            pw.print("VIPs:");
+            for (int u = 0; u < mVipOverrides.numMaps(); ++u) {
+                final int userId = mVipOverrides.keyAt(u);
+
+                for (int p = 0; p < mVipOverrides.numElementsForKeyAt(u); ++p) {
+                    final String pkgName = mVipOverrides.keyAt(u, p);
+
+                    printedVips = true;
+                    pw.println();
+                    pw.print(appToString(userId, pkgName));
+                    pw.print("=");
+                    pw.print(mVipOverrides.valueAt(u, p));
+                }
+            }
+            if (printedVips) {
+                pw.println();
+            } else {
+                pw.print(" None");
+            }
+            pw.println();
+
             pw.println();
             mCompleteEconomicPolicy.dump(pw);
 
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index e7db1ad..cbb88c0 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -151,7 +151,6 @@
     private long mHardSatiatedConsumptionLimit;
 
     private final KeyValueListParser mParser = new KeyValueListParser(',');
-    private final InternalResourceService mInternalResourceService;
     private final Injector mInjector;
 
     private final SparseArray<Action> mActions = new SparseArray<>();
@@ -159,7 +158,6 @@
 
     JobSchedulerEconomicPolicy(InternalResourceService irs, Injector injector) {
         super(irs);
-        mInternalResourceService = irs;
         mInjector = injector;
         loadConstants("", null);
     }
@@ -167,14 +165,17 @@
     @Override
     void setup(@NonNull DeviceConfig.Properties properties) {
         super.setup(properties);
-        ContentResolver resolver = mInternalResourceService.getContext().getContentResolver();
+        final ContentResolver resolver = mIrs.getContext().getContentResolver();
         loadConstants(mInjector.getSettingsGlobalString(resolver, TARE_JOB_SCHEDULER_CONSTANTS),
                 properties);
     }
 
     @Override
     long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) {
-        if (mInternalResourceService.isPackageExempted(userId, pkgName)) {
+        if (mIrs.isPackageRestricted(userId, pkgName)) {
+            return 0;
+        }
+        if (mIrs.isPackageExempted(userId, pkgName)) {
             return mMinSatiatedBalanceExempted;
         }
         // TODO: take other exemptions into account
@@ -182,7 +183,10 @@
     }
 
     @Override
-    long getMaxSatiatedBalance() {
+    long getMaxSatiatedBalance(int userId, @NonNull String pkgName) {
+        if (mIrs.isPackageRestricted(userId, pkgName)) {
+            return 0;
+        }
         return mMaxSatiatedBalance;
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
index e91ed12..620d1a0d 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
@@ -247,7 +247,7 @@
 
         boolean printedTransactionTitle = false;
         for (int t = 0; t < Math.min(MAX_TRANSACTION_COUNT, numRecentTransactions); ++t) {
-            final int idx = (mTransactionIndex - t + MAX_TRANSACTION_COUNT) % MAX_TRANSACTION_COUNT;
+            final int idx = (mTransactionIndex + t) % MAX_TRANSACTION_COUNT;
             final Transaction transaction = mTransactions[idx];
             if (transaction == null) {
                 continue;
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
index 8f7657e..29478d0 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -19,10 +19,10 @@
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 
 import static com.android.server.tare.TareUtils.appToString;
+import static com.android.server.tare.TareUtils.cakeToString;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.pm.PackageInfo;
 import android.os.Environment;
 import android.os.UserHandle;
 import android.util.ArraySet;
@@ -137,9 +137,15 @@
 
     @GuardedBy("mIrs.getLock()")
     void adjustRemainingConsumableCakesLocked(long delta) {
-        if (delta != 0) {
-            // No point doing any work if the change is 0.
-            mRemainingConsumableCakes += delta;
+        final long staleCakes = mRemainingConsumableCakes;
+        mRemainingConsumableCakes += delta;
+        if (mRemainingConsumableCakes < 0) {
+            Slog.w(TAG, "Overdrew consumable cakes by " + cakeToString(-mRemainingConsumableCakes));
+            // A negative value would interfere with allowing free actions, so set the minimum as 0.
+            mRemainingConsumableCakes = 0;
+        }
+        if (mRemainingConsumableCakes != staleCakes) {
+            // No point doing any work if there was no functional change.
             postWrite();
         }
     }
@@ -210,17 +216,21 @@
         mRemainingConsumableCakes = 0;
 
         final SparseArray<ArraySet<String>> installedPackagesPerUser = new SparseArray<>();
-        final List<PackageInfo> installedPackages = mIrs.getInstalledPackages();
-        for (int i = 0; i < installedPackages.size(); ++i) {
-            final PackageInfo packageInfo = installedPackages.get(i);
-            if (packageInfo.applicationInfo != null) {
-                final int userId = UserHandle.getUserId(packageInfo.applicationInfo.uid);
-                ArraySet<String> pkgsForUser = installedPackagesPerUser.get(userId);
-                if (pkgsForUser == null) {
-                    pkgsForUser = new ArraySet<>();
-                    installedPackagesPerUser.put(userId, pkgsForUser);
+        final SparseArrayMap<String, InstalledPackageInfo> installedPackages =
+                mIrs.getInstalledPackages();
+        for (int uIdx = installedPackages.numMaps() - 1; uIdx >= 0; --uIdx) {
+            final int userId = installedPackages.keyAt(uIdx);
+
+            for (int pIdx = installedPackages.numElementsForKeyAt(uIdx) - 1; pIdx >= 0; --pIdx) {
+                final InstalledPackageInfo packageInfo = installedPackages.valueAt(uIdx, pIdx);
+                if (packageInfo.uid != InstalledPackageInfo.NO_UID) {
+                    ArraySet<String> pkgsForUser = installedPackagesPerUser.get(userId);
+                    if (pkgsForUser == null) {
+                        pkgsForUser = new ArraySet<>();
+                        installedPackagesPerUser.put(userId, pkgsForUser);
+                    }
+                    pkgsForUser.add(packageInfo.packageName);
                 }
-                pkgsForUser.add(packageInfo.packageName);
             }
         }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/TareShellCommand.java b/apex/jobscheduler/service/java/com/android/server/tare/TareShellCommand.java
new file mode 100644
index 0000000..5e380b40
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/tare/TareShellCommand.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.tare;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+
+import com.android.modules.utils.BasicShellCommandHandler;
+
+import java.io.PrintWriter;
+
+/**
+ * Shell command handler for TARE.
+ */
+public class TareShellCommand extends BasicShellCommandHandler {
+    static final int COMMAND_ERROR = -1;
+    static final int COMMAND_SUCCESS = 0;
+
+    private final InternalResourceService mIrs;
+
+    public TareShellCommand(@NonNull InternalResourceService irs) {
+        mIrs = irs;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        final PrintWriter pw = getOutPrintWriter();
+        try {
+            switch (cmd != null ? cmd : "") {
+                case "clear-vip":
+                    return runClearVip(pw);
+                case "set-vip":
+                    return runSetVip(pw);
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        } catch (Exception e) {
+            pw.println("Exception: " + e);
+        }
+        return COMMAND_ERROR;
+    }
+
+    @Override
+    public void onHelp() {
+        final PrintWriter pw = getOutPrintWriter();
+
+        pw.println("TARE commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println("  clear-vip");
+        pw.println("    Clears all VIP settings resulting from previous calls using `set-vip` and");
+        pw.println("    resets them all to default.");
+        pw.println("  set-vip <USER_ID> <PACKAGE> <true|false|default>");
+        pw.println("    Designate the app as a Very Important Package or not. A VIP is allowed to");
+        pw.println("    do as much work as it wants, regardless of TARE state.");
+        pw.println("    The user ID must be an explicit user ID. USER_ALL, CURRENT, etc. are not");
+        pw.println("    supported.");
+        pw.println();
+    }
+
+    private void checkPermission(@NonNull String operation) throws Exception {
+        final int perm = mIrs.getContext()
+                .checkCallingOrSelfPermission(Manifest.permission.CHANGE_APP_IDLE_STATE);
+        if (perm != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Uid " + Binder.getCallingUid()
+                    + " not permitted to " + operation);
+        }
+    }
+
+    private int runClearVip(@NonNull PrintWriter pw) throws Exception {
+        checkPermission("clear vip");
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return mIrs.executeClearVip(pw);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private int runSetVip(@NonNull PrintWriter pw) throws Exception {
+        checkPermission("modify vip");
+
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final String pkgName = getNextArgRequired();
+        final String vipState = getNextArgRequired();
+        final Boolean isVip = "default".equals(vipState) ? null : Boolean.valueOf(vipState);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return mIrs.executeSetVip(pw, userId, pkgName, isVip);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+}
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 65916d7..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_;
@@ -66,18 +68,21 @@
 
  private:
   struct SerializedData {
-    std::unique_ptr<uint8_t[]> data;
-    size_t data_size;
-    uint32_t crc;
-  };
+    std::unique_ptr<uint8_t[]> pb_data;
+    size_t pb_data_size;
+    uint32_t pb_crc;
+    std::string sp_data;
+   };
 
   Result<SerializedData*> InitializeData() const;
   Result<uint32_t> GetCrc() const;
 
   explicit FabricatedOverlay(pb::FabricatedOverlay&& overlay,
+                             std::string&& string_pool_data_,
                              std::optional<uint32_t> crc_from_disk = {});
 
   pb::FabricatedOverlay overlay_pb_;
+  std::string string_pool_data_;
   std::optional<uint32_t> crc_from_disk_;
   mutable std::optional<SerializedData> data_;
 
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 4d49674..bde9b0b 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -17,8 +17,9 @@
 #include "idmap2/FabricatedOverlay.h"
 
 #include <androidfw/ResourceUtils.h>
+#include <androidfw/StringPool.h>
 #include <google/protobuf/io/coded_stream.h>
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <google/protobuf/io/zero_copy_stream_impl.h>
 #include <utils/ByteOrder.h>
 #include <zlib.h>
 
@@ -30,6 +31,8 @@
 
 namespace android::idmap2 {
 
+constexpr auto kBufferSize = 1024;
+
 namespace {
 bool Read32(std::istream& stream, uint32_t* out) {
   uint32_t value;
@@ -47,8 +50,11 @@
 }  // namespace
 
 FabricatedOverlay::FabricatedOverlay(pb::FabricatedOverlay&& overlay,
+                                     std::string&& string_pool_data,
                                      std::optional<uint32_t> crc_from_disk)
-    : overlay_pb_(std::forward<pb::FabricatedOverlay>(overlay)), crc_from_disk_(crc_from_disk) {
+    : overlay_pb_(std::forward<pb::FabricatedOverlay>(overlay)),
+    string_pool_data_(std::move(string_pool_data)),
+    crc_from_disk_(crc_from_disk) {
 }
 
 FabricatedOverlay::Builder::Builder(const std::string& package_name, const std::string& name,
@@ -64,19 +70,26 @@
 }
 
 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() {
-  std::map<std::string, std::map<std::string, std::map<std::string, TargetValue>>> entries;
+  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;
+  android::StringPool string_pool;
   for (const auto& res_entry : entries_) {
     StringPiece package_substr;
     StringPiece type_name;
@@ -96,11 +109,10 @@
       return Error("resource name '%s' missing entry name", res_entry.resource_name.c_str());
     }
 
-    auto package = entries.find(package_name);
-    if (package == entries.end()) {
-      package = entries
-                    .insert(std::make_pair(
-                        package_name, std::map<std::string, std::map<std::string, TargetValue>>()))
+    auto package = package_map.find(package_name);
+    if (package == package_map.end()) {
+      package = package_map
+                    .insert(std::make_pair(package_name, TypeMap()))
                     .first;
     }
 
@@ -108,17 +120,22 @@
     if (type == package->second.end()) {
       type =
           package->second
-              .insert(std::make_pair(type_name.to_string(), std::map<std::string, TargetValue>()))
+              .insert(std::make_pair(type_name.to_string(), EntryMap()))
               .first;
     }
 
     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;
@@ -127,25 +144,35 @@
   overlay_pb.set_target_package_name(target_package_name_);
   overlay_pb.set_target_overlayable(target_overlayable_);
 
-  for (const auto& package : entries) {
+  for (auto& package : package_map) {
     auto package_pb = overlay_pb.add_packages();
     package_pb->set_name(package.first);
 
-    for (const auto& type : package.second) {
+    for (auto& type : package.second) {
       auto type_pb = package_pb->add_types();
       type_pb->set_name(type.first);
 
-      for (const 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);
-        value->set_data_value(entry.second.data_value);
+      for (auto& entry : type.second) {
+        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);
+          }
+        }
       }
     }
   }
 
-  return FabricatedOverlay(std::move(overlay_pb));
+  android::BigBuffer string_buffer(kBufferSize);
+  android::StringPool::FlattenUtf8(&string_buffer, string_pool, nullptr);
+  return FabricatedOverlay(std::move(overlay_pb), string_buffer.to_string());
 }
 
 Result<FabricatedOverlay> FabricatedOverlay::FromBinaryStream(std::istream& stream) {
@@ -163,48 +190,67 @@
     return Error("Failed to read fabricated overlay version.");
   }
 
-  if (version != 1) {
+  if (version != 1 && version != 2) {
     return Error("Invalid fabricated overlay version '%u'.", version);
   }
 
   uint32_t crc;
   if (!Read32(stream, &crc)) {
-    return Error("Failed to read fabricated overlay version.");
+    return Error("Failed to read fabricated overlay crc.");
   }
 
   pb::FabricatedOverlay overlay{};
-  if (!overlay.ParseFromIstream(&stream)) {
-    return Error("Failed read fabricated overlay proto.");
+  std::string sp_data;
+  if (version == 2) {
+    uint32_t sp_size;
+    if (!Read32(stream, &sp_size)) {
+      return Error("Failed read string pool size.");
+    }
+    std::string buf(sp_size, '\0');
+    if (!stream.read(buf.data(), sp_size)) {
+      return Error("Failed to read string pool.");
+    }
+    sp_data = buf;
+
+    if (!overlay.ParseFromIstream(&stream)) {
+      return Error("Failed read fabricated overlay proto.");
+    }
+  } else {
+    if (!overlay.ParseFromIstream(&stream)) {
+      return Error("Failed read fabricated overlay proto.");
+    }
   }
 
   // If the proto version is the latest version, then the contents of the proto must be the same
   // when the proto is re-serialized; otherwise, the crc must be calculated because migrating the
   // proto to the latest version will likely change the contents of the fabricated overlay.
-  return FabricatedOverlay(std::move(overlay), version == kFabricatedOverlayCurrentVersion
+  return FabricatedOverlay(std::move(overlay), std::move(sp_data),
+                           version == kFabricatedOverlayCurrentVersion
                                                    ? std::optional<uint32_t>(crc)
                                                    : std::nullopt);
 }
 
 Result<FabricatedOverlay::SerializedData*> FabricatedOverlay::InitializeData() const {
   if (!data_.has_value()) {
-    auto size = overlay_pb_.ByteSizeLong();
-    auto data = std::unique_ptr<uint8_t[]>(new uint8_t[size]);
+    auto pb_size = overlay_pb_.ByteSizeLong();
+    auto pb_data = std::unique_ptr<uint8_t[]>(new uint8_t[pb_size]);
 
     // Ensure serialization is deterministic
-    google::protobuf::io::ArrayOutputStream array_stream(data.get(), size);
+    google::protobuf::io::ArrayOutputStream array_stream(pb_data.get(), pb_size);
     google::protobuf::io::CodedOutputStream output_stream(&array_stream);
     output_stream.SetSerializationDeterministic(true);
     overlay_pb_.SerializeWithCachedSizes(&output_stream);
-    if (output_stream.HadError() || size != output_stream.ByteCount()) {
+    if (output_stream.HadError() || pb_size != output_stream.ByteCount()) {
       return Error("Failed to serialize fabricated overlay.");
     }
 
     // Calculate the crc using the proto data and the version.
-    uint32_t crc = crc32(0L, Z_NULL, 0);
-    crc = crc32(crc, reinterpret_cast<const uint8_t*>(&kFabricatedOverlayCurrentVersion),
+    uint32_t pb_crc = crc32(0L, Z_NULL, 0);
+    pb_crc = crc32(pb_crc, reinterpret_cast<const uint8_t*>(&kFabricatedOverlayCurrentVersion),
                 sizeof(uint32_t));
-    crc = crc32(crc, data.get(), size);
-    data_ = SerializedData{std::move(data), size, crc};
+    pb_crc = crc32(pb_crc, pb_data.get(), pb_size);
+
+    data_ = SerializedData{std::move(pb_data), pb_size, pb_crc, string_pool_data_};
   }
   return &(*data_);
 }
@@ -216,7 +262,7 @@
   if (!data) {
     return data.GetError();
   }
-  return (*data)->crc;
+  return (*data)->pb_crc;
 }
 
 Result<Unit> FabricatedOverlay::ToBinaryStream(std::ostream& stream) const {
@@ -227,8 +273,13 @@
 
   Write32(stream, kFabricatedOverlayMagic);
   Write32(stream, kFabricatedOverlayCurrentVersion);
-  Write32(stream, (*data)->crc);
-  stream.write(reinterpret_cast<const char*>((*data)->data.get()), (*data)->data_size);
+  Write32(stream, (*data)->pb_crc);
+  Write32(stream, (*data)->sp_data.length());
+  stream.write((*data)->sp_data.data(), (*data)->sp_data.length());
+  if (stream.bad()) {
+    return Error("Failed to write string pool data.");
+  }
+  stream.write(reinterpret_cast<const char*>((*data)->pb_data.get()), (*data)->pb_data_size);
   if (stream.bad()) {
     return Error("Failed to write serialized fabricated overlay.");
   }
@@ -290,11 +341,20 @@
                                        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()}}});
       }
     }
   }
+  const uint32_t string_pool_data_length = overlay_.string_pool_data_.length();
+  result.string_pool_data = OverlayData::InlineStringPoolData{
+      .data = std::unique_ptr<uint8_t[]>(new uint8_t[string_pool_data_length]),
+      .data_length = string_pool_data_length,
+      .string_pool_offset = 0,
+  };
+  memcpy(result.string_pool_data->data.get(), overlay_.string_pool_data_.data(),
+       string_pool_data_length);
   return result;
 }
 
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 468ea0c..e804c87 100644
--- a/cmds/idmap2/tests/FabricatedOverlayTests.cpp
+++ b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
@@ -43,9 +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: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));
@@ -59,42 +67,54 @@
 
   auto pairs = container->GetOverlayData(*info);
   ASSERT_TRUE(pairs);
-  EXPECT_FALSE(pairs->string_pool_data.has_value());
-  ASSERT_EQ(3U, pairs->pairs.size());
+  ASSERT_EQ(4U, pairs->pairs.size());
+  auto string_pool = ResStringPool(pairs->string_pool_data->data.get(),
+                                        pairs->string_pool_data->data_length, false);
 
   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.split:integer/int2", it.resource_name);
-  entry = std::get_if<TargetValue>(&it.value);
+  ASSERT_EQ("com.example.target:string/string1", it.resource_name);
+  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(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<TargetValueWithConfig>(&it.value);
+  ASSERT_NE(nullptr, entry);
+  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());
   }
 }
@@ -103,7 +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: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;
@@ -126,14 +148,23 @@
 
   auto pairs = (*container)->GetOverlayData(*info);
   ASSERT_TRUE(pairs) << pairs.GetErrorMessage();
-  EXPECT_EQ(1U, pairs->pairs.size());
+  EXPECT_EQ(2U, pairs->pairs.size());
+  auto string_pool = ResStringPool(pairs->string_pool_data->data.get(),
+                                        pairs->string_pool_data->data_length, false);
 
   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<TargetValueWithConfig>(&it.value);
+  ASSERT_NE(nullptr, entry);
+  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 738b9cf..ee9a424 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -261,8 +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("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);
@@ -288,12 +289,19 @@
   ASSERT_EQ(data->GetTargetEntries().size(), 0U);
   ASSERT_EQ(data->GetOverlayEntries().size(), 0U);
 
+  auto string_pool_data = data->GetStringPoolData();
+  auto string_pool = ResStringPool(string_pool_data.data(), string_pool_data.size(), false);
+
+
   const auto& target_inline_entries = data->GetTargetInlineEntries();
-  ASSERT_EQ(target_inline_entries.size(), 2U);
+  ASSERT_EQ(target_inline_entries.size(), 3U);
   ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1,
                              Res_value::TYPE_INT_DEC, 2U);
   ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1,
                              Res_value::TYPE_REFERENCE, 0x7f010000);
+  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[2], R::target::string::str2,
+                             Res_value::TYPE_STRING,
+                             (uint32_t) (string_pool.indexOfString(u"foobar", 6)).value_or(-1));
 }
 
 TEST(IdmapTests, FailCreateIdmapInvalidName) {
diff --git a/cmds/idmap2/tests/R.h b/cmds/idmap2/tests/R.h
index 89219c9..ad998b9 100644
--- a/cmds/idmap2/tests/R.h
+++ b/cmds/idmap2/tests/R.h
@@ -41,6 +41,7 @@
     constexpr ResourceId policy_system = 0x7f02000c;
     constexpr ResourceId policy_system_vendor = 0x7f02000d;
     constexpr ResourceId str1 = 0x7f02000e;
+    constexpr ResourceId str2 = 0x7f02000f;
     constexpr ResourceId str3 = 0x7f020010;
     constexpr ResourceId str4 = 0x7f020011;
   }  // namespace string
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index 32b3d13..ca9a444 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -194,8 +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("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);
@@ -209,9 +210,14 @@
 
   ASSERT_TRUE(resources) << resources.GetErrorMessage();
   auto& res = *resources;
-  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 2U);
+  auto string_pool_data = res.GetStringPoolData();
+  auto string_pool = ResStringPool(string_pool_data.data(), string_pool_data.size(), false);
+
+  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U);
   ASSERT_EQ(res.GetOverlayToTargetMap().size(), 0U);
   ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_REFERENCE, 0x7f010000));
+  ASSERT_RESULT(MappingExists(res, R::target::string::str2, Res_value::TYPE_STRING,
+                              (uint32_t) (string_pool.indexOfString(u"foobar", 6)).value_or(-1)));
   ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_INT_DEC, 2U));
 }
 
diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
index b1b432b..6fd2bf2 100644
--- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
+++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
@@ -380,7 +380,7 @@
         Tracer.trace();
         Display display = getAutomatorBridge().getDefaultDisplay();
         Point p = new Point();
-        display.getSize(p);
+        display.getRealSize(p);
         return p.x;
     }
 
@@ -394,7 +394,7 @@
         Tracer.trace();
         Display display = getAutomatorBridge().getDefaultDisplay();
         Point p = new Point();
-        display.getSize(p);
+        display.getRealSize(p);
         return p.y;
     }
 
diff --git a/config/preloaded-classes-denylist b/config/preloaded-classes-denylist
index 02f2df6..502d8c6 100644
--- a/config/preloaded-classes-denylist
+++ b/config/preloaded-classes-denylist
@@ -9,3 +9,4 @@
 android.net.rtp.AudioStream
 android.net.rtp.RtpStream
 java.util.concurrent.ThreadLocalRandom
+com.android.internal.jank.InteractionJankMonitor$InstanceHolder
diff --git a/core/TEST_MAPPING b/core/TEST_MAPPING
index 682d3ab..fd571c9 100644
--- a/core/TEST_MAPPING
+++ b/core/TEST_MAPPING
@@ -10,9 +10,6 @@
           "include-filter": "com.android.internal.inputmethod"
         },
         {
-          "include-filter": "android.accessibilityService.cts"
-        },
-        {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ],
diff --git a/core/api/current.txt b/core/api/current.txt
index ea2ae0c..317f8eb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12448,6 +12448,16 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.SigningInfo> CREATOR;
   }
 
+  public final class UserProperties implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getShowInLauncher();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserProperties> CREATOR;
+    field public static final int SHOW_IN_LAUNCHER_NO = 2; // 0x2
+    field public static final int SHOW_IN_LAUNCHER_SEPARATE = 1; // 0x1
+    field public static final int SHOW_IN_LAUNCHER_WITH_PARENT = 0; // 0x0
+  }
+
   public final class VersionedPackage implements android.os.Parcelable {
     ctor public VersionedPackage(@NonNull String, int);
     ctor public VersionedPackage(@NonNull String, long);
@@ -14006,49 +14016,49 @@
 
   public final class Bitmap implements android.os.Parcelable {
     method @NonNull public android.graphics.Bitmap asShared();
-    method @WorkerThread public boolean compress(android.graphics.Bitmap.CompressFormat, int, java.io.OutputStream);
-    method public android.graphics.Bitmap copy(android.graphics.Bitmap.Config, boolean);
-    method public void copyPixelsFromBuffer(java.nio.Buffer);
-    method public void copyPixelsToBuffer(java.nio.Buffer);
-    method public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap);
-    method public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap, int, int, int, int);
-    method public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap, int, int, int, int, @Nullable android.graphics.Matrix, boolean);
-    method public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config);
-    method public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config);
-    method public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config, boolean);
-    method public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config, boolean, @NonNull android.graphics.ColorSpace);
-    method public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config, boolean);
-    method public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config, boolean, @NonNull android.graphics.ColorSpace);
-    method public static android.graphics.Bitmap createBitmap(@ColorInt @NonNull int[], int, int, int, int, @NonNull android.graphics.Bitmap.Config);
-    method public static android.graphics.Bitmap createBitmap(@NonNull android.util.DisplayMetrics, @ColorInt @NonNull int[], int, int, int, int, @NonNull android.graphics.Bitmap.Config);
-    method public static android.graphics.Bitmap createBitmap(@ColorInt @NonNull int[], int, int, android.graphics.Bitmap.Config);
-    method public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, @ColorInt @NonNull int[], int, int, @NonNull android.graphics.Bitmap.Config);
+    method @WorkerThread public boolean compress(@NonNull android.graphics.Bitmap.CompressFormat, int, @NonNull java.io.OutputStream);
+    method public android.graphics.Bitmap copy(@NonNull android.graphics.Bitmap.Config, boolean);
+    method public void copyPixelsFromBuffer(@NonNull java.nio.Buffer);
+    method public void copyPixelsToBuffer(@NonNull java.nio.Buffer);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap, int, int, int, int);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap, int, int, int, int, @Nullable android.graphics.Matrix, boolean);
+    method @NonNull public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config);
+    method @NonNull public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config, boolean);
+    method @NonNull public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config, boolean, @NonNull android.graphics.ColorSpace);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config, boolean);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config, boolean, @NonNull android.graphics.ColorSpace);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@ColorInt @NonNull int[], int, int, int, int, @NonNull android.graphics.Bitmap.Config);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.util.DisplayMetrics, @ColorInt @NonNull int[], int, int, int, int, @NonNull android.graphics.Bitmap.Config);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@ColorInt @NonNull int[], int, int, android.graphics.Bitmap.Config);
+    method @NonNull public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, @ColorInt @NonNull int[], int, int, @NonNull android.graphics.Bitmap.Config);
     method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Picture);
     method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Picture, int, int, @NonNull android.graphics.Bitmap.Config);
-    method public static android.graphics.Bitmap createScaledBitmap(@NonNull android.graphics.Bitmap, int, int, boolean);
+    method @NonNull public static android.graphics.Bitmap createScaledBitmap(@NonNull android.graphics.Bitmap, int, int, boolean);
     method public int describeContents();
     method public void eraseColor(@ColorInt int);
     method public void eraseColor(@ColorLong long);
-    method @CheckResult public android.graphics.Bitmap extractAlpha();
-    method @CheckResult public android.graphics.Bitmap extractAlpha(android.graphics.Paint, int[]);
+    method @CheckResult @NonNull public android.graphics.Bitmap extractAlpha();
+    method @CheckResult @NonNull public android.graphics.Bitmap extractAlpha(@Nullable android.graphics.Paint, int[]);
     method public int getAllocationByteCount();
     method public int getByteCount();
     method @NonNull public android.graphics.Color getColor(int, int);
     method @Nullable public android.graphics.ColorSpace getColorSpace();
-    method public android.graphics.Bitmap.Config getConfig();
+    method @NonNull public android.graphics.Bitmap.Config getConfig();
     method public int getDensity();
     method public int getGenerationId();
     method @NonNull public android.hardware.HardwareBuffer getHardwareBuffer();
     method public int getHeight();
-    method public byte[] getNinePatchChunk();
+    method @Nullable public byte[] getNinePatchChunk();
     method @ColorInt public int getPixel(int, int);
-    method public void getPixels(@ColorInt int[], int, int, int, int, int, int);
+    method public void getPixels(@ColorInt @NonNull int[], int, int, int, int, int, int);
     method public int getRowBytes();
-    method public int getScaledHeight(android.graphics.Canvas);
-    method public int getScaledHeight(android.util.DisplayMetrics);
+    method public int getScaledHeight(@NonNull android.graphics.Canvas);
+    method public int getScaledHeight(@NonNull android.util.DisplayMetrics);
     method public int getScaledHeight(int);
-    method public int getScaledWidth(android.graphics.Canvas);
-    method public int getScaledWidth(android.util.DisplayMetrics);
+    method public int getScaledWidth(@NonNull android.graphics.Canvas);
+    method public int getScaledWidth(@NonNull android.util.DisplayMetrics);
     method public int getScaledWidth(int);
     method public int getWidth();
     method public boolean hasAlpha();
@@ -14057,21 +14067,21 @@
     method public boolean isPremultiplied();
     method public boolean isRecycled();
     method public void prepareToDraw();
-    method public void reconfigure(int, int, android.graphics.Bitmap.Config);
+    method public void reconfigure(int, int, @NonNull android.graphics.Bitmap.Config);
     method public void recycle();
-    method public boolean sameAs(android.graphics.Bitmap);
+    method @WorkerThread public boolean sameAs(@Nullable android.graphics.Bitmap);
     method public void setColorSpace(@NonNull android.graphics.ColorSpace);
-    method public void setConfig(android.graphics.Bitmap.Config);
+    method public void setConfig(@NonNull android.graphics.Bitmap.Config);
     method public void setDensity(int);
     method public void setHasAlpha(boolean);
     method public void setHasMipMap(boolean);
     method public void setHeight(int);
     method public void setPixel(int, int, @ColorInt int);
-    method public void setPixels(@ColorInt int[], int, int, int, int, int, int);
+    method public void setPixels(@ColorInt @NonNull int[], int, int, int, int, int, int);
     method public void setPremultiplied(boolean);
     method public void setWidth(int);
     method @Nullable public static android.graphics.Bitmap wrapHardwareBuffer(@NonNull android.hardware.HardwareBuffer, @Nullable android.graphics.ColorSpace);
-    method public void writeToParcel(android.os.Parcel, int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.graphics.Bitmap> CREATOR;
     field public static final int DENSITY_NONE = 0; // 0x0
   }
@@ -16903,6 +16913,7 @@
     method public int describeContents();
     method public int getFormat();
     method public int getHeight();
+    method public long getId();
     method public int getLayers();
     method public long getUsage();
     method public int getWidth();
@@ -23189,9 +23200,10 @@
 
   public abstract static class MediaRouter2.RouteCallback {
     ctor public MediaRouter2.RouteCallback();
-    method public void onRoutesAdded(@NonNull java.util.List<android.media.MediaRoute2Info>);
-    method public void onRoutesChanged(@NonNull java.util.List<android.media.MediaRoute2Info>);
-    method public void onRoutesRemoved(@NonNull java.util.List<android.media.MediaRoute2Info>);
+    method @Deprecated public void onRoutesAdded(@NonNull java.util.List<android.media.MediaRoute2Info>);
+    method @Deprecated public void onRoutesChanged(@NonNull java.util.List<android.media.MediaRoute2Info>);
+    method @Deprecated public void onRoutesRemoved(@NonNull java.util.List<android.media.MediaRoute2Info>);
+    method public void onRoutesUpdated(@NonNull java.util.List<android.media.MediaRoute2Info>);
   }
 
   public class MediaRouter2.RoutingController {
@@ -32249,8 +32261,10 @@
     method public android.os.UserHandle getUserForSerialNumber(long);
     method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS", "android.permission.QUERY_USERS", android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public String getUserName();
     method public java.util.List<android.os.UserHandle> getUserProfiles();
+    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();
@@ -41341,6 +41355,8 @@
     field public static final String KEY_CARRIER_INSTANT_LETTERING_ESCAPED_CHARS_STRING = "carrier_instant_lettering_escaped_chars_string";
     field public static final String KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING = "carrier_instant_lettering_invalid_chars_string";
     field public static final String KEY_CARRIER_INSTANT_LETTERING_LENGTH_LIMIT_INT = "carrier_instant_lettering_length_limit_int";
+    field public static final String KEY_CARRIER_METERED_APN_TYPES_STRINGS = "carrier_metered_apn_types_strings";
+    field public static final String KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS = "carrier_metered_roaming_apn_types_strings";
     field public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool";
     field public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string";
     field public static final String KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY = "carrier_nr_availabilities_int_array";
@@ -49956,7 +49972,6 @@
     method public void invalidate();
     method public void invalidateDrawable(@NonNull android.graphics.drawable.Drawable);
     method public void invalidateOutline();
-    method public boolean isAccessibilityDataPrivate();
     method public boolean isAccessibilityFocused();
     method public boolean isAccessibilityHeading();
     method public boolean isActivated();
@@ -50134,7 +50149,6 @@
     method public void scrollTo(int, int);
     method public void sendAccessibilityEvent(int);
     method public void sendAccessibilityEventUnchecked(android.view.accessibility.AccessibilityEvent);
-    method public void setAccessibilityDataPrivate(int);
     method public void setAccessibilityDelegate(@Nullable android.view.View.AccessibilityDelegate);
     method public void setAccessibilityHeading(boolean);
     method public void setAccessibilityLiveRegion(int);
@@ -50315,9 +50329,6 @@
     method @CallSuper protected boolean verifyDrawable(@NonNull android.graphics.drawable.Drawable);
     method @Deprecated public boolean willNotCacheDrawing();
     method public boolean willNotDraw();
-    field public static final int ACCESSIBILITY_DATA_PRIVATE_AUTO = 0; // 0x0
-    field public static final int ACCESSIBILITY_DATA_PRIVATE_NO = 2; // 0x2
-    field public static final int ACCESSIBILITY_DATA_PRIVATE_YES = 1; // 0x1
     field public static final int ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 2; // 0x2
     field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
     field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
@@ -51770,11 +51781,9 @@
     method public int getSpeechStateChangeTypes();
     method public int getWindowChanges();
     method public void initFromParcel(android.os.Parcel);
-    method public boolean isAccessibilityDataPrivate();
     method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(int);
     method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent);
     method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain();
-    method public void setAccessibilityDataPrivate(boolean);
     method public void setAction(int);
     method public void setContentChangeTypes(int);
     method public void setEventTime(long);
@@ -51865,7 +51874,6 @@
     method public static boolean isAccessibilityButtonSupported();
     method public boolean isAudioDescriptionRequested();
     method public boolean isEnabled();
-    method public boolean isRequestFromAccessibilityTool();
     method public boolean isTouchExplorationEnabled();
     method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
     method public boolean removeAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
@@ -51972,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();
@@ -52978,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();
@@ -53072,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();
@@ -53162,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);
@@ -53383,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 572101c..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
@@ -13398,6 +13400,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult changeIccLockPin(@NonNull String, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int checkCarrierPrivilegesForPackage(String);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int checkCarrierPrivilegesForPackageAnyPhone(String);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void clearRadioPowerOffForReason(int);
     method public void dial(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean disableDataConnectivity();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean enableDataConnectivity();
@@ -13443,6 +13446,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getMergedImsisFromGroup();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.PhoneCapability getPhoneCapability();
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getPreferredNetworkTypeBitmask();
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Set<java.lang.Integer> getRadioPowerOffReasons();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public int getRadioPowerState();
     method public int getSimApplicationState();
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimApplicationState(int);
@@ -13497,6 +13501,7 @@
     method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestModemActivityInfo(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.ModemActivityInfo,android.telephony.TelephonyManager.ModemActivityInfoException>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestNumberVerification(@NonNull android.telephony.PhoneNumberRange, long, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.NumberVerificationCallback);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestRadioPowerOffForReason(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetAllCarrierActions();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetCarrierKeysForImsiEncryption();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void resetIms(int);
@@ -13521,9 +13526,9 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setNrDualConnectivityState(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRadioEnabled(boolean);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadioPower(boolean);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRadioEnabled(boolean);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadioPower(boolean);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerState(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerState(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerStateForSlot(int, int);
@@ -13605,6 +13610,10 @@
     field public static final int PREPARE_UNATTENDED_REBOOT_SUCCESS = 0; // 0x0
     field public static final int RADIO_POWER_OFF = 0; // 0x0
     field public static final int RADIO_POWER_ON = 1; // 0x1
+    field public static final int RADIO_POWER_REASON_CARRIER = 2; // 0x2
+    field public static final int RADIO_POWER_REASON_NEARBY_DEVICE = 3; // 0x3
+    field public static final int RADIO_POWER_REASON_THERMAL = 1; // 0x1
+    field public static final int RADIO_POWER_REASON_USER = 0; // 0x0
     field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2
     field public static final int SET_CARRIER_RESTRICTION_ERROR = 2; // 0x2
     field public static final int SET_CARRIER_RESTRICTION_NOT_SUPPORTED = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 6c941e7..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";
@@ -90,7 +89,6 @@
 
   public class AccessibilityServiceInfo implements android.os.Parcelable {
     method @NonNull public android.content.ComponentName getComponentName();
-    method public void setAccessibilityTool(boolean);
   }
 
 }
@@ -132,6 +130,7 @@
     method public static void resumeAppSwitches() throws android.os.RemoteException;
     method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setStopUserOnSwitch(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean startUserInBackgroundOnSecondaryDisplay(int, int);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean);
     method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
@@ -798,6 +797,7 @@
     field public static final long OVERRIDE_MIN_ASPECT_RATIO_MEDIUM = 180326845L; // 0xabf91bdL
     field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f;
     field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L
+    field public static final long OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN = 208648326L; // 0xc6fb886L
     field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
   }
 
@@ -1875,6 +1875,7 @@
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
     method public static boolean isSplitSystemUser();
+    method public static boolean isUsersOnSecondaryDisplaysEnabled();
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
   }
 
@@ -3202,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();
   }
@@ -3331,11 +3337,12 @@
     ctor public TaskFragmentOrganizer(@NonNull java.util.concurrent.Executor);
     method @NonNull public java.util.concurrent.Executor getExecutor();
     method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizerToken();
-    method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentInfo);
-    method public void onTaskFragmentError(@NonNull android.os.IBinder, @Nullable android.window.TaskFragmentInfo, int, @NonNull Throwable);
-    method public void onTaskFragmentInfoChanged(@NonNull android.window.TaskFragmentInfo);
-    method public void onTaskFragmentParentInfoChanged(@NonNull android.os.IBinder, @NonNull android.content.res.Configuration);
-    method public void onTaskFragmentVanished(@NonNull android.window.TaskFragmentInfo);
+    method public void onActivityReparentedToTask(@NonNull android.window.WindowContainerTransaction, int, @NonNull android.content.Intent, @NonNull android.os.IBinder);
+    method public void onTaskFragmentAppeared(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.TaskFragmentInfo);
+    method public void onTaskFragmentError(@NonNull android.window.WindowContainerTransaction, @NonNull android.os.IBinder, @Nullable android.window.TaskFragmentInfo, int, @NonNull Throwable);
+    method public void onTaskFragmentInfoChanged(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.TaskFragmentInfo);
+    method public void onTaskFragmentParentInfoChanged(@NonNull android.window.WindowContainerTransaction, int, @NonNull android.content.res.Configuration);
+    method public void onTaskFragmentVanished(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.TaskFragmentInfo);
     method @CallSuper public void registerOrganizer();
     method @CallSuper public void unregisterOrganizer();
   }
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 8f6bfd3..2e89ce8 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -784,7 +784,6 @@
         mNonInteractiveUiTimeout = other.mNonInteractiveUiTimeout;
         mInteractiveUiTimeout = other.mInteractiveUiTimeout;
         flags = other.flags;
-        mIsAccessibilityTool = other.mIsAccessibilityTool;
     }
 
     private boolean isRequestAccessibilityButtonChangeEnabled(IPlatformCompat platformCompat) {
@@ -1113,26 +1112,6 @@
     }
 
     /**
-     * Sets whether the service is used to assist users with disabilities.
-     *
-     * <p>
-     * This property is normally provided in the service's {@link #mResolveInfo ResolveInfo}.
-     * </p>
-     *
-     * <p>
-     * This method is helpful for unit testing. However, this property is not dynamically
-     * configurable by a standard {@link AccessibilityService} so it's not possible to update the
-     * copy held by the system with this method.
-     * </p>
-     *
-     * @hide
-     */
-    @TestApi
-    public void setAccessibilityTool(boolean isAccessibilityTool) {
-        mIsAccessibilityTool = isAccessibilityTool;
-    }
-
-    /**
      * Indicates if the service is used to assist users with disabilities.
      *
      * @return {@code true} if the property is set to true.
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/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 449729e..182b0a3 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4336,13 +4336,42 @@
     @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
             android.Manifest.permission.CREATE_USERS})
     public boolean switchUser(@NonNull UserHandle user) {
-        if (user == null) {
-            throw new IllegalArgumentException("UserHandle cannot be null.");
-        }
+        Preconditions.checkNotNull(user, "UserHandle cannot be null.");
+
         return switchUser(user.getIdentifier());
     }
 
     /**
+     * Starts the given user in background and associate the user with the given display.
+     *
+     * <p>This method will allow the user to launch activities on that display, and it's typically
+     * used only on automotive builds when the vehicle has multiple displays (you can verify if it's
+     * supported by calling {@link UserManager#isBackgroundUsersOnSecondaryDisplaysSupported()}).
+     *
+     * @return whether the user was started.
+     *
+     * @throws UnsupportedOperationException if the device does not support background users on
+     * secondary displays.
+     * @throws IllegalArgumentException if the display does not exist.
+     * @throws IllegalStateException if the user cannot be started on that display (for example, if
+     * there's already a user using that display or if the user is already associated with other
+     * display).
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.CREATE_USERS})
+    public boolean startUserInBackgroundOnSecondaryDisplay(@UserIdInt int userId,
+            int displayId) {
+        try {
+            return getService().startUserInBackgroundOnSecondaryDisplay(userId, displayId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Gets the message that is shown when a user is switched from.
      *
      * @hide
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 9210958..419b8e1 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -47,6 +47,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.BiFunction;
 
 /**
  * Activity manager local system service interface.
@@ -623,6 +624,11 @@
      * broadcast my be sent to; any app Ids < {@link android.os.Process#FIRST_APPLICATION_UID} are
      * automatically allowlisted.
      *
+     * @param filterExtrasForReceiver A function to filter intent extras for the given receiver by
+     * using the rules of package visibility. Returns extras with legitimate package info that the
+     * receiver is able to access, or {@code null} if none of the packages is visible to the
+     * receiver.
+     *
      * @see com.android.server.am.ActivityManagerService#broadcastIntentWithFeature(
      *      IApplicationThread, String, Intent, String, IIntentReceiver, int, String, Bundle,
      *      String[], int, Bundle, boolean, boolean, int)
@@ -630,7 +636,9 @@
     public abstract int broadcastIntent(Intent intent,
             IIntentReceiver resultTo,
             String[] requiredPermissions, boolean serialized,
-            int userId, int[] appIdAllowList, @Nullable Bundle bOptions);
+            int userId, int[] appIdAllowList,
+            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+            @Nullable Bundle bOptions);
 
     /**
      * Add uid to the ActivityManagerService PendingStartActivityUids list.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 6b3dc82..db76816 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -107,7 +107,6 @@
 import android.net.Proxy;
 import android.net.TrafficStats;
 import android.net.Uri;
-import android.net.wifi.WifiFrameworkInitializer;
 import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.BluetoothServiceManager;
@@ -4178,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);
     }
 
@@ -4966,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.
@@ -7901,8 +7907,6 @@
         BluetoothFrameworkInitializer.setBluetoothServiceManager(new BluetoothServiceManager());
         BluetoothFrameworkInitializer.setBinderCallsStatsInitializer(context -> {
             BinderCallsStats.startForBluetooth(context); });
-        WifiFrameworkInitializer.setBinderCallsStatsInitializer(context -> {
-            BinderCallsStats.startForWifi(context); });
     }
 
     private void purgePendingResources() {
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 9072b50..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;
     }
 
     /**
@@ -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;
     }
@@ -10084,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/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index f0e1448..aa5fa5b 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -528,6 +528,28 @@
         return mIsAlarmBroadcast;
     }
 
+    /**
+     * Did this broadcast originate from a push message from the server?
+     *
+     * @return true if this broadcast is a push message, false otherwise.
+     * @hide
+     */
+    public boolean isPushMessagingBroadcast() {
+        return mTemporaryAppAllowlistReasonCode == PowerExemptionManager.REASON_PUSH_MESSAGING;
+    }
+
+    /**
+     * Did this broadcast originate from a push message from the server which was over the allowed
+     * quota?
+     *
+     * @return true if this broadcast is a push message over quota, false otherwise.
+     * @hide
+     */
+    public boolean isPushMessagingOverQuotaBroadcast() {
+        return mTemporaryAppAllowlistReasonCode
+                == PowerExemptionManager.REASON_PUSH_MESSAGING_OVER_QUOTA;
+    }
+
     /** {@hide} */
     public long getRequireCompatChangeId() {
         return mRequireCompatChangeId;
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/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 8367441..bd999fc 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -760,4 +760,15 @@
      * </p>
      */
     int getBackgroundRestrictionExemptionReason(int uid);
+
+    // Start (?) of T transactions
+    /**
+     * Similar to {@link #startUserInBackgroundWithListener(int userId, IProgressListener unlockProgressListener),
+     * but setting the user as the visible user of that display (i.e., allowing the user and its
+     * running profiles to launch activities on that display).
+     *
+     * <p>Typically used only by automotive builds when the vehicle has multiple displays.
+     */
+    boolean startUserInBackgroundOnSecondaryDisplay(int userid, int displayId);
+
 }
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 f97415c..2b245aa 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -43,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";
@@ -90,13 +91,14 @@
      */
     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;
         }
@@ -120,7 +122,7 @@
         } 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);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 36e1eee..4da957c 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -147,8 +147,6 @@
 import android.net.PacProxyManager;
 import android.net.TetheringManager;
 import android.net.VpnManager;
-import android.net.lowpan.ILowpanManager;
-import android.net.lowpan.LowpanManager;
 import android.net.vcn.IVcnManagementService;
 import android.net.vcn.VcnManager;
 import android.net.wifi.WifiFrameworkInitializer;
@@ -779,15 +777,6 @@
                         ctx.mMainThread.getHandler());
             }});
 
-        registerService(Context.LOWPAN_SERVICE, LowpanManager.class,
-                new CachedServiceFetcher<LowpanManager>() {
-            @Override
-            public LowpanManager createService(ContextImpl ctx) throws ServiceNotFoundException {
-                IBinder b = ServiceManager.getServiceOrThrow(Context.LOWPAN_SERVICE);
-                ILowpanManager service = ILowpanManager.Stub.asInterface(b);
-                return new LowpanManager(ctx.getOuterContext(), service);
-            }});
-
         registerService(Context.WIFI_NL80211_SERVICE, WifiNl80211Manager.class,
                 new CachedServiceFetcher<WifiNl80211Manager>() {
                     @Override
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 8d57e32..a045157 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -550,7 +550,6 @@
         info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
                 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
                 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
-        info.setAccessibilityTool(true);
         try {
             // Calling out with a lock held is fine since if the system
             // process is gone the client calling in will be killed.
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/attention/AttentionManagerInternal.java b/core/java/android/attention/AttentionManagerInternal.java
index 47bec61..24fe0db 100644
--- a/core/java/android/attention/AttentionManagerInternal.java
+++ b/core/java/android/attention/AttentionManagerInternal.java
@@ -83,11 +83,11 @@
     }
 
     /** Internal interface for proximity callback. */
-    public abstract static class ProximityUpdateCallbackInternal {
+    public interface ProximityUpdateCallbackInternal {
         /**
          * @param distance the estimated distance of the user (in meter)
          * The distance will be PROXIMITY_UNKNOWN if the proximity sensing was inconclusive.
          */
-        public abstract void onProximityUpdate(double distance);
+        void onProximityUpdate(double distance);
     }
 }
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/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/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index a3d595ef..e92be7c 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1109,6 +1109,17 @@
     public static final float OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE = 16 / 9f;
 
     /**
+     * Enables the use of split screen aspect ratio. This allows an app to use all the available
+     * space in split mode avoiding letterboxing.
+     * @hide
+     */
+    @ChangeId
+    @Disabled
+    @Overridable
+    @TestApi
+    public static final long OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN = 208648326L;
+
+    /**
      * Compares activity window layout min width/height with require space for multi window to
      * determine if it can be put into multi window mode.
      */
@@ -1317,8 +1328,8 @@
      * Returns true if the activity has maximum or minimum aspect ratio.
      * @hide
      */
-    public boolean hasFixedAspectRatio(@ScreenOrientation int orientation) {
-        return getMaxAspectRatio() != 0 || getMinAspectRatio(orientation) != 0;
+    public boolean hasFixedAspectRatio() {
+        return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
     }
 
     /**
@@ -1460,30 +1471,10 @@
     }
 
     /**
-     * Returns the min aspect ratio of this activity.
-     *
-     * This takes into account the minimum aspect ratio as defined in the app's manifest and
-     * possible overrides as per OVERRIDE_MIN_ASPECT_RATIO.
-     *
-     * In the rare cases where the manifest minimum aspect ratio is required, use
-     * {@code getManifestMinAspectRatio}.
+     * Returns the min aspect ratio of this activity as defined in the manifest file.
      * @hide
      */
-    public float getMinAspectRatio(@ScreenOrientation int orientation) {
-        if (applicationInfo == null || !isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO) || (
-                isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
-                        && !isFixedOrientationPortrait(orientation))) {
-            return mMinAspectRatio;
-        }
-
-        if (isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE)) {
-            return Math.max(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE, mMinAspectRatio);
-        }
-
-        if (isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM)) {
-            return Math.max(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, mMinAspectRatio);
-        }
-
+    public float getMinAspectRatio() {
         return mMinAspectRatio;
     }
 
@@ -1512,7 +1503,13 @@
         }
     }
 
-    private boolean isChangeEnabled(long changeId) {
+    /**
+     * Checks if a changeId is enabled for the current user
+     * @param changeId The changeId to verify
+     * @return True of the changeId is enabled
+     * @hide
+     */
+    public boolean isChangeEnabled(long changeId) {
         return CompatChanges.isChangeEnabled(changeId, applicationInfo.packageName,
                 UserHandle.getUserHandleForUid(applicationInfo.uid));
     }
@@ -1633,12 +1630,9 @@
         if (getMaxAspectRatio() != 0) {
             pw.println(prefix + "maxAspectRatio=" + getMaxAspectRatio());
         }
-        final float minAspectRatio = getMinAspectRatio(screenOrientation);
+        final float minAspectRatio = getMinAspectRatio();
         if (minAspectRatio != 0) {
             pw.println(prefix + "minAspectRatio=" + minAspectRatio);
-            if (getManifestMinAspectRatio() !=  minAspectRatio) {
-                pw.println(prefix + "getManifestMinAspectRatio=" + getManifestMinAspectRatio());
-            }
         }
         if (supportsSizeChanges) {
             pw.println(prefix + "supportsSizeChanges=true");
diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS
index 087e61d..f9d3222 100644
--- a/core/java/android/content/pm/OWNERS
+++ b/core/java/android/content/pm/OWNERS
@@ -11,3 +11,4 @@
 per-file AppSearchPerson.java = file:/core/java/android/content/pm/SHORTCUT_OWNERS
 per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS
 per-file UserInfo* = file:/MULTIUSER_OWNERS
+per-file *UserProperties* = file:/MULTIUSER_OWNERS
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/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt b/core/java/android/content/pm/UserProperties.aidl
similarity index 78%
copy from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt
copy to core/java/android/content/pm/UserProperties.aidl
index 5f4da8a..4d37067 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt
+++ b/core/java/android/content/pm/UserProperties.aidl
@@ -11,12 +11,9 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
  */
 
-package com.android.settingslib.spa.theme
+package android.content.pm;
 
-object SettingsOpacity {
-    const val Full = 1f
-    const val Disabled = 0.38f
-}
+parcelable UserProperties;
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
new file mode 100644
index 0000000..1a82e4d
--- /dev/null
+++ b/core/java/android/content/pm/UserProperties.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Slog;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class holding the properties of a user that derive mostly from its user type.
+ */
+public final class UserProperties implements Parcelable {
+    private static final String LOG_TAG = UserProperties.class.getSimpleName();
+
+    // Attribute strings for reading/writing properties to/from XML.
+    private static final String ATTR_SHOW_IN_LAUNCHER = "showInLauncher";
+    private static final String ATTR_START_WITH_PARENT = "startWithParent";
+
+    /** Index values of each property (to indicate whether they are present in this object). */
+    @IntDef(prefix = "INDEX_", value = {
+            INDEX_SHOW_IN_LAUNCHER,
+            INDEX_START_WITH_PARENT,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface PropertyIndex {
+    }
+    private static final int INDEX_SHOW_IN_LAUNCHER = 0;
+    private static final int INDEX_START_WITH_PARENT = 1;
+    /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
+    private long mPropertiesPresent = 0;
+
+
+    /**
+     * Possible values for whether or how to show this user in the Launcher.
+     * @hide
+     */
+    @IntDef(prefix = "SHOW_IN_LAUNCHER_", value = {
+            SHOW_IN_LAUNCHER_WITH_PARENT,
+            SHOW_IN_LAUNCHER_SEPARATE,
+            SHOW_IN_LAUNCHER_NO,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ShowInLauncher {
+    }
+    /**
+     * Suggests that the launcher should show this user's apps in the main tab.
+     * That is, either this user is a full user, so its apps should be presented accordingly, or, if
+     * this user is a profile, then its apps should be shown alongside its parent's apps.
+     */
+    public static final int SHOW_IN_LAUNCHER_WITH_PARENT = 0;
+    /**
+     * Suggests that the launcher should show this user's apps, but separately from the apps of this
+     * user's parent.
+     */
+    public static final int SHOW_IN_LAUNCHER_SEPARATE = 1;
+    /**
+     * Suggests that the launcher should not show this user.
+     */
+    public static final int SHOW_IN_LAUNCHER_NO = 2;
+
+    /**
+     * Reference to the default user properties for this user's user type.
+     * <li>If non-null, then any absent property will use the default property from here instead.
+     * <li>If null, then any absent property indicates that the caller lacks permission to see it,
+     *          so attempting to get that property will trigger a SecurityException.
+     */
+    private final @Nullable UserProperties mDefaultProperties;
+
+    /**
+     * Creates a UserProperties (intended for the SystemServer) that stores a reference to the given
+     * default properties, which it uses for any property not subsequently set.
+     * @hide
+     */
+    public UserProperties(@NonNull UserProperties defaultProperties) {
+        mDefaultProperties = defaultProperties;
+        mPropertiesPresent = 0;
+    }
+
+    /**
+     * Copies the given UserProperties, excluding any information that doesn't satisfy the specified
+     * permissions.
+     * Can only be used on the original version (one that won't throw on permission errors).
+     * Note that, internally, this does not perform an exact copy.
+     * @hide
+     */
+    public UserProperties(UserProperties orig,
+            boolean exposeAllFields,
+            boolean hasManagePermission,
+            boolean hasQueryPermission) {
+
+        if (orig.mDefaultProperties == null) {
+            throw new IllegalArgumentException("Attempting to copy a non-original UserProperties.");
+        }
+
+        this.mDefaultProperties = null;
+
+        // NOTE: Copy each property using getters to ensure default values are copied if needed.
+        if (exposeAllFields) {
+            setStartWithParent(orig.getStartWithParent());
+        }
+        if (hasManagePermission) {
+            // Add any items that require this permission.
+        }
+        if (hasQueryPermission) {
+            // Add any items that require this permission.
+        }
+        // Add any items that require no permissions at all.
+        setShowInLauncher(orig.getShowInLauncher());
+    }
+
+    /**
+     * Indicates that the given property is being stored explicitly in this object.
+     * If false, it means that either
+     * <li>the default property for the user type should be used instead (for SystemServer callers)
+     * <li>the caller lacks permission to see this property (for all other callers)
+     */
+    private boolean isPresent(@PropertyIndex long index) {
+        return (mPropertiesPresent & (1L << index)) != 0;
+    }
+
+    /** Indicates that the given property is henceforth being explicitly stored in this object. */
+    private void setPresent(@PropertyIndex long index) {
+        mPropertiesPresent |= (1L << index);
+    }
+
+    /** @hide Returns the internal mPropertiesPresent value. Only for testing purposes. */
+    @VisibleForTesting
+    public long getPropertiesPresent() {
+        return mPropertiesPresent;
+    }
+
+    /**
+     * Returns whether, and how, a user should be shown in the Launcher.
+     * This is generally inapplicable for non-profile users.
+     *
+     * Possible return values include
+     *    {@link #SHOW_IN_LAUNCHER_WITH_PARENT}},
+     *    {@link #SHOW_IN_LAUNCHER_SEPARATE},
+     *    and {@link #SHOW_IN_LAUNCHER_NO}.
+     *
+     * @return whether, and how, a profile should be shown in the Launcher.
+     */
+    public @ShowInLauncher int getShowInLauncher() {
+        if (isPresent(INDEX_SHOW_IN_LAUNCHER)) return mShowInLauncher;
+        if (mDefaultProperties != null) return mDefaultProperties.mShowInLauncher;
+        throw new SecurityException("You don't have permission to query showInLauncher");
+    }
+    /** @hide */
+    public void setShowInLauncher(@ShowInLauncher int val) {
+        this.mShowInLauncher = val;
+        setPresent(INDEX_SHOW_IN_LAUNCHER);
+    }
+    private @ShowInLauncher int mShowInLauncher;
+
+    /**
+     * Returns whether a profile should be started when its parent starts (unless in quiet mode).
+     * This only applies for users that have parents (i.e. for profiles).
+     * @hide
+     */
+    public boolean getStartWithParent() {
+        if (isPresent(INDEX_START_WITH_PARENT)) return mStartWithParent;
+        if (mDefaultProperties != null) return mDefaultProperties.mStartWithParent;
+        throw new SecurityException("You don't have permission to query startWithParent");
+    }
+    /** @hide */
+    public void setStartWithParent(boolean val) {
+        this.mStartWithParent = val;
+        setPresent(INDEX_START_WITH_PARENT);
+    }
+    private boolean mStartWithParent;
+
+    @Override
+    public String toString() {
+        // Please print in increasing order of PropertyIndex.
+        return "UserProperties{"
+                + "mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent)
+                + ", mShowInLauncher=" + getShowInLauncher()
+                + ", mStartWithParent=" + getStartWithParent()
+                + "}";
+    }
+
+    /**
+     * Print the UserProperties to the given PrintWriter.
+     * @hide
+     */
+    public void println(PrintWriter pw, String prefix) {
+        // Please print in increasing order of PropertyIndex.
+        pw.println(prefix + "UserProperties:");
+        pw.println(prefix + "    mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent));
+        pw.println(prefix + "    mShowInLauncher=" + getShowInLauncher());
+        pw.println(prefix + "    mStartWithParent=" + getStartWithParent());
+    }
+
+    /**
+     * Reads in a UserProperties from an xml file, for use by the SystemServer.
+     *
+     * The serializer should already be inside a tag from which to read the user properties.
+     *
+     * @param defaultUserPropertiesReference the default UserProperties to use for this user type.
+     * @see #writeToXml
+     * @hide
+     */
+    public UserProperties(
+            TypedXmlPullParser parser,
+            @NonNull UserProperties defaultUserPropertiesReference)
+            throws IOException, XmlPullParserException {
+
+        this(defaultUserPropertiesReference);
+        updateFromXml(parser);
+    }
+
+    /**
+     * Parses the given xml file and updates this UserProperties with its data.
+     * I.e., if a piece of data is present in the xml, it will overwrite whatever was
+     * previously stored in this UserProperties.
+     * @hide
+     */
+    public void updateFromXml(TypedXmlPullParser parser)
+            throws IOException, XmlPullParserException {
+
+        final int attributeCount = parser.getAttributeCount();
+        for (int i = 0; i < attributeCount; i++) {
+            final String attributeName = parser.getAttributeName(i);
+            switch(attributeName) {
+                case ATTR_SHOW_IN_LAUNCHER:
+                    setShowInLauncher(parser.getAttributeInt(i));
+                    break;
+                case ATTR_START_WITH_PARENT:
+                    setStartWithParent(parser.getAttributeBoolean(i));
+                    break;
+                default:
+                    Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
+            }
+        }
+    }
+
+    /**
+     * Writes the UserProperties, as used by the SystemServer, to the xml file.
+     *
+     * The serializer should already be inside a tag in which to write the user properties.
+     *
+     * @see  #UserProperties(TypedXmlPullParser, UserProperties)
+     * @hide
+     */
+    public void writeToXml(TypedXmlSerializer serializer)
+            throws IOException, XmlPullParserException {
+
+        if (isPresent(INDEX_SHOW_IN_LAUNCHER)) {
+            serializer.attributeInt(null, ATTR_SHOW_IN_LAUNCHER, mShowInLauncher);
+        }
+        if (isPresent(INDEX_START_WITH_PARENT)) {
+            serializer.attributeBoolean(null, ATTR_START_WITH_PARENT, mStartWithParent);
+        }
+    }
+
+    // For use only with an object that has already had any permission-lacking fields stripped out.
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int parcelableFlags) {
+        dest.writeLong(mPropertiesPresent);
+        dest.writeInt(mShowInLauncher);
+        dest.writeBoolean(mStartWithParent);
+    }
+
+    /**
+     * Reads a UserProperties object from the parcel.
+     * Not suitable for the canonical SystemServer version since it lacks mDefaultProperties.
+      */
+    private UserProperties(@NonNull Parcel source) {
+        mDefaultProperties = null;
+
+        mPropertiesPresent = source.readLong();
+        mShowInLauncher = source.readInt();
+        mStartWithParent = source.readBoolean();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<UserProperties> CREATOR
+            = new Parcelable.Creator<UserProperties>() {
+        public UserProperties createFromParcel(Parcel source) {
+            return new UserProperties(source);
+        }
+        public UserProperties[] newArray(int size) {
+            return new UserProperties[size];
+        }
+    };
+
+    /**
+     * Builder for the SystemServer's {@link UserProperties}; see that class for documentation.
+     * Intended for building default values (and so all properties are present in the built object).
+     * @hide
+     */
+    public static final class Builder {
+        // UserProperties fields and their default values.
+        private @ShowInLauncher int mShowInLauncher = SHOW_IN_LAUNCHER_WITH_PARENT;
+        private boolean mStartWithParent = false;
+
+        public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
+            mShowInLauncher = showInLauncher;
+            return this;
+        }
+
+        public Builder setStartWithParent(boolean startWithParent) {
+            mStartWithParent = startWithParent;
+            return this;
+        }
+
+        /** Builds a UserProperties object with *all* values populated. */
+        public UserProperties build() {
+            return new UserProperties(
+                    mShowInLauncher,
+                    mStartWithParent);
+        }
+    } // end Builder
+
+    /** Creates a UserProperties with the given properties. Intended for building default values. */
+    private UserProperties(
+            @ShowInLauncher int showInLauncher,
+            boolean startWithParent) {
+
+        mDefaultProperties = null;
+        setShowInLauncher(showInLauncher);
+        setStartWithParent(startWithParent);
+    }
+}
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index 1e4c9501..11892fe 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -188,9 +188,6 @@
     public static HardwareBuffer create(
             @IntRange(from = 1) int width, @IntRange(from = 1) int height,
             @Format int format, @IntRange(from = 1) int layers, @Usage long usage) {
-        if (!HardwareBuffer.isSupportedFormat(format)) {
-            throw new IllegalArgumentException("Invalid pixel format " + format);
-        }
         if (width <= 0) {
             throw new IllegalArgumentException("Invalid width " + width);
         }
@@ -226,9 +223,6 @@
      */
     public static boolean isSupported(@IntRange(from = 1) int width, @IntRange(from = 1) int height,
             @Format int format, @IntRange(from = 1) int layers, @Usage long usage) {
-        if (!HardwareBuffer.isSupportedFormat(format)) {
-            throw new IllegalArgumentException("Invalid pixel format " + format);
-        }
         if (width <= 0) {
             throw new IllegalArgumentException("Invalid width " + width);
         }
@@ -286,10 +280,7 @@
      * Returns the width of this buffer in pixels.
      */
     public int getWidth() {
-        if (isClosed()) {
-            throw new IllegalStateException("This HardwareBuffer has been closed and its width "
-                    + "cannot be obtained.");
-        }
+        checkClosed("width");
         return nGetWidth(mNativeObject);
     }
 
@@ -297,10 +288,7 @@
      * Returns the height of this buffer in pixels.
      */
     public int getHeight() {
-        if (isClosed()) {
-            throw new IllegalStateException("This HardwareBuffer has been closed and its height "
-                    + "cannot be obtained.");
-        }
+        checkClosed("height");
         return nGetHeight(mNativeObject);
     }
 
@@ -309,10 +297,7 @@
      */
     @Format
     public int getFormat() {
-        if (isClosed()) {
-            throw new IllegalStateException("This HardwareBuffer has been closed and its format "
-                    + "cannot be obtained.");
-        }
+        checkClosed("format");
         return nGetFormat(mNativeObject);
     }
 
@@ -320,10 +305,7 @@
      * Returns the number of layers in this buffer.
      */
     public int getLayers() {
-        if (isClosed()) {
-            throw new IllegalStateException("This HardwareBuffer has been closed and its layer "
-                    + "count cannot be obtained.");
-        }
+        checkClosed("layer count");
         return nGetLayers(mNativeObject);
     }
 
@@ -331,14 +313,27 @@
      * Returns the usage flags of the usage hints set on this buffer.
      */
     public long getUsage() {
-        if (isClosed()) {
-            throw new IllegalStateException("This HardwareBuffer has been closed and its usage "
-                    + "cannot be obtained.");
-        }
+        checkClosed("usage");
         return nGetUsage(mNativeObject);
     }
 
     /**
+     * Returns the system-wide unique id for this buffer
+     *
+     */
+    public long getId() {
+        checkClosed("id");
+        return nGetId(mNativeObject);
+    }
+
+    private void checkClosed(String name) {
+        if (isClosed()) {
+            throw new IllegalStateException("This HardwareBuffer has been closed and its "
+                    + name + " cannot be obtained.");
+        }
+    }
+
+    /**
      * Destroys this buffer immediately. Calling this method frees up any
      * underlying native resources. After calling this method, this buffer
      * must not be used in any way.
@@ -407,36 +402,6 @@
         }
     };
 
-    /**
-     * Validates whether a particular format is supported by HardwareBuffer.
-     *
-     * @param format The format to validate.
-     *
-     * @return True if <code>format</code> is a supported format. false otherwise.
-     * See {@link #create(int, int, int, int, long)}.
-     */
-    private static boolean isSupportedFormat(@Format int format) {
-        switch(format) {
-            case RGBA_8888:
-            case RGBA_FP16:
-            case RGBA_1010102:
-            case RGBX_8888:
-            case RGB_565:
-            case RGB_888:
-            case BLOB:
-            case YCBCR_420_888:
-            case D_16:
-            case D_24:
-            case DS_24UI8:
-            case D_FP32:
-            case DS_FP32UI8:
-            case S_UI8:
-            case YCBCR_P010:
-                return true;
-        }
-        return false;
-    }
-
     private static native long nCreateHardwareBuffer(int width, int height, int format, int layers,
             long usage);
     private static native long nCreateFromGraphicBuffer(GraphicBuffer graphicBuffer);
@@ -457,4 +422,6 @@
             long usage);
     @CriticalNative
     private static native long nEstimateSize(long nativeObject);
+    @CriticalNative
+    private static native long nGetId(long nativeObject);
 }
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/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index a9d665c8..621eab5 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1963,7 +1963,6 @@
     }
 
     private static Object mServiceLock = new Object();
-    private static ISoundTriggerMiddlewareService mService;
 
     /**
      * Translate an exception thrown from interaction with the underlying service to an error code.
@@ -2217,20 +2216,12 @@
                     binder =
                             ServiceManager.getServiceOrThrow(
                                     Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE);
-                    binder.linkToDeath(() -> {
-                        synchronized (mServiceLock) {
-                            mService = null;
-                        }
-                    }, 0);
-                    mService = ISoundTriggerMiddlewareService.Stub.asInterface(binder);
-                    break;
+                    return ISoundTriggerMiddlewareService.Stub.asInterface(binder);
                 } catch (Exception e) {
                     Log.e(TAG, "Failed to bind to soundtrigger service", e);
                 }
             }
-            return  mService;
         }
-
     }
 
     /**
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/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index f69d6b0..88649cb 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -23,6 +23,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
 import android.content.IntentSender;
 import android.content.RestrictionEntry;
 import android.graphics.Bitmap;
@@ -71,9 +72,11 @@
     boolean isUserOfType(int userId, in String userType);
     @UnsupportedAppUsage
     UserInfo getUserInfo(int userId);
+    UserProperties getUserPropertiesCopy(int userId);
     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);
@@ -126,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/UserManager.java b/core/java/android/os/UserManager.java
index 16352b8..f6aaee8 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -51,6 +51,7 @@
 import android.content.IntentSender;
 import android.content.pm.UserInfo;
 import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.pm.UserProperties;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -1994,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();
     }
 
@@ -2786,7 +2788,7 @@
         return isUserRunning(user.getIdentifier());
     }
 
-    /** {@hide} */
+    /** @hide */
     @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
             Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
     public boolean isUserRunning(@UserIdInt int userId) {
@@ -2841,6 +2843,7 @@
     /**
      * @hide
      */
+    @TestApi
     public static boolean isUsersOnSecondaryDisplaysEnabled() {
         return SystemProperties.getBoolean("fw.users_on_secondary_displays",
                 Resources.getSystem()
@@ -2883,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
@@ -2968,7 +2986,7 @@
                 }
             };
 
-    /** {@hide} */
+    /** @hide */
     @UnsupportedAppUsage
     @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
             Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
@@ -2976,13 +2994,13 @@
         return mIsUserUnlockedCache.query(userId);
     }
 
-    /** {@hide} */
+    /** @hide */
     public void disableIsUserUnlockedCache() {
         mIsUserUnlockedCache.disableLocal();
         mIsUserUnlockingOrUnlockedCache.disableLocal();
     }
 
-    /** {@hide} */
+    /** @hide */
     public static final void invalidateIsUserUnlockedCache() {
         PropertyInvalidatedCache.invalidateCache(CACHE_KEY_IS_USER_UNLOCKED_PROPERTY);
     }
@@ -3012,7 +3030,7 @@
         return isUserUnlockingOrUnlocked(user.getIdentifier());
     }
 
-    /** {@hide} */
+    /** @hide */
     @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
             Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
     public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
@@ -3083,6 +3101,30 @@
     }
 
     /**
+     * Returns a {@link UserProperties} object describing the properties of the given user.
+     *
+     * Note that the caller may not have permission to access all items; requesting any item for
+     * which permission is lacking will throw a {@link SecurityException}.
+     *
+     * <p> Requires
+     * {@code android.Manifest.permission#MANAGE_USERS},
+     * {@code android.Manifest.permission#QUERY_USERS}, or
+     * {@code android.Manifest.permission#INTERACT_ACROSS_USERS}
+     * permission, or else the caller must be in the same profile group as the caller.
+     *
+     * @param userHandle the user handle of the user whose information is being requested.
+     * @return a UserProperties object for a specific user.
+     * @throws IllegalArgumentException if {@code userHandle} doesn't correspond to an existing user
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.QUERY_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+    public @NonNull UserProperties getUserProperties(@NonNull UserHandle userHandle) {
+        return mUserPropertiesCache.query(userHandle.getIdentifier());
+    }
+
+    /**
      * @hide
      *
      * Returns who set a user restriction on a user.
@@ -5220,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?
@@ -5480,11 +5512,37 @@
                 }
             };
 
-    /** {@hide} */
+    /** @hide */
     public static final void invalidateStaticUserProperties() {
         PropertyInvalidatedCache.invalidateCache(CACHE_KEY_STATIC_USER_PROPERTIES);
     }
 
+    /* Cache key for UserProperties object. */
+    private static final String CACHE_KEY_USER_PROPERTIES = "cache_key.user_properties";
+
+    // TODO: It would be better to somehow have this as static, so that it can work cross-context.
+    private final PropertyInvalidatedCache<Integer, UserProperties> mUserPropertiesCache =
+            new PropertyInvalidatedCache<Integer, UserProperties>(16, CACHE_KEY_USER_PROPERTIES) {
+                @Override
+                public UserProperties recompute(Integer userId) {
+                    try {
+                        // If the userId doesn't exist, this will throw rather than cache garbage.
+                        return mService.getUserPropertiesCopy(userId);
+                    } catch (RemoteException re) {
+                        throw re.rethrowFromSystemServer();
+                    }
+                }
+                @Override
+                public boolean bypass(Integer query) {
+                    return query < 0;
+                }
+            };
+
+    /** @hide */
+    public static final void invalidateUserPropertiesCache() {
+        PropertyInvalidatedCache.invalidateCache(CACHE_KEY_USER_PROPERTIES);
+    }
+
     /**
      * @hide
      * User that enforces a restriction.
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 2483f99..b7adcb8 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -755,6 +755,13 @@
     public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native";
 
     /**
+     * Namespace for Vendor System Native Boot related features.
+     *
+     * @hide
+     */
+    public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT = "vendor_system_native_boot";
+
+    /**
      * Namespace for memory safety related features (e.g. MTE)
      *
      * @hide
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 05b3925..14598d5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5486,6 +5486,15 @@
         public static final String MULTI_AUDIO_FOCUS_ENABLED = "multi_audio_focus_enabled";
 
         /**
+         * Whether desktop mode is enabled or not.
+         * 0 = off
+         * 1 = on
+         * @hide
+         */
+        @Readable
+        public static final String DESKTOP_MODE = "desktop_mode";
+
+        /**
          * IMPORTANT: If you add a new public settings you also have to add it to
          * PUBLIC_SETTINGS below. If the new setting is hidden you have to add
          * it to PRIVATE_SETTINGS below. Also add a validator that can validate
@@ -5614,6 +5623,7 @@
             PRIVATE_SETTINGS.add(SHOW_BATTERY_PERCENT);
             PRIVATE_SETTINGS.add(DISPLAY_COLOR_MODE);
             PRIVATE_SETTINGS.add(DISPLAY_COLOR_MODE_VENDOR_HINT);
+            PRIVATE_SETTINGS.add(DESKTOP_MODE);
         }
 
         /**
@@ -7812,6 +7822,16 @@
                 "accessibility_display_magnification_auto_update";
 
         /**
+         * Accessibility Window Magnification Allow diagonal scrolling value. The value is boolean.
+         * 1 : on, 0 : off
+         *
+         * @hide
+         */
+        public static final String ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING =
+                "accessibility_allow_diagonal_scrolling";
+
+
+        /**
          * Setting that specifies what mode the soft keyboard is in (default or hidden). Can be
          * modified from an AccessibilityService using the SoftKeyboardController.
          *
@@ -9167,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";
 
 
         /**
@@ -10641,6 +10659,13 @@
         public static final String MEDIA_CONTROLS_RESUME = "qs_media_resumption";
 
         /**
+         * Whether to enable media controls on lock screen.
+         * When enabled, media controls will appear on lock screen.
+         * @hide
+         */
+        public static final String MEDIA_CONTROLS_LOCK_SCREEN = "media_controls_lock_screen";
+
+        /**
          * Controls whether contextual suggestions can be shown in the media controls.
          * @hide
          */
@@ -10821,6 +10846,23 @@
                 "accessibility_software_cursor_enabled";
 
         /**
+         * Software Cursor settings that specifies whether trigger hints are enabled.
+         *
+         * @hide
+         */
+        public static final String ACCESSIBILITY_SOFTWARE_CURSOR_TRIGGER_HINTS_ENABLED =
+                "accessibility_software_cursor_trigger_hints_enabled";
+
+        /**
+         * Software Cursor settings that specifies whether triggers are shifted when the keyboard
+         * is shown.
+         *
+         * @hide
+         */
+        public static final String ACCESSIBILITY_SOFTWARE_CURSOR_KEYBOARD_SHIFT_ENABLED =
+                "accessibility_software_cursor_keyboard_shift_enabled";
+
+        /**
          * Whether the Adaptive connectivity option is enabled.
          *
          * @hide
@@ -17688,20 +17730,13 @@
                     "wear_activity_auto_resume_timeout_set_by_user";
 
             /**
-             * The maximum times that we are allowed to reset the activity auto-resume timeout
-             * timer.
-             * @hide
-             */
-            public static final String WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_MAX_RESET_COUNT =
-                    "wear_activity_auto_resume_timeout_max_reset_count";
-
-            /**
              * If burn in protection is enabled.
              * @hide
              */
             public static final String BURN_IN_PROTECTION_ENABLED = "burn_in_protection";
 
             /**
+
              * Whether the device has combined location setting enabled.
              * @hide
              */
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 3e0deeb..53ae657 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -35,6 +35,8 @@
     void testDream(int userId, in ComponentName componentName);
     @UnsupportedAppUsage
     boolean isDreaming();
+    @UnsupportedAppUsage
+    boolean isDreamingOrInPreview();
     void finishSelf(in IBinder token, boolean immediate);
     void startDozing(in IBinder token, int screenState, int screenBrightness);
     void stopDozing(in IBinder token);
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/DefaultSelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
index f028ed3..ad73a53 100644
--- a/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
+++ b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
@@ -69,7 +69,7 @@
 
         if (mToolbarCache.indexOfKey(callingUid) < 0) {
             RemoteSelectionToolbar toolbar = new RemoteSelectionToolbar(this,
-                    widgetToken, showInfo.getHostInputToken(),
+                    widgetToken, showInfo,
                     callbackWrapper, this::transferTouch);
             mToolbarCache.put(callingUid, new Pair<>(widgetToken, toolbar));
         }
diff --git a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
index d75fbc0..9292e96 100644
--- a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
+++ b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
@@ -22,7 +22,6 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.content.Context;
-import android.content.res.TypedArray;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.AnimatedVectorDrawable;
@@ -162,15 +161,14 @@
     private final Rect mTempContentRectForRoot = new Rect();
     private final int[] mTempCoords = new int[2];
 
-    RemoteSelectionToolbar(Context context, long selectionToolbarToken, IBinder hostInputToken,
+    RemoteSelectionToolbar(Context context, long selectionToolbarToken, ShowInfo showInfo,
             SelectionToolbarRenderService.RemoteCallbackWrapper callbackWrapper,
             SelectionToolbarRenderService.TransferTouchListener transferTouchListener) {
-        mContext = applyDefaultTheme(context);
+        mContext = applyDefaultTheme(context, showInfo.isIsLightTheme());
         mSelectionToolbarToken = selectionToolbarToken;
         mCallbackWrapper = callbackWrapper;
         mTransferTouchListener = transferTouchListener;
-        mHostInputToken = hostInputToken;
-
+        mHostInputToken = showInfo.getHostInputToken();
         mContentContainer = createContentContainer(mContext);
         mMarginHorizontal = mContext.getResources()
                 .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
@@ -1319,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;
     }
@@ -1359,12 +1356,9 @@
     /**
      * Returns a re-themed context with controlled look and feel for views.
      */
-    private static Context applyDefaultTheme(Context originalContext) {
-        TypedArray a = originalContext.obtainStyledAttributes(new int[]{R.attr.isLightTheme});
-        boolean isLightTheme = a.getBoolean(0, true);
+    private static Context applyDefaultTheme(Context originalContext, boolean isLightTheme) {
         int themeId =
                 isLightTheme ? R.style.Theme_DeviceDefault_Light : R.style.Theme_DeviceDefault;
-        a.recycle();
         return new ContextThemeWrapper(originalContext, themeId);
     }
 
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index 7234145..ab71459 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -95,6 +95,16 @@
     /** Limits the max value for the triggered audio channel. */
     private static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE = 63;
 
+    /**
+     * The bundle key for proximity value
+     *
+     * TODO(b/238896013): Move the proximity logic out of bundle to proper API.
+     *
+     * @hide
+     */
+    public static final String EXTRA_PROXIMITY_METERS =
+            "android.service.voice.extra.PROXIMITY_METERS";
+
     /** Confidence level in the trigger outcome. */
     @HotwordConfidenceLevelValue
     private final int mConfidenceLevel;
@@ -197,6 +207,14 @@
      * <p>The use of this method is discouraged, and support for it will be removed in future
      * versions of Android.
      *
+     * <p>After the trigger happens, a special case of proximity-related extra, with the key of
+     * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
+     * will be stored to enable proximity logic. The proximity meters is provided by the system,
+     * on devices that support detecting proximity of nearby users, to help disambiguate which
+     * nearby device should respond. When the proximity is unknown, the proximity value will not
+     * be stored. This mapping will be excluded from the max bundle size calculation because this
+     * mapping is included after the result is returned from the hotword detector service.
+     *
      * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
      * that can be used to communicate with other processes.
      */
@@ -315,8 +333,23 @@
                     "audioChannel");
         }
         if (!mExtras.isEmpty()) {
-            Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0, getMaxBundleSize(),
-                    "extras");
+            // Remove the proximity key from the bundle before checking the bundle size. The
+            // proximity value is added after the privileged module and can avoid the
+            // maxBundleSize limitation.
+            if (mExtras.containsKey(EXTRA_PROXIMITY_METERS)) {
+                double proximityMeters = mExtras.getDouble(EXTRA_PROXIMITY_METERS);
+                mExtras.remove(EXTRA_PROXIMITY_METERS);
+                // Skip checking parcelable size if the new bundle size is 0. Newly empty bundle
+                // has parcelable size of 4, but the default bundle has parcelable size of 0.
+                if (mExtras.size() > 0) {
+                    Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
+                            getMaxBundleSize(), "extras");
+                }
+                mExtras.putDouble(EXTRA_PROXIMITY_METERS, proximityMeters);
+            } else {
+                Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
+                        getMaxBundleSize(), "extras");
+            }
         }
     }
 
@@ -513,6 +546,14 @@
      * <p>The use of this method is discouraged, and support for it will be removed in future
      * versions of Android.
      *
+     * <p>After the trigger happens, a special case of proximity-related extra, with the key of
+     * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
+     * will be stored to enable proximity logic. The proximity meters is provided by the system,
+     * on devices that support detecting proximity of nearby users, to help disambiguate which
+     * nearby device should respond. When the proximity is unknown, the proximity value will not
+     * be stored. This mapping will be excluded from the max bundle size calculation because this
+     * mapping is included after the result is returned from the hotword detector service.
+     *
      * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
      * that can be used to communicate with other processes.
      */
@@ -813,6 +854,14 @@
          * <p>The use of this method is discouraged, and support for it will be removed in future
          * versions of Android.
          *
+         * <p>After the trigger happens, a special case of proximity-related extra, with the key of
+         * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
+         * will be stored to enable proximity logic. The proximity meters is provided by the system,
+         * on devices that support detecting proximity of nearby users, to help disambiguate which
+         * nearby device should respond. When the proximity is unknown, the proximity value will not
+         * be stored. This mapping will be excluded from the max bundle size calculation because this
+         * mapping is included after the result is returned from the hotword detector service.
+         *
          * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
          * that can be used to communicate with other processes.
          */
@@ -882,10 +931,10 @@
     }
 
     @DataClass.Generated(
-            time = 1625541522353L,
+            time = 1658357814396L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
-            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final  java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 4fbf4b5..bc8822c 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1153,8 +1153,8 @@
                         mLayout.surfaceInsets.set(0, 0, 0, 0);
                     }
                     final int relayoutResult = mSession.relayout(mWindow, mLayout, mWidth, mHeight,
-                            View.VISIBLE, 0, mWinFrames, mMergedConfiguration, mSurfaceControl,
-                            mInsetsState, mTempControls, mSyncSeqIdBundle);
+                            View.VISIBLE, 0, 0, 0, mWinFrames, mMergedConfiguration,
+                            mSurfaceControl, mInsetsState, mTempControls, mSyncSeqIdBundle);
 
                     final int transformHint = SurfaceControl.rotationToBufferTransform(
                             (mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index eae951f..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
@@ -115,6 +111,11 @@
     public static final String SETTINGS_ACCESSIBILITY_SIMPLE_CURSOR =
             "settings_accessibility_simple_cursor";
 
+    /**
+     * Enable new language and keyboard settings UI
+     * @hide
+     */
+    public static final String SETTINGS_NEW_KEYBOARD_UI = "settings_new_keyboard_ui";
 
     private static final Map<String, String> DEFAULT_FLAGS;
 
@@ -145,9 +146,9 @@
         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");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
@@ -161,6 +162,7 @@
         PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME);
         PERSISTENT_FLAGS.add(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE);
         PERSISTENT_FLAGS.add(SETTINGS_AUTO_TEXT_WRAPPING);
+        PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_UI);
     }
 
     /**
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 52dc342..a6f63e8 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -89,7 +89,9 @@
 
     // Callbacks should have the same configuration of the flags below to allow satisfying a pending
     // node request on prefetch
-    private static final int FLAGS_AFFECTING_REPORTED_DATA = AccessibilityNodeInfo.FLAG_REPORT_MASK;
+    private static final int FLAGS_AFFECTING_REPORTED_DATA =
+            AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+            | AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
 
     private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
         new ArrayList<AccessibilityNodeInfo>();
@@ -165,11 +167,6 @@
         return (view != null) && (view.getWindowVisibility() == View.VISIBLE && view.isShown());
     }
 
-    private boolean isVisibleToAccessibilityService(View view) {
-        return view != null && (!view.isAccessibilityDataPrivate()
-                || mA11yManager.isRequestFromAccessibilityTool());
-    }
-
     public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
             long accessibilityNodeId, Region interactiveRegion, int interactionId,
             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
@@ -361,7 +358,7 @@
             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
                 return;
             }
-            setAccessibilityFetchFlags(flags);
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
             requestedView = findViewByAccessibilityId(accessibilityViewId);
             if (requestedView != null && isShown(requestedView)) {
                 requestedNode = populateAccessibilityNodeInfoForView(
@@ -374,7 +371,7 @@
                     mPrefetcher.prefetchAccessibilityNodeInfos(requestedView,
                             requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode),
                             infos);
-                    resetAccessibilityFetchFlags();
+                    mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
                 }
             }
         } finally {
@@ -399,7 +396,7 @@
         }
         mPrefetcher.prefetchAccessibilityNodeInfos(requestedView,
                 requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode), infos);
-        resetAccessibilityFetchFlags();
+        mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
         updateInfosForViewPort(infos, spec, matrixValues, interactiveRegion);
         final SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest =
                 getSatisfiedRequestInPrefetch(requestedNode == null ? null : requestedNode, infos,
@@ -481,7 +478,7 @@
                     || viewId == null) {
                 return;
             }
-            setAccessibilityFetchFlags(flags);
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
             final View root = findViewByAccessibilityId(accessibilityViewId);
             if (root != null) {
                 final int resolvedViewId = root.getContext().getResources()
@@ -497,7 +494,7 @@
                 mAddNodeInfosForViewId.reset();
             }
         } finally {
-            resetAccessibilityFetchFlags();
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
             updateInfosForViewportAndReturnFindNodeResult(
                     infos, callback, interactionId, spec, matrixValues, interactiveRegion);
         }
@@ -545,7 +542,7 @@
             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
                 return;
             }
-            setAccessibilityFetchFlags(flags);
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
             final View root = findViewByAccessibilityId(accessibilityViewId);
             if (root != null && isShown(root)) {
                 AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
@@ -564,7 +561,7 @@
                         final int viewCount = foundViews.size();
                         for (int i = 0; i < viewCount; i++) {
                             View foundView = foundViews.get(i);
-                            if (isShown(foundView) && isVisibleToAccessibilityService(foundView)) {
+                            if (isShown(foundView)) {
                                 provider = foundView.getAccessibilityNodeProvider();
                                 if (provider != null) {
                                     List<AccessibilityNodeInfo> infosFromProvider =
@@ -582,7 +579,7 @@
                 }
             }
         } finally {
-            resetAccessibilityFetchFlags();
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
             updateInfosForViewportAndReturnFindNodeResult(
                     infos, callback, interactionId, spec, matrixValues, interactiveRegion);
         }
@@ -630,7 +627,7 @@
             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
                 return;
             }
-            setAccessibilityFetchFlags(flags);
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
             final View root = findViewByAccessibilityId(accessibilityViewId);
             if (root != null && isShown(root)) {
                 switch (focusType) {
@@ -645,9 +642,6 @@
                         if (!isShown(host)) {
                             break;
                         }
-                        if (!isVisibleToAccessibilityService(host)) {
-                            break;
-                        }
                         // If the host has a provider ask this provider to search for the
                         // focus instead fetching all provider nodes to do the search here.
                         AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
@@ -668,9 +662,6 @@
                         if (!isShown(target)) {
                             break;
                         }
-                        if (!isVisibleToAccessibilityService(target)) {
-                            break;
-                        }
                         AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
                         if (provider != null) {
                             focused = provider.findFocus(focusType);
@@ -684,7 +675,7 @@
                 }
             }
         } finally {
-            resetAccessibilityFetchFlags();
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
             updateInfoForViewportAndReturnFindNodeResult(
                     focused, callback, interactionId, spec, matrixValues, interactiveRegion);
         }
@@ -731,7 +722,7 @@
             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
                 return;
             }
-            setAccessibilityFetchFlags(flags);
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
             final View root = findViewByAccessibilityId(accessibilityViewId);
             if (root != null && isShown(root)) {
                 View nextView = root.focusSearch(direction);
@@ -740,7 +731,7 @@
                 }
             }
         } finally {
-            resetAccessibilityFetchFlags();
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
             updateInfoForViewportAndReturnFindNodeResult(
                     next, callback, interactionId, spec, matrixValues, interactiveRegion);
         }
@@ -787,9 +778,9 @@
                     mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) {
                 return;
             }
-            setAccessibilityFetchFlags(flags);
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
             final View target = findViewByAccessibilityId(accessibilityViewId);
-            if (target != null && isShown(target) && isVisibleToAccessibilityService(target)) {
+            if (target != null && isShown(target)) {
                 mA11yManager.notifyPerformingAction(action);
                 if (action == R.id.accessibilityActionClickOnClickableSpan) {
                     // Handle this hidden action separately
@@ -808,7 +799,7 @@
             }
         } finally {
             try {
-                resetAccessibilityFetchFlags();
+                mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
                 callback.setPerformAccessibilityActionResult(succeeded, interactionId);
             } catch (RemoteException re) {
                 /* ignore - the other side will time out */
@@ -832,9 +823,9 @@
             return;
         }
         try {
-            setAccessibilityFetchFlags(
-                    AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS);
-            final View root = getRootView();
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags =
+                    AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+            final View root = mViewRootImpl.mView;
             if (root != null && isShown(root)) {
                 final View host = mViewRootImpl.mAccessibilityFocusedHost;
                 // If there is no accessibility focus host or it is not a descendant
@@ -858,7 +849,7 @@
                 }
             }
         } finally {
-            resetAccessibilityFetchFlags();
+            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
         }
     }
 
@@ -878,7 +869,7 @@
                 || mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) {
             return;
         }
-        final View root = getRootView();
+        final View root = mViewRootImpl.mView;
         if (root != null && isShown(root)) {
             // trigger ACTION_OUTSIDE to notify windows
             final long now = SystemClock.uptimeMillis();
@@ -891,30 +882,12 @@
 
     private View findViewByAccessibilityId(int accessibilityId) {
         if (accessibilityId == AccessibilityNodeInfo.ROOT_ITEM_ID) {
-            return getRootView();
+            return mViewRootImpl.mView;
         } else {
             return AccessibilityNodeIdManager.getInstance().findView(accessibilityId);
         }
     }
 
-    private View getRootView() {
-        if (!isVisibleToAccessibilityService(mViewRootImpl.mView)) {
-            return null;
-        }
-        return mViewRootImpl.mView;
-    }
-
-    private void setAccessibilityFetchFlags(int flags) {
-        mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
-        mA11yManager.setRequestFromAccessibilityTool(
-                (flags & AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL) != 0);
-    }
-
-    private void resetAccessibilityFetchFlags() {
-        mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
-        mA11yManager.setRequestFromAccessibilityTool(false);
-    }
-
     // The boundInScreen includes magnification effect, so we need to normalize it before
     // determine the visibility.
     private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info,
@@ -1733,7 +1706,7 @@
 
         @Override
         public boolean test(View view) {
-            if (view.getId() == mViewId && isShown(view) && isVisibleToAccessibilityService(view)) {
+            if (view.getId() == mViewId && isShown(view)) {
                 mInfos.add(view.createAccessibilityNodeInfo());
             }
             return 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/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 3016473..afcec66 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -75,41 +75,42 @@
      * @param requestedWidth The width the window wants to be.
      * @param requestedHeight The height the window wants to be.
      * @param viewVisibility Window root view's visibility.
-     * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING},
-     * {@link WindowManagerGlobal#RELAYOUT_DEFER_SURFACE_DESTROY}.
-     * @param outFrame Rect in which is placed the new position/size on
-     * screen.
-     * @param outContentInsets Rect in which is placed the offsets from
-     * <var>outFrame</var> in which the content of the window should be
-     * placed.  This can be used to modify the window layout to ensure its
-     * contents are visible to the user, taking into account system windows
-     * like the status bar or a soft keyboard.
-     * @param outVisibleInsets Rect in which is placed the offsets from
-     * <var>outFrame</var> in which the window is actually completely visible
-     * to the user.  This can be used to temporarily scroll the window's
-     * contents to make sure the user can see it.  This is different than
-     * <var>outContentInsets</var> in that these insets change transiently,
-     * so complex relayout of the window should not happen based on them.
-     * @param outOutsets Rect in which is placed the dead area of the screen that we would like to
-     * treat as real display. Example of such area is a chin in some models of wearable devices.
-     * @param outBackdropFrame Rect which is used draw the resizing background during a resize
-     * operation.
+     * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING}.
+     * @param seq The calling sequence of {@link #relayout} and {@link #relayoutAsync}.
+     * @param lastSyncSeqId The last SyncSeqId that the client applied.
+     * @param outFrames The window frames used by the client side for layout.
      * @param outMergedConfiguration New config container that holds global, override and merged
-     * config for window, if it is now becoming visible and the merged configuration has changed
-     * since it was last displayed.
-     * @param outSurface Object in which is placed the new display surface.
+     *                               config for window, if it is now becoming visible and the merged
+     *                               config has changed since it was last displayed.
+     * @param outSurfaceControl Object in which is placed the new display surface.
      * @param insetsState The current insets state in the system.
-     *
-     * @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_SHOW_FOCUS},
-     * {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}.
+     * @param activeControls Objects which allow controlling {@link InsetsSource}s.
+     * @param bundle A temporary object to obtain the latest SyncSeqId.
+     * @return int Result flags, defined in {@link WindowManagerGlobal}.
      */
     int relayout(IWindow window, in WindowManager.LayoutParams attrs,
             int requestedWidth, int requestedHeight, int viewVisibility,
-            int flags, out ClientWindowFrames outFrames,
+            int flags, int seq, int lastSyncSeqId, out ClientWindowFrames outFrames,
             out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl,
             out InsetsState insetsState, out InsetsSourceControl[] activeControls,
             out Bundle bundle);
 
+    /**
+     * Similar to {@link #relayout} but this is an oneway method which doesn't return anything.
+     *
+     * @param window The window being modified.
+     * @param attrs If non-null, new attributes to apply to the window.
+     * @param requestedWidth The width the window wants to be.
+     * @param requestedHeight The height the window wants to be.
+     * @param viewVisibility Window root view's visibility.
+     * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING}.
+     * @param seq The calling sequence of {@link #relayout} and {@link #relayoutAsync}.
+     * @param lastSyncSeqId The last SyncSeqId that the client applied.
+     */
+    oneway void relayoutAsync(IWindow window, in WindowManager.LayoutParams attrs,
+            int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,
+            int lastSyncSeqId);
+
     /*
      * Notify the window manager that an application is relaunching and
      * windows should be prepared for replacement.
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index a2cb1d5..4a72a62 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -314,6 +314,9 @@
             (int) (startValue.right + fraction * (endValue.right - startValue.right)),
             (int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom)));
 
+    /** Logging listener. */
+    private WindowInsetsAnimationControlListener mLoggingListener;
+
     /**
      * The default implementation of listener, to be used by InsetsController and InsetsPolicy to
      * animate insets.
@@ -330,6 +333,7 @@
         private final long mDurationMs;
         private final boolean mDisable;
         private final int mFloatingImeBottomInset;
+        private final WindowInsetsAnimationControlListener mLoggingListener;
 
         private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
                 new ThreadLocal<AnimationHandler>() {
@@ -343,7 +347,7 @@
 
         public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks,
                 @InsetsType int requestedTypes, @Behavior int behavior, boolean disable,
-                int floatingImeBottomInset) {
+                int floatingImeBottomInset, WindowInsetsAnimationControlListener loggingListener) {
             mShow = show;
             mHasAnimationCallbacks = hasAnimationCallbacks;
             mRequestedTypes = requestedTypes;
@@ -351,12 +355,16 @@
             mDurationMs = calculateDurationMs();
             mDisable = disable;
             mFloatingImeBottomInset = floatingImeBottomInset;
+            mLoggingListener = loggingListener;
         }
 
         @Override
         public void onReady(WindowInsetsAnimationController controller, int types) {
             mController = controller;
             if (DEBUG) Log.d(TAG, "default animation onReady types: " + types);
+            if (mLoggingListener != null) {
+                mLoggingListener.onReady(controller, types);
+            }
 
             if (mDisable) {
                 onAnimationFinish();
@@ -410,6 +418,9 @@
         public void onFinished(WindowInsetsAnimationController controller) {
             if (DEBUG) Log.d(TAG, "InternalAnimationControlListener onFinished types:"
                     + Type.toString(mRequestedTypes));
+            if (mLoggingListener != null) {
+                mLoggingListener.onFinished(controller);
+            }
         }
 
         @Override
@@ -420,6 +431,9 @@
             }
             if (DEBUG) Log.d(TAG, "InternalAnimationControlListener onCancelled types:"
                     + mRequestedTypes);
+            if (mLoggingListener != null) {
+                mLoggingListener.onCancelled(controller);
+            }
         }
 
         protected Interpolator getInsetsInterpolator() {
@@ -1147,6 +1161,13 @@
         updateRequestedVisibilities();
     }
 
+    // TODO(b/242962223): Make this setter restrictive.
+    @Override
+    public void setSystemDrivenInsetsAnimationLoggingListener(
+            @Nullable WindowInsetsAnimationControlListener listener) {
+        mLoggingListener = listener;
+    }
+
     /**
      * @return Pair of (types ready to animate, IME ready to animate).
      */
@@ -1159,7 +1180,7 @@
             final InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
             boolean show = animationType == ANIMATION_TYPE_SHOW
                     || animationType == ANIMATION_TYPE_USER;
-            boolean canRun = false;
+            boolean canRun = true;
             if (show) {
                 // Show request
                 if (fromIme) {
@@ -1169,7 +1190,6 @@
                 }
                 switch(consumer.requestShow(fromIme)) {
                     case ShowResult.SHOW_IMMEDIATELY:
-                        canRun = true;
                         break;
                     case ShowResult.IME_SHOW_DELAYED:
                         imeReady = false;
@@ -1180,6 +1200,7 @@
                                 + fromIme);
                         // IME cannot be shown (since it didn't have focus), proceed
                         // with animation of other types.
+                        canRun = false;
                         break;
                 }
             } else {
@@ -1189,7 +1210,6 @@
                 if (!fromIme) {
                     consumer.notifyHidden();
                 }
-                canRun = true;
             }
             if (!canRun) {
                 if (WARN) Log.w(TAG, String.format(
@@ -1460,7 +1480,8 @@
         boolean hasAnimationCallbacks = mHost.hasAnimationCallbacks();
         final InternalAnimationControlListener listener = new InternalAnimationControlListener(
                 show, hasAnimationCallbacks, types, mHost.getSystemBarsBehavior(),
-                skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP));
+                skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP),
+                mLoggingListener);
 
         // We are about to playing the default animation (show/hide). Passing a null frame indicates
         // the controlled types should be animated regardless of the frame.
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index d63c25a..5236fe7 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -176,7 +176,9 @@
 
                 // If we have a new leash, make sure visibility is up-to-date, even though we
                 // didn't want to run an animation above.
-                applyRequestedVisibilityToControl();
+                if (mController.getAnimationType(control.getType()) == ANIMATION_TYPE_NONE) {
+                    applyRequestedVisibilityToControl();
+                }
 
                 // Remove the surface that owned by last control when it lost.
                 if (!requestedVisible && lastControl == null) {
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/OWNERS b/core/java/android/view/OWNERS
index 1e1c250..6d64022 100644
--- a/core/java/android/view/OWNERS
+++ b/core/java/android/view/OWNERS
@@ -81,8 +81,8 @@
 per-file DisplayCutout.aidl = file:/services/core/java/com/android/server/wm/OWNERS
 per-file DisplayCutout.java = file:/services/core/java/com/android/server/wm/OWNERS
 per-file IDisplay*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
-per-file Inset*.java = file:/services/core/java/com/android/server/wm/OWNERS
-per-file Inset*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
+per-file *Inset*.java = file:/services/core/java/com/android/server/wm/OWNERS
+per-file *Inset*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
 per-file IPinnedStackListener.aidl = file:/services/core/java/com/android/server/wm/OWNERS
 per-file IRecents*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
 per-file IRemote*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
@@ -94,7 +94,6 @@
 per-file SurfaceControl*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
 per-file SurfaceSession.java = file:/services/core/java/com/android/server/wm/OWNERS
 per-file SyncRtSurfaceTransactionApplier.java = file:/services/core/java/com/android/server/wm/OWNERS
-per-file ViewRootInsetsControllerHost.java = file:/services/core/java/com/android/server/wm/OWNERS
 per-file Window*.java = file:/services/core/java/com/android/server/wm/OWNERS
 per-file Window*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
 per-file TransactionCommittedCallback.java = file:/services/core/java/com/android/server/wm/OWNERS
diff --git a/core/java/android/view/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java
index c61baf6..3fe9110 100644
--- a/core/java/android/view/PendingInsetsController.java
+++ b/core/java/android/view/PendingInsetsController.java
@@ -44,6 +44,7 @@
     private ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners
             = new ArrayList<>();
     private int mCaptionInsetsHeight = 0;
+    private WindowInsetsAnimationControlListener mLoggingListener;
 
     @Override
     public void show(int types) {
@@ -176,6 +177,9 @@
             controller.addOnControllableInsetsChangedListener(
                     mControllableInsetsChangedListeners.get(i));
         }
+        if (mLoggingListener != null) {
+            controller.setSystemDrivenInsetsAnimationLoggingListener(mLoggingListener);
+        }
 
         // Reset all state so it doesn't get applied twice just in case
         mRequests.clear();
@@ -184,7 +188,7 @@
         mAppearance = 0;
         mAppearanceMask = 0;
         mAnimationsDisabled = false;
-
+        mLoggingListener = null;
         // After replaying, we forward everything directly to the replayed instance.
         mReplayedInsetsController = controller;
     }
@@ -198,6 +202,16 @@
     }
 
     @Override
+    public void setSystemDrivenInsetsAnimationLoggingListener(
+            @Nullable WindowInsetsAnimationControlListener listener) {
+        if (mReplayedInsetsController != null) {
+            mReplayedInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener);
+        } else {
+            mLoggingListener = listener;
+        }
+    }
+
+    @Override
     public void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
             @Nullable Interpolator interpolator,
             CancellationSignal cancellationSignal,
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/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 5721fa6..3acb053 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -28,8 +28,8 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.util.Log;
-import android.window.WindowTokenClient;
 import android.view.accessibility.IAccessibilityEmbeddedConnection;
+import android.window.WindowTokenClient;
 
 import java.util.Objects;
 
@@ -271,14 +271,8 @@
     /** @hide */
     public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
             @NonNull WindowlessWindowManager wwm) {
-        this(c, d, wwm, false /* useSfChoreographer */);
-    }
-
-    /** @hide */
-    public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
-            @NonNull WindowlessWindowManager wwm, boolean useSfChoreographer) {
         mWm = wwm;
-        mViewRoot = new ViewRootImpl(c, d, mWm, new WindowlessWindowLayout(), useSfChoreographer);
+        mViewRoot = new ViewRootImpl(c, d, mWm, new WindowlessWindowLayout());
         addConfigCallback(c, d);
 
         WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 70a5eda..b6c92e3 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -119,10 +119,6 @@
     final int[] mLocation = new int[2];
 
     @UnsupportedAppUsage
-    // Used to ensure the Surface remains valid between SurfaceHolder#lockCanvas and
-    // SurfaceHolder#unlockCanvasAndPost calls. This prevents SurfaceView from destroying or
-    // invalidating the Surface. This means this lock should be acquired when destroying the
-    // BlastBufferQueue.
     final ReentrantLock mSurfaceLock = new ReentrantLock();
     @UnsupportedAppUsage
     final Surface mSurface = new Surface();       // Current surface in use
@@ -727,18 +723,14 @@
 
     private void releaseSurfaces(boolean releaseSurfacePackage) {
         mSurfaceAlpha = 1f;
-        try {
-            mSurfaceLock.lock();
+
+        synchronized (mSurfaceControlLock) {
             mSurface.destroy();
             if (mBlastBufferQueue != null) {
                 mBlastBufferQueue.destroy();
                 mBlastBufferQueue = null;
             }
-        } finally {
-            mSurfaceLock.unlock();
-        }
 
-        synchronized (mSurfaceControlLock) {
             final Transaction transaction = new Transaction();
             if (mSurfaceControl != null) {
                 transaction.remove(mSurfaceControl);
@@ -782,99 +774,105 @@
             Transaction surfaceUpdateTransaction) {
         boolean realSizeChanged = false;
 
-        mDrawingStopped = !mVisible;
+        mSurfaceLock.lock();
+        try {
+            mDrawingStopped = !mVisible;
 
-        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
-                + "Cur surface: " + mSurface);
+            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                    + "Cur surface: " + mSurface);
 
-        // If we are creating the surface control or the parent surface has not
-        // changed, then set relative z. Otherwise allow the parent
-        // SurfaceChangedCallback to update the relative z. This is needed so that
-        // we do not change the relative z before the server is ready to swap the
-        // parent surface.
-        if (creating) {
-            updateRelativeZ(surfaceUpdateTransaction);
-            if (mSurfacePackage != null) {
-                reparentSurfacePackage(surfaceUpdateTransaction, mSurfacePackage);
+            // If we are creating the surface control or the parent surface has not
+            // changed, then set relative z. Otherwise allow the parent
+            // SurfaceChangedCallback to update the relative z. This is needed so that
+            // we do not change the relative z before the server is ready to swap the
+            // parent surface.
+            if (creating) {
+                updateRelativeZ(surfaceUpdateTransaction);
+                if (mSurfacePackage != null) {
+                    reparentSurfacePackage(surfaceUpdateTransaction, mSurfacePackage);
+                }
             }
-        }
-        mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId();
+            mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId();
 
-        if (mViewVisibility) {
-            surfaceUpdateTransaction.show(mSurfaceControl);
-        } else {
-            surfaceUpdateTransaction.hide(mSurfaceControl);
-        }
-
-
-
-        updateBackgroundVisibility(surfaceUpdateTransaction);
-        updateBackgroundColor(surfaceUpdateTransaction);
-        if (mUseAlpha) {
-            float alpha = getFixedAlpha();
-            surfaceUpdateTransaction.setAlpha(mSurfaceControl, alpha);
-            mSurfaceAlpha = alpha;
-        }
-
-        surfaceUpdateTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
-        if ((sizeChanged || hintChanged) && !creating) {
-            setBufferSize(surfaceUpdateTransaction);
-        }
-        if (sizeChanged || creating || !isHardwareAccelerated()) {
-            // Set a window crop when creating the surface or changing its size to
-            // crop the buffer to the surface size since the buffer producer may
-            // use SCALING_MODE_SCALE and submit a larger size than the surface
-            // size.
-            if (mClipSurfaceToBounds && mClipBounds != null) {
-                surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
+            if (mViewVisibility) {
+                surfaceUpdateTransaction.show(mSurfaceControl);
             } else {
-                surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
-                        mSurfaceHeight);
+                surfaceUpdateTransaction.hide(mSurfaceControl);
             }
 
-            surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
-                        mSurfaceHeight);
 
-            if (isHardwareAccelerated()) {
-                // This will consume the passed in transaction and the transaction will be
-                // applied on a render worker thread.
-                replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight);
+
+            updateBackgroundVisibility(surfaceUpdateTransaction);
+            updateBackgroundColor(surfaceUpdateTransaction);
+            if (mUseAlpha) {
+                float alpha = getFixedAlpha();
+                surfaceUpdateTransaction.setAlpha(mSurfaceControl, alpha);
+                mSurfaceAlpha = alpha;
+            }
+
+            surfaceUpdateTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
+            if ((sizeChanged || hintChanged) && !creating) {
+                setBufferSize(surfaceUpdateTransaction);
+            }
+            if (sizeChanged || creating || !isHardwareAccelerated()) {
+
+                // Set a window crop when creating the surface or changing its size to
+                // crop the buffer to the surface size since the buffer producer may
+                // use SCALING_MODE_SCALE and submit a larger size than the surface
+                // size.
+                if (mClipSurfaceToBounds && mClipBounds != null) {
+                    surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
+                } else {
+                    surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
+                            mSurfaceHeight);
+                }
+
+                surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
+                            mSurfaceHeight);
+
+                if (isHardwareAccelerated()) {
+                    // This will consume the passed in transaction and the transaction will be
+                    // applied on a render worker thread.
+                    replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight);
+                } else {
+                    onSetSurfacePositionAndScale(surfaceUpdateTransaction, mSurfaceControl,
+                            mScreenRect.left /*positionLeft*/,
+                            mScreenRect.top /*positionTop*/,
+                            mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
+                            mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
+                }
+                if (DEBUG_POSITION) {
+                    Log.d(TAG, String.format(
+                            "%d performSurfaceTransaction %s "
+                                + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
+                            System.identityHashCode(this),
+                            isHardwareAccelerated() ? "RenderWorker" : "UI Thread",
+                            mScreenRect.left, mScreenRect.top, mScreenRect.right,
+                            mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight));
+                }
+            }
+            applyTransactionOnVriDraw(surfaceUpdateTransaction);
+            updateEmbeddedAccessibilityMatrix(false);
+
+            mSurfaceFrame.left = 0;
+            mSurfaceFrame.top = 0;
+            if (translator == null) {
+                mSurfaceFrame.right = mSurfaceWidth;
+                mSurfaceFrame.bottom = mSurfaceHeight;
             } else {
-                onSetSurfacePositionAndScale(surfaceUpdateTransaction, mSurfaceControl,
-                        mScreenRect.left /*positionLeft*/,
-                        mScreenRect.top /*positionTop*/,
-                        mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
-                        mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
+                float appInvertedScale = translator.applicationInvertedScale;
+                mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
+                mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
             }
-            if (DEBUG_POSITION) {
-                Log.d(TAG, String.format(
-                        "%d performSurfaceTransaction %s "
-                            + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
-                        System.identityHashCode(this),
-                        isHardwareAccelerated() ? "RenderWorker" : "UI Thread",
-                        mScreenRect.left, mScreenRect.top, mScreenRect.right,
-                        mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight));
-            }
+            final int surfaceWidth = mSurfaceFrame.right;
+            final int surfaceHeight = mSurfaceFrame.bottom;
+            realSizeChanged = mLastSurfaceWidth != surfaceWidth
+                    || mLastSurfaceHeight != surfaceHeight;
+            mLastSurfaceWidth = surfaceWidth;
+            mLastSurfaceHeight = surfaceHeight;
+        } finally {
+            mSurfaceLock.unlock();
         }
-        applyTransactionOnVriDraw(surfaceUpdateTransaction);
-        updateEmbeddedAccessibilityMatrix(false);
-         mSurfaceFrame.left = 0;
-        mSurfaceFrame.top = 0;
-        if (translator == null) {
-            mSurfaceFrame.right = mSurfaceWidth;
-            mSurfaceFrame.bottom = mSurfaceHeight;
-        } else {
-            float appInvertedScale = translator.applicationInvertedScale;
-            mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
-            mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
-        }
-        final int surfaceWidth = mSurfaceFrame.right;
-        final int surfaceHeight = mSurfaceFrame.bottom;
-        realSizeChanged = mLastSurfaceWidth != surfaceWidth
-                || mLastSurfaceHeight != surfaceHeight;
-        mLastSurfaceWidth = surfaceWidth;
-        mLastSurfaceHeight = surfaceHeight;
-
         return realSizeChanged;
     }
 
@@ -916,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()
@@ -927,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
@@ -1141,30 +1138,21 @@
      *                          Surface for compatibility reasons.
      */
     private void copySurface(boolean surfaceControlCreated, boolean bufferSizeChanged) {
-        // Some legacy applications use the underlying native {@link Surface} object
-        // as a key to whether anything has changed. In these cases, updates to the
-        // existing {@link Surface} will be ignored when the size changes.
-        // Therefore, we must explicitly recreate the {@link Surface} in these
-        // cases.
-        boolean needsWorkaround = bufferSizeChanged &&
-            getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O;
-       if (!surfaceControlCreated && !needsWorkaround) {
-           return;
-       }
-       mSurfaceLock.lock();
-       try {
-           if (surfaceControlCreated) {
-               mSurface.copyFrom(mBlastBufferQueue);
-           }
+        if (surfaceControlCreated) {
+            mSurface.copyFrom(mBlastBufferQueue);
+        }
 
-           if (needsWorkaround) {
-               if (mBlastBufferQueue != null) {
-                   mSurface.transferFrom(mBlastBufferQueue.createSurfaceWithHandle());
-               }
-           }
-       } finally {
-           mSurfaceLock.unlock();
-       }
+        if (bufferSizeChanged && getContext().getApplicationInfo().targetSdkVersion
+                < Build.VERSION_CODES.O) {
+            // Some legacy applications use the underlying native {@link Surface} object
+            // as a key to whether anything has changed. In these cases, updates to the
+            // existing {@link Surface} will be ignored when the size changes.
+            // Therefore, we must explicitly recreate the {@link Surface} in these
+            // cases.
+            if (mBlastBufferQueue != null) {
+                mSurface.transferFrom(mBlastBufferQueue.createSurfaceWithHandle());
+            }
+        }
     }
 
     private void setBufferSize(Transaction transaction) {
@@ -1240,21 +1228,16 @@
                     .build();
         }
 
+        // Always recreate the IGBP for compatibility. This can be optimized in the future but
+        // the behavior change will need to be gated by SDK version.
+        if (mBlastBufferQueue != null) {
+            mBlastBufferQueue.destroy();
+        }
         mTransformHint = viewRoot.getBufferTransformHint();
         mBlastSurfaceControl.setTransformHint(mTransformHint);
 
-        // Always recreate the IGBP for compatibility. This can be optimized in the future but
-        // the behavior change will need to be gated by SDK version.
-        try {
-            mSurfaceLock.lock();
-            if (mBlastBufferQueue != null) {
-                mBlastBufferQueue.destroy();
-            }
-            mBlastBufferQueue = new BLASTBufferQueue(name, false /* updateDestinationFrame */);
-            mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat);
-        } finally {
-            mSurfaceLock.unlock();
-        }
+        mBlastBufferQueue = new BLASTBufferQueue(name, false /* updateDestinationFrame */);
+        mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat);
         mBlastBufferQueue.setTransactionHangCallback(ViewRootImpl.sTransactionHangCallback);
     }
 
@@ -1827,14 +1810,7 @@
             // so the next connect will always work if we end up reusing
             // the surface.
             if (mSurface.isValid()) {
-                // We need to grab this lock since mSurface.forceScopedDisconnect
-                // will free buffers from the queue.
-                try {
-                    mSurfaceLock.lock();
-                    mSurface.forceScopedDisconnect();
-                } finally {
-                    mSurfaceLock.unlock();
-                }
+                mSurface.forceScopedDisconnect();
             }
         }
     }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 84edb3a..4893777 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3085,45 +3085,6 @@
     static final int IMPORTANT_FOR_ACCESSIBILITY_DEFAULT = IMPORTANT_FOR_ACCESSIBILITY_AUTO;
 
     /**
-     * Automatically determine whether the view should only allow interactions from
-     * {@link android.accessibilityservice.AccessibilityService}s with the
-     * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
-     * set to true.
-     *
-     * <p>
-     * Accessibility interactions from services without {@code isAccessibilityTool} set to true are
-     * disallowed for any of the following conditions:
-     * <li>this view's window sets {@link WindowManager.LayoutParams#FLAG_SECURE}.</li>
-     * <li>this view sets {@link #getFilterTouchesWhenObscured()}.</li>
-     * <li>any parent of this view returns true from {@link #isAccessibilityDataPrivate()}.</li>
-     * </p>
-     */
-    public static final int ACCESSIBILITY_DATA_PRIVATE_AUTO = 0x00000000;
-
-    /**
-     * Only allow interactions from {@link android.accessibilityservice.AccessibilityService}s
-     * with the {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool}
-     * property set to true.
-     */
-    public static final int ACCESSIBILITY_DATA_PRIVATE_YES = 0x00000001;
-
-    /**
-     * Allow interactions from all {@link android.accessibilityservice.AccessibilityService}s,
-     * regardless of their
-     * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property.
-     */
-    public static final int ACCESSIBILITY_DATA_PRIVATE_NO = 0x00000002;
-
-    /** @hide */
-    @IntDef(prefix = { "ACCESSIBILITY_DATA_PRIVATE_" }, value = {
-            ACCESSIBILITY_DATA_PRIVATE_AUTO,
-            ACCESSIBILITY_DATA_PRIVATE_YES,
-            ACCESSIBILITY_DATA_PRIVATE_NO,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface AccessibilityDataPrivate {}
-
-    /**
      * Mask for obtaining the bits which specify how to determine
      * whether a view is important for accessibility.
      */
@@ -4566,14 +4527,6 @@
     private CharSequence mAccessibilityPaneTitle;
 
     /**
-     * Describes whether this view should only allow interactions from
-     * {@link android.accessibilityservice.AccessibilityService}s with the
-     * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
-     * set to true.
-     */
-    private int mAccessibilityDataPrivate = ACCESSIBILITY_DATA_PRIVATE_AUTO;
-
-    /**
      * Specifies the id of a view for which this view serves as a label for
      * accessibility purposes.
      */
@@ -5966,10 +5919,6 @@
                     setImportantForAccessibility(a.getInt(attr,
                             IMPORTANT_FOR_ACCESSIBILITY_DEFAULT));
                     break;
-                case R.styleable.View_accessibilityDataPrivate:
-                    setAccessibilityDataPrivate(a.getInt(attr,
-                            ACCESSIBILITY_DATA_PRIVATE_AUTO));
-                    break;
                 case R.styleable.View_accessibilityLiveRegion:
                     setAccessibilityLiveRegion(a.getInt(attr, ACCESSIBILITY_LIVE_REGION_DEFAULT));
                     break;
@@ -8569,11 +8518,6 @@
      * is responsible for handling this call.
      * </p>
      * <p>
-     * If this view sets {@link #isAccessibilityDataPrivate()} then this view should only append
-     * sensitive information to an event that also sets
-     * {@link AccessibilityEvent#isAccessibilityDataPrivate()}.
-     * </p>
-     * <p>
      * <em>Note:</em> Accessibility events of certain types are not dispatched for
      * populating the event text via this method. For details refer to {@link AccessibilityEvent}.
      * </p>
@@ -10475,7 +10419,7 @@
             }
 
             if ((mAttachInfo.mAccessibilityFetchFlags
-                    & AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS) != 0
+                    & AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS) != 0
                     && Resources.resourceHasPackage(mID)) {
                 try {
                     String viewId = getResources().getResourceName(mID);
@@ -14458,75 +14402,14 @@
     @UnsupportedAppUsage
     public boolean includeForAccessibility() {
         if (mAttachInfo != null) {
-            if (isAccessibilityDataPrivate() && !AccessibilityManager.getInstance(
-                    mContext).isRequestFromAccessibilityTool()) {
-                return false;
-            }
-
             return (mAttachInfo.mAccessibilityFetchFlags
-                    & AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
+                    & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
                     || isImportantForAccessibility();
         }
         return false;
     }
 
     /**
-     * Whether this view should restrict accessibility service access only to services that have the
-     * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
-     * set to true.
-     *
-     * <p>
-     * See default behavior provided by {@link #ACCESSIBILITY_DATA_PRIVATE_AUTO}. Otherwise,
-     * returns true for {@link #ACCESSIBILITY_DATA_PRIVATE_YES} or false for {@link
-     * #ACCESSIBILITY_DATA_PRIVATE_NO}.
-     * </p>
-     *
-     * @return True if this view should restrict accessibility service access to services that have
-     * the isAccessibilityTool property.
-     */
-    @ViewDebug.ExportedProperty(category = "accessibility")
-    public boolean isAccessibilityDataPrivate() {
-        if (mAccessibilityDataPrivate == ACCESSIBILITY_DATA_PRIVATE_YES) {
-            return true;
-        }
-        if (mAccessibilityDataPrivate == ACCESSIBILITY_DATA_PRIVATE_NO) {
-            return false;
-        }
-
-        // Views inside FLAG_SECURE windows default to accessibilityDataPrivate.
-        if (mAttachInfo != null && mAttachInfo.mWindowSecure) {
-            return true;
-        }
-        // Views that set filterTouchesWhenObscured default to accessibilityDataPrivate.
-        if (getFilterTouchesWhenObscured()) {
-            return true;
-        }
-
-        // Descendants of an accessibilityDataPrivate View are also accessibilityDataPrivate.
-        ViewParent parent = mParent;
-        while (parent instanceof View) {
-            if (((View) parent).isAccessibilityDataPrivate()) {
-                return true;
-            }
-            parent = parent.getParent();
-        }
-
-        // Otherwise, default to not accessibilityDataPrivate.
-        return false;
-    }
-
-    /**
-     * Specifies whether this view should only allow interactions from
-     * {@link android.accessibilityservice.AccessibilityService}s with the
-     * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
-     * set to true.
-     */
-    public void setAccessibilityDataPrivate(
-            @AccessibilityDataPrivate int accessibilityDataPrivate) {
-        mAccessibilityDataPrivate = accessibilityDataPrivate;
-    }
-
-    /**
      * Returns whether the View is considered actionable from
      * accessibility perspective. Such view are important for
      * accessibility.
@@ -30213,11 +30096,6 @@
         int mWindowVisibility;
 
         /**
-         * Indicates whether the view's window sets {@link WindowManager.LayoutParams#FLAG_SECURE}.
-         */
-        boolean mWindowSecure;
-
-        /**
          * Indicates the time at which drawing started to occur.
          */
         @UnsupportedAppUsage
@@ -30394,8 +30272,8 @@
         /**
          * Flags related to accessibility processing.
          *
-         * @see AccessibilityNodeInfo#FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS
-         * @see AccessibilityNodeInfo#FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS
+         * @see AccessibilityNodeInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+         * @see AccessibilityNodeInfo#FLAG_REPORT_VIEW_IDS
          */
         int mAccessibilityFetchFlags;
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f79f0d4..59bc061 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -75,6 +75,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
@@ -172,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;
@@ -707,6 +709,8 @@
     final Rect mPendingBackDropFrame = new Rect();
 
     boolean mPendingAlwaysConsumeSystemBars;
+    private int mRelayoutSeq;
+    private final Rect mWinFrameInScreen = new Rect();
     private final InsetsState mTempInsets = new InsetsState();
     private final InsetsSourceControl[] mTempControls = new InsetsSourceControl[SIZE];
     private final WindowConfiguration mTempWinConfig = new WindowConfiguration();
@@ -907,17 +911,11 @@
     private String mTag = TAG;
 
     public ViewRootImpl(Context context, Display display) {
-        this(context, display, WindowManagerGlobal.getWindowSession(), new WindowLayout(),
-                false /* useSfChoreographer */);
+        this(context, display, WindowManagerGlobal.getWindowSession(), new WindowLayout());
     }
 
     public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
             WindowLayout windowLayout) {
-        this(context, display, session, windowLayout, false /* useSfChoreographer */);
-    }
-
-    public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
-            WindowLayout windowLayout, boolean useSfChoreographer) {
         mContext = context;
         mWindowSession = session;
         mWindowLayout = windowLayout;
@@ -949,8 +947,7 @@
         mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
         mFallbackEventHandler = new PhoneFallbackEventHandler(context);
         // TODO(b/222696368): remove getSfInstance usage and use vsyncId for transactions
-        mChoreographer = useSfChoreographer
-                ? Choreographer.getSfInstance() : Choreographer.getInstance();
+        mChoreographer = Choreographer.getInstance();
         mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
         mInsetsController = new InsetsController(new ViewRootInsetsControllerHost(this));
         mHandwritingInitiator = new HandwritingInitiator(
@@ -2839,7 +2836,6 @@
             // However, windows are now always 32 bits by default, so choose 32 bits
             mAttachInfo.mUse32BitDrawingCache = true;
             mAttachInfo.mWindowVisibility = viewVisibility;
-            mAttachInfo.mWindowSecure = (lp.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0;
             mAttachInfo.mRecomputeGlobalAttributes = false;
             mLastConfigurationFromResources.setTo(config);
             mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
@@ -3364,20 +3360,6 @@
                 }
             }
         } else {
-            // If a relayout isn't going to happen, we still need to check if this window can draw
-            // when mCheckIfCanDraw is set. This is because it means we had a sync in the past, but
-            // have not been told by WMS that the sync is complete and that we can continue to draw
-            if (mCheckIfCanDraw) {
-                try {
-                    cancelDraw = mWindowSession.cancelDraw(mWindow);
-                    cancelReason = "wm_sync";
-                    if (DEBUG_BLAST) {
-                        Log.d(mTag, "cancelDraw returned " + cancelDraw);
-                    }
-                } catch (RemoteException e) {
-                }
-            }
-
             // Not the first pass and no window/insets/visibility change but the window
             // may have moved and we need check that and if so to update the left and right
             // in the attach info. We translate only the window frame since on window move
@@ -3386,6 +3368,20 @@
             maybeHandleWindowMove(frame);
         }
 
+        if (!mRelayoutRequested && mCheckIfCanDraw) {
+            // We had a sync previously, but we didn't call IWindowSession#relayout in this
+            // traversal. So we don't know if the sync is complete that we can continue to draw.
+            // Here invokes cancelDraw to obtain the information.
+            try {
+                cancelDraw = mWindowSession.cancelDraw(mWindow);
+                cancelReason = "wm_sync";
+                if (DEBUG_BLAST) {
+                    Log.d(mTag, "cancelDraw returned " + cancelDraw);
+                }
+            } catch (RemoteException e) {
+            }
+        }
+
         if (surfaceSizeChanged || surfaceReplaced || surfaceCreated || windowAttributesChanged) {
             // If the surface has been replaced, there's a chance the bounds layer is not parented
             // to the new layer. When updating bounds layer, also reparent to the main VRI
@@ -5323,6 +5319,7 @@
         }
 
         mAccessibilityInteractionConnectionManager.ensureNoConnection();
+        mAccessibilityInteractionConnectionManager.ensureNoDirectConnection();
         removeSendWindowContentChangedCallback();
 
         destroyHardwareRenderer();
@@ -6403,6 +6400,12 @@
             // Make sure the fallback event policy sees all keys that will be
             // delivered to the view hierarchy.
             mFallbackEventHandler.preDispatchKeyEvent(event);
+
+            // Reset last tracked MotionEvent click toolType.
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                mLastClickToolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+            }
+
             return FORWARD;
         }
 
@@ -8151,7 +8154,43 @@
 
     private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
             boolean insetsPending) throws RemoteException {
-        mRelayoutRequested = true;
+        final WindowConfiguration winConfigFromAm = getConfiguration().windowConfiguration;
+        final WindowConfiguration winConfigFromWm =
+                mLastReportedMergedConfiguration.getGlobalConfiguration().windowConfiguration;
+        final WindowConfiguration winConfig = getCompatWindowConfiguration();
+        final int measuredWidth = mView.getMeasuredWidth();
+        final int measuredHeight = mView.getMeasuredHeight();
+        final boolean relayoutAsync;
+        if (LOCAL_LAYOUT && !mFirst && viewVisibility == mViewVisibility
+                && mWindowAttributes.type != TYPE_APPLICATION_STARTING
+                && mSyncSeqId <= mLastSyncSeqId
+                && winConfigFromAm.diff(winConfigFromWm, false /* compareUndefined */) == 0) {
+            final InsetsState state = mInsetsController.getState();
+            final Rect displayCutoutSafe = mTempRect;
+            state.getDisplayCutoutSafe(displayCutoutSafe);
+            mWindowLayout.computeFrames(mWindowAttributes.forRotation(winConfig.getRotation()),
+                    state, displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
+                    measuredWidth, measuredHeight, mInsetsController.getRequestedVisibilities(),
+                    1f /* compatScale */, mTmpFrames);
+            mWinFrameInScreen.set(mTmpFrames.frame);
+            if (mTranslator != null) {
+                mTranslator.translateRectInAppWindowToScreen(mWinFrameInScreen);
+            }
+
+            // If the position and the size of the frame are both changed, it will trigger a BLAST
+            // sync, and we still need to call relayout to obtain the syncSeqId. Otherwise, we just
+            // need to send attributes via relayoutAsync.
+            final Rect oldFrame = mWinFrame;
+            final Rect newFrame = mTmpFrames.frame;
+            final boolean positionChanged =
+                    newFrame.top != oldFrame.top || newFrame.left != oldFrame.left;
+            final boolean sizeChanged =
+                    newFrame.width() != oldFrame.width() || newFrame.height() != oldFrame.height();
+            relayoutAsync = !positionChanged || !sizeChanged;
+        } else {
+            relayoutAsync = false;
+        }
+
         float appScale = mAttachInfo.mApplicationScale;
         boolean restore = false;
         if (params != null && mTranslator != null) {
@@ -8173,26 +8212,47 @@
             }
         }
 
-        final int requestedWidth = (int) (mView.getMeasuredWidth() * appScale + 0.5f);
-        final int requestedHeight = (int) (mView.getMeasuredHeight() * appScale + 0.5f);
+        final int requestedWidth = (int) (measuredWidth * appScale + 0.5f);
+        final int requestedHeight = (int) (measuredHeight * appScale + 0.5f);
+        int relayoutResult = 0;
+        mRelayoutSeq++;
+        if (relayoutAsync) {
+            mWindowSession.relayoutAsync(mWindow, params,
+                    requestedWidth, requestedHeight, viewVisibility,
+                    insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mRelayoutSeq,
+                    mLastSyncSeqId);
+        } else {
+            relayoutResult = mWindowSession.relayout(mWindow, params,
+                    requestedWidth, requestedHeight, viewVisibility,
+                    insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mRelayoutSeq,
+                    mLastSyncSeqId, mTmpFrames, mPendingMergedConfiguration, mSurfaceControl,
+                    mTempInsets, mTempControls, mRelayoutBundle);
+            mRelayoutRequested = true;
+            final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
+            if (maybeSyncSeqId > 0) {
+                mSyncSeqId = maybeSyncSeqId;
+            }
+            mWinFrameInScreen.set(mTmpFrames.frame);
+            if (mTranslator != null) {
+                mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
+                mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame);
+                mTranslator.translateRectInScreenToAppWindow(mTmpFrames.attachedFrame);
+                mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
+                mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
+            }
+            mInvSizeCompatScale = 1f / mTmpFrames.sizeCompatScale;
+            mInsetsController.onStateChanged(mTempInsets);
+            mInsetsController.onControlsChanged(mTempControls);
 
-        int relayoutResult = mWindowSession.relayout(mWindow, params,
-                requestedWidth, requestedHeight, viewVisibility,
-                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
-                mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
-                mTempControls, mRelayoutBundle);
-        final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
-        if (maybeSyncSeqId > 0) {
-            mSyncSeqId = maybeSyncSeqId;
+            mPendingAlwaysConsumeSystemBars =
+                    (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
         }
-        mInvSizeCompatScale = 1f / mTmpFrames.sizeCompatScale;
 
         final int transformHint = SurfaceControl.rotationToBufferTransform(
                 (mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
 
-        final WindowConfiguration winConfig = getCompatWindowConfiguration();
         WindowLayout.computeSurfaceSize(mWindowAttributes, winConfig.getMaxBounds(), requestedWidth,
-                requestedHeight, mTmpFrames.frame, mPendingDragResizing, mSurfaceSize);
+                requestedHeight, mWinFrameInScreen, mPendingDragResizing, mSurfaceSize);
 
         final boolean transformHintChanged = transformHint != mLastTransformHint;
         final boolean sizeChanged = !mLastSurfaceSize.equals(mSurfaceSize);
@@ -8239,23 +8299,11 @@
             destroySurface();
         }
 
-        mPendingAlwaysConsumeSystemBars =
-                (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
-
         if (restore) {
             params.restore();
         }
 
-        if (mTranslator != null) {
-            mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
-            mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame);
-            mTranslator.translateRectInScreenToAppWindow(mTmpFrames.attachedFrame);
-            mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
-            mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
-        }
         setFrame(mTmpFrames.frame);
-        mInsetsController.onStateChanged(mTempInsets);
-        mInsetsController.onControlsChanged(mTempControls);
         return relayoutResult;
     }
 
@@ -9524,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;
@@ -10399,6 +10455,8 @@
      */
     final class AccessibilityInteractionConnectionManager
             implements AccessibilityStateChangeListener {
+        private int mDirectConnectionId = AccessibilityNodeInfo.UNDEFINED_CONNECTION_ID;
+
         @Override
         public void onAccessibilityStateChanged(boolean enabled) {
             if (enabled) {
@@ -10442,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/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index 227b9f4..63f9e13 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -201,6 +201,21 @@
             @NonNull WindowInsetsAnimationControlListener listener);
 
     /**
+     * Lets the application add non-controllable listener object that can be called back
+     * when animation is invoked by the system by host calling methods such as {@link #show} or
+     * {@link #hide}.
+     *
+     * The listener is supposed to be used for logging only, using the control or
+     * relying on the timing of the callback in any other way is not supported.
+     *
+     * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when
+     *                 the animation is driven by the system and not the host
+     * @hide
+     */
+    void setSystemDrivenInsetsAnimationLoggingListener(
+            @Nullable WindowInsetsAnimationControlListener listener);
+
+    /**
      * Controls the appearance of system bars.
      * <p>
      * For example, the following statement adds {@link #APPEARANCE_LIGHT_STATUS_BARS}:
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/WindowManager.java b/core/java/android/view/WindowManager.java
index fe8d64f..a49caaf 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1054,6 +1054,14 @@
         }
     }
 
+    /**
+     * Ensure scales are between 0 and 20.
+     * @hide
+     */
+    static float fixScale(float scale) {
+        return Math.max(Math.min(scale, 20), 0);
+    }
+
     public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
         /**
          * X position for this window.  With the default gravity it is ignored.
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index d55c838..1ec17d0 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -286,10 +286,11 @@
 
     @Override
     public int relayout(IWindow window, WindowManager.LayoutParams inAttrs,
-            int requestedWidth, int requestedHeight, int viewFlags, int flags,
-            ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
-            SurfaceControl outSurfaceControl, InsetsState outInsetsState,
-            InsetsSourceControl[] outActiveControls, Bundle outSyncSeqIdBundle) {
+            int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+            int lastSyncSeqId, ClientWindowFrames outFrames,
+            MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl,
+            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+            Bundle outSyncSeqIdBundle) {
         final State state;
         synchronized (this) {
             state = mStateForWindow.get(window.asBinder());
@@ -309,15 +310,23 @@
 
         if (viewFlags == View.VISIBLE) {
             t.setOpaque(sc, isOpaque(attrs)).show(sc).apply();
-            outSurfaceControl.copyFrom(sc, "WindowlessWindowManager.relayout");
+            if (outSurfaceControl != null) {
+                outSurfaceControl.copyFrom(sc, "WindowlessWindowManager.relayout");
+            }
         } else {
             t.hide(sc).apply();
-            outSurfaceControl.release();
+            if (outSurfaceControl != null) {
+                outSurfaceControl.release();
+            }
         }
-        outFrames.frame.set(0, 0, attrs.width, attrs.height);
-        outFrames.displayFrame.set(outFrames.frame);
+        if (outFrames != null) {
+            outFrames.frame.set(0, 0, attrs.width, attrs.height);
+            outFrames.displayFrame.set(outFrames.frame);
+        }
 
-        mergedConfiguration.setConfiguration(mConfiguration, mConfiguration);
+        if (outMergedConfiguration != null) {
+            outMergedConfiguration.setConfiguration(mConfiguration, mConfiguration);
+        }
 
         if ((attrChanges & WindowManager.LayoutParams.FLAGS_CHANGED) != 0
                 && state.mInputChannelToken != null) {
@@ -335,7 +344,7 @@
             }
         }
 
-        if (mInsetsState != null) {
+        if (outInsetsState != null && mInsetsState != null) {
             outInsetsState.set(mInsetsState);
         }
 
@@ -343,6 +352,16 @@
     }
 
     @Override
+    public void relayoutAsync(IWindow window, WindowManager.LayoutParams inAttrs,
+            int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+            int lastSyncSeqId) {
+        relayout(window, inAttrs, requestedWidth, requestedHeight, viewFlags, flags, seq,
+                lastSyncSeqId, null /* outFrames */, null /* outMergedConfiguration */,
+                null /* outSurfaceControl */, null /* outInsetsState */,
+                null /* outActiveControls */, null /* outSyncSeqIdBundle */);
+    }
+
+    @Override
     public void prepareToReplaceWindows(android.os.IBinder appToken, boolean childrenOnly) {
     }
 
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 2db0dcb..cd0dd1d 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -46,17 +46,15 @@
  * </p>
  * <p>
  * The main purpose of an accessibility event is to communicate changes in the UI to an
- * {@link android.accessibilityservice.AccessibilityService}. If needed, the service may then
- * inspect the user interface by examining the View hierarchy through the event's
- * {@link #getSource() source}, as represented by a tree of {@link AccessibilityNodeInfo}s (snapshot
- * of a View state) which can be used for exploring the window content. Note that the privilege for
- * accessing an event's source, thus the window content, has to be explicitly requested. For more
+ * {@link android.accessibilityservice.AccessibilityService}. The service may then inspect,
+ * if needed the user interface by examining the View hierarchy, as represented by a tree of
+ * {@link AccessibilityNodeInfo}s (snapshot of a View state)
+ * which can be used for exploring the window content. Note that the privilege for accessing
+ * an event's source, thus the window content, has to be explicitly requested. For more
  * details refer to {@link android.accessibilityservice.AccessibilityService}. If an
  * accessibility service has not requested to retrieve the window content the event will
- * not contain reference to its source. <strong>Note: </strong> for events of type
- * {@link #TYPE_NOTIFICATION_STATE_CHANGED} the source is never available, and Views that set
- * {@link android.view.View#isAccessibilityDataPrivate()} may not populate all event properties on
- * events sent from higher up in the view hierarchy.
+ * not contain reference to its source. Also for events of type
+ * {@link #TYPE_NOTIFICATION_STATE_CHANGED} the source is never available.
  * </p>
  * <p>
  * This class represents various semantically different accessibility event
@@ -1094,47 +1092,6 @@
     }
 
     /**
-     * Whether the event should only be delivered to an
-     * {@link android.accessibilityservice.AccessibilityService} with the
-     * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
-     * set to true.
-     *
-     * <p>
-     * Initial value matches the {@link android.view.View#isAccessibilityDataPrivate} property from
-     * the event's source node, if present, or false by default.
-     * </p>
-     *
-     * @return True if the event should be delivered only to isAccessibilityTool services, false
-     * otherwise.
-     * @see #setAccessibilityDataPrivate
-     */
-    @Override
-    public boolean isAccessibilityDataPrivate() {
-        return super.isAccessibilityDataPrivate();
-    }
-
-    /**
-     * Sets whether the event should only be delivered to an
-     * {@link android.accessibilityservice.AccessibilityService} with the
-     * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
-     * set to true.
-     *
-     * <p>
-     * This will be set automatically based on the event's source (if present). If creating and
-     * sending an event directly through {@link AccessibilityManager} (where an event may have
-     * no source) then this method must be called explicitly if you want non-default behavior.
-     * </p>
-     *
-     * @param accessibilityDataPrivate True if the event should be delivered only to
-     *                                 isAccessibilityTool services, false otherwise.
-     * @throws IllegalStateException If called from an AccessibilityService.
-     */
-    @Override
-    public void setAccessibilityDataPrivate(boolean accessibilityDataPrivate) {
-        super.setAccessibilityDataPrivate(accessibilityDataPrivate);
-    }
-
-    /**
      * Gets the bit mask of the speech state signaled by a {@link #TYPE_SPEECH_STATE_CHANGE} event
      *
      * @see #SPEECH_STATE_SPEAKING_START
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/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index e89f836..9e3195a 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -276,8 +276,6 @@
     private final ArrayMap<AudioDescriptionRequestedChangeListener, Executor>
             mAudioDescriptionRequestedChangeListeners = new ArrayMap<>();
 
-    private boolean mRequestFromAccessibilityTool;
-
     /**
      * Map from a view's accessibility id to the list of request preparers set for that view
      */
@@ -985,39 +983,6 @@
     }
 
     /**
-     * Whether the current accessibility request comes from an
-     * {@link AccessibilityService} with the {@link AccessibilityServiceInfo#isAccessibilityTool}
-     * property set to true.
-     *
-     * <p>
-     * You can use this method inside {@link AccessibilityNodeProvider} to decide how to populate
-     * your nodes.
-     * </p>
-     *
-     * <p>
-     * <strong>Note:</strong> The return value is valid only when an {@link AccessibilityNodeInfo}
-     * request is in progress, can change from one request to another, and has no meaning when a
-     * request is not in progress.
-     * </p>
-     *
-     * @return True if the current request is from a tool that sets isAccessibilityTool.
-     */
-    public boolean isRequestFromAccessibilityTool() {
-        return mRequestFromAccessibilityTool;
-    }
-
-    /**
-     * Specifies whether the current accessibility request comes from an
-     * {@link AccessibilityService} with the {@link AccessibilityServiceInfo#isAccessibilityTool}
-     * property set to true.
-     *
-     * @hide
-     */
-    public void setRequestFromAccessibilityTool(boolean requestFromAccessibilityTool) {
-        mRequestFromAccessibilityTool = requestFromAccessibilityTool;
-    }
-
-    /**
      * Registers a {@link AccessibilityRequestPreparer}.
      */
     public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 15718c4..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
@@ -217,29 +220,14 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface PrefetchingStrategy {}
 
-    /**
-     * @see AccessibilityServiceInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
-     * @hide
-     */
-    public static final int FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000080;
-
-    /**
-     * @see AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS
-     * @hide
-     */
-    public static final int FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS = 0x00000100;
-
-    /**
-     * @see AccessibilityServiceInfo#isAccessibilityTool()
-     * @hide
-     */
-    public static final int FLAG_SERVICE_IS_ACCESSIBILITY_TOOL = 0x00000200;
+    /** @hide */
+    public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000080;
 
     /** @hide */
-    public static final int FLAG_REPORT_MASK =
-            FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS
-                    | FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS
-                    | FLAG_SERVICE_IS_ACCESSIBILITY_TOOL;
+    public static final int FLAG_REPORT_VIEW_IDS = 0x00000100;
+
+    /** @hide */
+    public static final int FLAG_REPORT_MASK = 0x00000180;
 
     // Actions.
 
@@ -1171,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);
@@ -1186,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.
      */
@@ -1908,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();
@@ -1935,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
@@ -3657,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.
@@ -3680,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.");
         }
@@ -4514,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/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index 789c740..036316e 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -72,7 +72,6 @@
     private static final int PROPERTY_FULL_SCREEN = 0x00000080;
     private static final int PROPERTY_SCROLLABLE = 0x00000100;
     private static final int PROPERTY_IMPORTANT_FOR_ACCESSIBILITY = 0x00000200;
-    private static final int PROPERTY_ACCESSIBILITY_DATA_PRIVATE = 0x00000400;
 
     private static final int GET_SOURCE_PREFETCH_FLAGS =
             AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS
@@ -160,8 +159,6 @@
             important = root.isImportantForAccessibility();
             rootViewId = root.getAccessibilityViewId();
             mSourceWindowId = root.getAccessibilityWindowId();
-            setBooleanProperty(PROPERTY_ACCESSIBILITY_DATA_PRIVATE,
-                    root.isAccessibilityDataPrivate());
         }
         setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, important);
         mSourceNodeId = AccessibilityNodeInfo.makeNodeId(rootViewId, virtualDescendantId);
@@ -391,23 +388,6 @@
     }
 
     /**
-     * @see AccessibilityEvent#isAccessibilityDataPrivate
-     * @hide
-     */
-    boolean isAccessibilityDataPrivate() {
-        return getBooleanProperty(PROPERTY_ACCESSIBILITY_DATA_PRIVATE);
-    }
-
-    /**
-     * @see AccessibilityEvent#setAccessibilityDataPrivate
-     * @hide
-     */
-    void setAccessibilityDataPrivate(boolean accessibilityDataPrivate) {
-        enforceNotSealed();
-        setBooleanProperty(PROPERTY_ACCESSIBILITY_DATA_PRIVATE, accessibilityDataPrivate);
-    }
-
-    /**
      * Gets the number of items that can be visited.
      *
      * @return The number of items.
@@ -961,8 +941,6 @@
         appendUnless(false, PROPERTY_CHECKED, builder);
         appendUnless(false, PROPERTY_FULL_SCREEN, builder);
         appendUnless(false, PROPERTY_SCROLLABLE, builder);
-        appendUnless(false, PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, builder);
-        appendUnless(false, PROPERTY_ACCESSIBILITY_DATA_PRIVATE, builder);
 
         append(builder, "BeforeText", mBeforeText);
         append(builder, "FromIndex", mFromIndex);
@@ -996,8 +974,6 @@
             case PROPERTY_SCROLLABLE: return "Scrollable";
             case PROPERTY_IMPORTANT_FOR_ACCESSIBILITY:
                 return "ImportantForAccessibility";
-            case PROPERTY_ACCESSIBILITY_DATA_PRIVATE:
-                return "AccessibilityDataPrivate";
             default: return Integer.toHexString(prop);
         }
     }
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/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt b/core/java/android/view/inputmethod/DeleteGesture.aidl
similarity index 82%
copy from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt
copy to core/java/android/view/inputmethod/DeleteGesture.aidl
index 5f4da8a..e9f31dd 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt
+++ b/core/java/android/view/inputmethod/DeleteGesture.aidl
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.theme
+package android.view.inputmethod;
 
-object SettingsOpacity {
-    const val Full = 1f
-    const val Disabled = 0.38f
-}
+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/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0c03d10..6049613 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -18,7 +18,6 @@
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
-import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS;
 import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_IMMEDIATE;
 import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_MONITOR;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID;
@@ -92,6 +91,7 @@
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewRootImpl;
+import android.view.WindowInsets;
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
@@ -2086,15 +2086,6 @@
                 Log.w(TAG, "Ignoring startStylusHandwriting: View's window does not have focus.");
                 return;
             }
-            if (mServedInputConnection != null && getDelegate().hasActiveConnection(view)) {
-                // TODO (b/210039666): optimize CURSOR_UPDATE_IMMEDIATE.
-                // TODO (b/210039666): Pipe IME displayId from InputBindResult and use it here.
-                //  instead of mDisplayId.
-                mServedInputConnection.requestCursorUpdatesFromImm(
-                        CURSOR_UPDATE_IMMEDIATE | CURSOR_UPDATE_MONITOR,
-                                CURSOR_UPDATE_FILTER_EDITOR_BOUNDS,
-                        mDisplayId);
-            }
 
             mServiceInvoker.startStylusHandwriting(mClient);
             // TODO(b/210039666): do we need any extra work for supporting non-native
@@ -2160,8 +2151,9 @@
                 null /* icProto */);
         synchronized (mH) {
             final View view = getServedViewLocked();
-            if (mImeInsetsConsumer != null && view != null) {
-                if (mImeInsetsConsumer.isRequestedVisible()) {
+            if (view != null) {
+                final WindowInsets rootInsets = view.getRootWindowInsets();
+                if (rootInsets != null && rootInsets.isVisible(WindowInsets.Type.ime())) {
                     hideSoftInputFromWindow(view.getWindowToken(), hideFlags, null,
                             SoftInputShowHideReason.HIDE_TOGGLE_SOFT_INPUT);
                 } else {
@@ -2341,9 +2333,6 @@
         editorInfo.packageName = view.getContext().getOpPackageName();
         editorInfo.autofillId = view.getAutofillId();
         editorInfo.fieldId = view.getId();
-        synchronized (mH) {
-            editorInfo.setInitialToolType(mCurRootView.getLastClickToolType());
-        }
         InputConnection ic = view.onCreateInputConnection(editorInfo);
         if (DEBUG) Log.v(TAG, "Starting input: editorInfo=" + editorInfo + " ic=" + ic);
 
@@ -2383,6 +2372,8 @@
                 startInputFlags |= StartInputFlags.INITIAL_CONNECTION;
             }
 
+            editorInfo.setInitialToolType(mCurRootView.getLastClickToolType());
+
             // Hook 'em up and let 'er rip.
             mCurrentEditorInfo = editorInfo.createCopyInternal();
             // Store the previously served connection so that we can determine whether it is safe
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt b/core/java/android/view/inputmethod/InsertGesture.aidl
similarity index 82%
copy from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt
copy to core/java/android/view/inputmethod/InsertGesture.aidl
index 5f4da8a..9cdb14a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt
+++ b/core/java/android/view/inputmethod/InsertGesture.aidl
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.theme
+package android.view.inputmethod;
 
-object SettingsOpacity {
-    const val Full = 1f
-    const val Disabled = 0.38f
-}
+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/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt b/core/java/android/view/inputmethod/SelectGesture.aidl
similarity index 82%
copy from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt
copy to core/java/android/view/inputmethod/SelectGesture.aidl
index 5f4da8a..65da4f3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt
+++ b/core/java/android/view/inputmethod/SelectGesture.aidl
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.theme
+package android.view.inputmethod;
 
-object SettingsOpacity {
-    const val Full = 1f
-    const val Disabled = 0.38f
-}
+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/view/selectiontoolbar/ShowInfo.java b/core/java/android/view/selectiontoolbar/ShowInfo.java
index d9adef2..28b4480 100644
--- a/core/java/android/view/selectiontoolbar/ShowInfo.java
+++ b/core/java/android/view/selectiontoolbar/ShowInfo.java
@@ -75,6 +75,11 @@
     @NonNull
     private final IBinder mHostInputToken;
 
+    /**
+     * If the host application uses light theme.
+     */
+    private final boolean mIsLightTheme;
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -109,6 +114,8 @@
      * @param hostInputToken
      *   The host application's input token, this allows the remote render service to transfer
      *   the touch focus to the host application.
+     * @param isLightTheme
+     *   If the host application uses light theme.
      */
     @DataClass.Generated.Member
     public ShowInfo(
@@ -118,7 +125,8 @@
             @NonNull Rect contentRect,
             int suggestedWidth,
             @NonNull Rect viewPortOnScreen,
-            @NonNull IBinder hostInputToken) {
+            @NonNull IBinder hostInputToken,
+            boolean isLightTheme) {
         this.mWidgetToken = widgetToken;
         this.mLayoutRequired = layoutRequired;
         this.mMenuItems = menuItems;
@@ -134,6 +142,7 @@
         this.mHostInputToken = hostInputToken;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mHostInputToken);
+        this.mIsLightTheme = isLightTheme;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -196,6 +205,14 @@
         return mHostInputToken;
     }
 
+    /**
+     * If the host application uses light theme.
+     */
+    @DataClass.Generated.Member
+    public boolean isIsLightTheme() {
+        return mIsLightTheme;
+    }
+
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -209,7 +226,8 @@
                 "contentRect = " + mContentRect + ", " +
                 "suggestedWidth = " + mSuggestedWidth + ", " +
                 "viewPortOnScreen = " + mViewPortOnScreen + ", " +
-                "hostInputToken = " + mHostInputToken +
+                "hostInputToken = " + mHostInputToken + ", " +
+                "isLightTheme = " + mIsLightTheme +
         " }";
     }
 
@@ -232,7 +250,8 @@
                 && java.util.Objects.equals(mContentRect, that.mContentRect)
                 && mSuggestedWidth == that.mSuggestedWidth
                 && java.util.Objects.equals(mViewPortOnScreen, that.mViewPortOnScreen)
-                && java.util.Objects.equals(mHostInputToken, that.mHostInputToken);
+                && java.util.Objects.equals(mHostInputToken, that.mHostInputToken)
+                && mIsLightTheme == that.mIsLightTheme;
     }
 
     @Override
@@ -249,6 +268,7 @@
         _hash = 31 * _hash + mSuggestedWidth;
         _hash = 31 * _hash + java.util.Objects.hashCode(mViewPortOnScreen);
         _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken);
+        _hash = 31 * _hash + Boolean.hashCode(mIsLightTheme);
         return _hash;
     }
 
@@ -258,9 +278,10 @@
         // You can override field parcelling by defining methods like:
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
-        byte flg = 0;
+        int flg = 0;
         if (mLayoutRequired) flg |= 0x2;
-        dest.writeByte(flg);
+        if (mIsLightTheme) flg |= 0x80;
+        dest.writeInt(flg);
         dest.writeLong(mWidgetToken);
         dest.writeParcelableList(mMenuItems, flags);
         dest.writeTypedObject(mContentRect, flags);
@@ -280,8 +301,9 @@
         // You can override field unparcelling by defining methods like:
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
-        byte flg = in.readByte();
+        int flg = in.readInt();
         boolean layoutRequired = (flg & 0x2) != 0;
+        boolean isLightTheme = (flg & 0x80) != 0;
         long widgetToken = in.readLong();
         List<ToolbarMenuItem> menuItems = new java.util.ArrayList<>();
         in.readParcelableList(menuItems, ToolbarMenuItem.class.getClassLoader(), android.view.selectiontoolbar.ToolbarMenuItem.class);
@@ -305,6 +327,7 @@
         this.mHostInputToken = hostInputToken;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mHostInputToken);
+        this.mIsLightTheme = isLightTheme;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -324,10 +347,10 @@
     };
 
     @DataClass.Generated(
-            time = 1643186262604L,
+            time = 1645108384245L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/ShowInfo.java",
-            inputSignatures = "private final  long mWidgetToken\nprivate final  boolean mLayoutRequired\nprivate final @android.annotation.NonNull java.util.List<android.view.selectiontoolbar.ToolbarMenuItem> mMenuItems\nprivate final @android.annotation.NonNull android.graphics.Rect mContentRect\nprivate final  int mSuggestedWidth\nprivate final @android.annotation.NonNull android.graphics.Rect mViewPortOnScreen\nprivate final @android.annotation.NonNull android.os.IBinder mHostInputToken\nclass ShowInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
+            inputSignatures = "private final  long mWidgetToken\nprivate final  boolean mLayoutRequired\nprivate final @android.annotation.NonNull java.util.List<android.view.selectiontoolbar.ToolbarMenuItem> mMenuItems\nprivate final @android.annotation.NonNull android.graphics.Rect mContentRect\nprivate final  int mSuggestedWidth\nprivate final @android.annotation.NonNull android.graphics.Rect mViewPortOnScreen\nprivate final @android.annotation.NonNull android.os.IBinder mHostInputToken\nprivate final  boolean mIsLightTheme\nclass ShowInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/translation/TranslationManager.java b/core/java/android/view/translation/TranslationManager.java
index 55c0726..5aad823 100644
--- a/core/java/android/view/translation/TranslationManager.java
+++ b/core/java/android/view/translation/TranslationManager.java
@@ -40,11 +40,11 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.SyncResultReceiver;
 
+import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeoutException;
@@ -92,7 +92,8 @@
     private final Map<Consumer<TranslationCapability>, IRemoteCallback> mCapabilityCallbacks =
             new ArrayMap<>();
 
-    private static final Random ID_GENERATOR = new Random();
+    // TODO(b/158778794): make the session ids truly globally unique across processes
+    private static final SecureRandom ID_GENERATOR = new SecureRandom();
     private final Object mLock = new Object();
 
     @NonNull
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index b233e54..b21c5b3 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -226,6 +226,8 @@
     final UndoInputFilter mUndoInputFilter = new UndoInputFilter(this);
     boolean mAllowUndo = true;
 
+    private int mLastToolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+
     private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
     // Cursor Controllers.
@@ -1732,6 +1734,9 @@
     @VisibleForTesting
     public void onTouchEvent(MotionEvent event) {
         final boolean filterOutEvent = shouldFilterOutTouchEvent(event);
+
+        mLastToolType = event.getToolType(event.getActionIndex());
+
         mLastButtonState = event.getButtonState();
         if (filterOutEvent) {
             if (event.getActionMasked() == MotionEvent.ACTION_UP) {
@@ -1784,7 +1789,7 @@
     }
 
     private void showFloatingToolbar() {
-        if (mTextActionMode != null) {
+        if (mTextActionMode != null && showUIForFingerInput()) {
             // Delay "show" so it doesn't interfere with click confirmations
             // or double-clicks that could "dismiss" the floating toolbar.
             int delay = ViewConfiguration.getDoubleTapTimeout();
@@ -1864,7 +1869,8 @@
             final CursorController cursorController = mTextView.hasSelection()
                     ? getSelectionController() : getInsertionController();
             if (cursorController != null && !cursorController.isActive()
-                    && !cursorController.isCursorBeingModified()) {
+                    && !cursorController.isCursorBeingModified()
+                    && showUIForFingerInput()) {
                 cursorController.show();
             }
         }
@@ -2515,6 +2521,10 @@
             return false;
         }
 
+        if (!showUIForFingerInput()) {
+            return false;
+        }
+
         ActionMode.Callback actionModeCallback = new TextActionModeCallback(actionMode);
         mTextActionMode = mTextView.startActionMode(actionModeCallback, ActionMode.TYPE_FLOATING);
         registerOnBackInvokedCallback();
@@ -2667,7 +2677,7 @@
                     mTextView.postDelayed(mShowSuggestionRunnable,
                             ViewConfiguration.getDoubleTapTimeout());
                 } else if (hasInsertionController()) {
-                    if (shouldInsertCursor) {
+                    if (shouldInsertCursor && showUIForFingerInput()) {
                         getInsertionController().show();
                     } else {
                         getInsertionController().hide();
@@ -5397,7 +5407,8 @@
             final PointF showPosInView = new PointF();
             final boolean shouldShow = checkForTransforms() /*check not rotated and compute scale*/
                     && !tooLargeTextForMagnifier()
-                    && obtainMagnifierShowCoordinates(event, showPosInView);
+                    && obtainMagnifierShowCoordinates(event, showPosInView)
+                    && showUIForFingerInput();
             if (shouldShow) {
                 // Make the cursor visible and stop blinking.
                 mRenderCursorRegardlessTiming = true;
@@ -6343,6 +6354,15 @@
         }
     }
 
+    /**
+     * Returns true when need to show UIs, e.g. floating toolbar, etc, for finger based interaction.
+     *
+     * @return true if UIs need to show for finger interaciton. false if UIs are not necessary.
+     */
+    public boolean showUIForFingerInput() {
+        return mLastToolType != MotionEvent.TOOL_TYPE_MOUSE;
+    }
+
     /** Controller for the insertion cursor. */
     @VisibleForTesting
     public class InsertionPointCursorController implements CursorController {
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/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index a0ec48b..54a415c 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -301,7 +301,11 @@
             final SelectionModifierCursorController controller = mEditor.getSelectionController();
             if (controller != null
                     && (mTextView.isTextSelectable() || mTextView.isTextEditable())) {
-                controller.show();
+                if (mEditor.showUIForFingerInput()) {
+                    controller.show();
+                } else {
+                    controller.hide();
+                }
             }
             if (result != null) {
                 switch (actionMode) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3f87ec2..cd1c23c 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4887,20 +4887,28 @@
     }
 
     /**
-     * Set the line break style for text wrapping.
+     * Sets the line-break style for text wrapping.
      *
-     * The line break style to indicates the line break strategies can be used when
-     * calculating the text wrapping. The line break style affects rule-based breaking. It
-     * specifies the strictness of line-breaking rules.
-     * There are several types for the line break style:
-     * {@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE},
-     * {@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} and
-     * {@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}. The default values of the line break style
-     * is {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, indicating no breaking rule is specified.
-     * See <a href="https://www.w3.org/TR/css-text-3/#line-break-property">
-     *         the line-break property</a>
+     * <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.
      *
-     * @param lineBreakStyle the line break style for the text.
+     * <p>The following are types of line-break styles:
+     * <ul>
+     *   <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>See the
+     * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
+     * line-break property</a> for more information.
+     *
+     * @param lineBreakStyle The line-break style for the text.
      */
     public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) {
         if (mLineBreakStyle != lineBreakStyle) {
@@ -4914,17 +4922,22 @@
     }
 
     /**
-     * Set the line break word style for text wrapping.
+     * Sets the line-break word style for text wrapping.
      *
-     * The line break word style affects dictionary-based breaking and provide phrase-based
-     * breaking opportunities. The type for the line break word style is
-     * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE}. The default values of the line break
-     * word style is {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, indicating no breaking rule
-     * is specified.
-     * See <a href="https://www.w3.org/TR/css-text-3/#word-break-property">
-     *         the word-break property</a>
+     * <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.
      *
-     * @param lineBreakWordStyle the line break word style for the tet
+     * <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>See the
+     * <a href="https://www.w3.org/TR/css-text-3/#word-break-property" class="external">
+     * word-break property</a> for more information.
+     *
+     * @param lineBreakWordStyle The line-break word style for the text.
      */
     public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
         mUserSpeficiedLineBreakwordStyle = true;
@@ -4939,18 +4952,18 @@
     }
 
     /**
-     * Get the current line break style for text wrapping.
+     * Gets the current line-break style for text wrapping.
      *
-     * @return the current line break style to be used for text wrapping.
+     * @return The line-break style to be used for text wrapping.
      */
     public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() {
         return mLineBreakStyle;
     }
 
     /**
-     * Get the current line word break style for text wrapping.
+     * Gets the current line-break word style for text wrapping.
      *
-     * @return the current line break word style to be used for text wrapping.
+     * @return The line-break word style to be used for text wrapping.
      */
     public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() {
         return mLineBreakWordStyle;
@@ -12076,13 +12089,6 @@
     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
         super.onPopulateAccessibilityEventInternal(event);
 
-        if (this.isAccessibilityDataPrivate() && !event.isAccessibilityDataPrivate()) {
-            // This view's accessibility data is private, but another view that generated this event
-            // is not, so don't append this view's text to the event in order to prevent sharing
-            // this view's contents with non-accessibility-tool services.
-            return;
-        }
-
         final CharSequence text = getTextForAccessibility();
         if (!TextUtils.isEmpty(text)) {
             event.getText().add(text);
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 e567ced..7359172 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -16,7 +16,7 @@
 
 package android.window;
 
-import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENT_TO_TASK;
+import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED;
@@ -32,10 +32,8 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.util.SparseArray;
 import android.view.RemoteAnimationDefinition;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -74,12 +72,6 @@
      */
     private final Executor mExecutor;
 
-    // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next release.
-    /** Map from Task id to client tokens of TaskFragments in the Task. */
-    private final SparseArray<List<IBinder>> mTaskIdToFragmentTokens = new SparseArray<>();
-    /** Map from Task id to Task configuration. */
-    private final SparseArray<Configuration> mTaskIdToConfigurations = new SparseArray<>();
-
     public TaskFragmentOrganizer(@NonNull Executor executor) {
         mExecutor = executor;
     }
@@ -147,52 +139,81 @@
         }
     }
 
-    /** Called when a TaskFragment is created and organized by this organizer. */
-    public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {}
-
-    /** Called when the status of an organized TaskFragment is changed. */
-    public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {}
-
-    /** Called when an organized TaskFragment is removed. */
-    public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {}
-
     /**
-     * Called when the parent leaf Task of organized TaskFragments is changed.
-     * When the leaf Task is changed, the organizer may want to update the TaskFragments in one
-     * transaction.
+     * Notifies the server that the organizer has finished handling the given transaction. The
+     * server should apply the given {@link WindowContainerTransaction} for the necessary changes.
      *
-     * For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new
-     * Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override
-     * bounds.
-     */
-    public void onTaskFragmentParentInfoChanged(
-            @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) {}
-
-    /**
-     * Called when the parent leaf Task of organized TaskFragments is changed.
-     * When the leaf Task is changed, the organizer may want to update the TaskFragments in one
-     * transaction.
-     *
-     * For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new
-     * Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override
-     * bounds.
+     * @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 onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) {
-        // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next release.
-        final List<IBinder> tokens = mTaskIdToFragmentTokens.get(taskId);
-        if (tokens == null || tokens.isEmpty()) {
-            return;
-        }
-        for (int i = tokens.size() - 1; i >= 0; i--) {
-            onTaskFragmentParentInfoChanged(tokens.get(i), parentConfig);
+    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
+     *              need to call {@link #applyTransaction} as it will be applied by the caller.
+     * @param taskFragmentInfo  Info of the TaskFragment that is created.
+     */
+    public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
+            @NonNull TaskFragmentInfo taskFragmentInfo) {}
+
+    /**
+     * Called when the status of an organized TaskFragment is changed.
+     *
+     * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed. No
+     *              need to call {@link #applyTransaction} as it will be applied by the caller.
+     * @param taskFragmentInfo  Info of the TaskFragment that is changed.
+     */
+    public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
+            @NonNull TaskFragmentInfo taskFragmentInfo) {}
+
+    /**
+     * Called when an organized TaskFragment is removed.
+     *
+     * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed. No
+     *              need to call {@link #applyTransaction} as it will be applied by the caller.
+     * @param taskFragmentInfo  Info of the TaskFragment that is removed.
+     */
+    public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
+            @NonNull TaskFragmentInfo taskFragmentInfo) {}
+
+    /**
+     * Called when the parent leaf Task of organized TaskFragments is changed.
+     * When the leaf Task is changed, the organizer may want to update the TaskFragments in one
+     * transaction.
+     *
+     * For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new
+     * Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override
+     * bounds.
+     *
+     * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed. No
+     *              need to call {@link #applyTransaction} as it will be applied by the caller.
+     * @param taskId    Id of the parent Task that is changed.
+     * @param parentConfig  Config of the parent Task.
+     */
+    public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, int taskId,
+            @NonNull Configuration parentConfig) {}
+
+    /**
      * Called when the {@link WindowContainerTransaction} created with
      * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
      *
+     * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed. No
+     *              need to call {@link #applyTransaction} as it will be applied by the caller.
      * @param errorCallbackToken    token set in
      *                             {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
      * @param taskFragmentInfo  The {@link TaskFragmentInfo}. This could be {@code null} if no
@@ -201,16 +222,18 @@
      *                          transaction operation.
      * @param exception             exception from the server side.
      */
-    public void onTaskFragmentError(
+    public void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
             @NonNull IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo,
             int opType, @NonNull Throwable exception) {}
 
     /**
      * Called when an Activity is reparented to the Task with organized TaskFragment. For example,
      * when an Activity enters and then exits Picture-in-picture, it will be reparented back to its
-     * orginial Task. In this case, we need to notify the organizer so that it can check if the
+     * original Task. In this case, we need to notify the organizer so that it can check if the
      * Activity matches any split rule.
      *
+     * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed. No
+     *              need to call {@link #applyTransaction} as it will be applied by the caller.
      * @param taskId            The Task that the activity is reparented to.
      * @param activityIntent    The intent that the activity is original launched with.
      * @param activityToken     If the activity belongs to the same process as the organizer, this
@@ -218,10 +241,9 @@
      *                          different process, the server will generate a temporary token that
      *                          the organizer can use to reparent the activity through
      *                          {@link WindowContainerTransaction} if needed.
-     * @hide
      */
-    public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
-            @NonNull IBinder activityToken) {}
+    public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
+            int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) {}
 
     /**
      * Called when the transaction is ready so that the organizer can update the TaskFragments based
@@ -229,50 +251,27 @@
      * @hide
      */
     public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
         final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
         for (TaskFragmentTransaction.Change change : changes) {
-            // TODO(b/240519866): apply all changes in one WCT.
             final int taskId = change.getTaskId();
             switch (change.getType()) {
                 case TYPE_TASK_FRAGMENT_APPEARED:
-                    // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next
-                    // release.
-                    if (!mTaskIdToFragmentTokens.contains(taskId)) {
-                        mTaskIdToFragmentTokens.put(taskId, new ArrayList<>());
-                    }
-                    mTaskIdToFragmentTokens.get(taskId).add(change.getTaskFragmentToken());
-                    onTaskFragmentParentInfoChanged(change.getTaskFragmentToken(),
-                            mTaskIdToConfigurations.get(taskId));
-
-                    onTaskFragmentAppeared(change.getTaskFragmentInfo());
+                    onTaskFragmentAppeared(wct, change.getTaskFragmentInfo());
                     break;
                 case TYPE_TASK_FRAGMENT_INFO_CHANGED:
-                    onTaskFragmentInfoChanged(change.getTaskFragmentInfo());
+                    onTaskFragmentInfoChanged(wct, change.getTaskFragmentInfo());
                     break;
                 case TYPE_TASK_FRAGMENT_VANISHED:
-                    // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next
-                    // release.
-                    if (mTaskIdToFragmentTokens.contains(taskId)) {
-                        final List<IBinder> tokens = mTaskIdToFragmentTokens.get(taskId);
-                        tokens.remove(change.getTaskFragmentToken());
-                        if (tokens.isEmpty()) {
-                            mTaskIdToFragmentTokens.remove(taskId);
-                            mTaskIdToConfigurations.remove(taskId);
-                        }
-                    }
-
-                    onTaskFragmentVanished(change.getTaskFragmentInfo());
+                    onTaskFragmentVanished(wct, change.getTaskFragmentInfo());
                     break;
                 case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED:
-                    // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next
-                    // release.
-                    mTaskIdToConfigurations.put(taskId, change.getTaskConfiguration());
-
-                    onTaskFragmentParentInfoChanged(taskId, change.getTaskConfiguration());
+                    onTaskFragmentParentInfoChanged(wct, taskId, change.getTaskConfiguration());
                     break;
                 case TYPE_TASK_FRAGMENT_ERROR:
                     final Bundle errorBundle = change.getErrorBundle();
                     onTaskFragmentError(
+                            wct,
                             change.getErrorCallbackToken(),
                             errorBundle.getParcelable(
                                     KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class),
@@ -280,8 +279,9 @@
                             errorBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION,
                                     java.lang.Throwable.class));
                     break;
-                case TYPE_ACTIVITY_REPARENT_TO_TASK:
-                    onActivityReparentToTask(
+                case TYPE_ACTIVITY_REPARENTED_TO_TASK:
+                    onActivityReparentedToTask(
+                            wct,
                             change.getTaskId(),
                             change.getActivityIntent(),
                             change.getActivityToken());
@@ -291,6 +291,9 @@
                             "Unknown TaskFragmentEvent=" + change.getType());
             }
         }
+
+        // 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 755864f..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(',');
@@ -122,7 +137,7 @@
      * then exits Picture-in-picture, it will be reparented back to its original Task. In this case,
      * we need to notify the organizer so that it can check if the Activity matches any split rule.
      */
-    public static final int TYPE_ACTIVITY_REPARENT_TO_TASK = 6;
+    public static final int TYPE_ACTIVITY_REPARENTED_TO_TASK = 6;
 
     @IntDef(prefix = { "TYPE_" }, value = {
             TYPE_TASK_FRAGMENT_APPEARED,
@@ -130,7 +145,7 @@
             TYPE_TASK_FRAGMENT_VANISHED,
             TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED,
             TYPE_TASK_FRAGMENT_ERROR,
-            TYPE_ACTIVITY_REPARENT_TO_TASK
+            TYPE_ACTIVITY_REPARENTED_TO_TASK
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface ChangeType {}
@@ -247,7 +262,7 @@
 
         /**
          * Intent of the activity that is reparented to the Task for
-         * {@link #TYPE_ACTIVITY_REPARENT_TO_TASK}.
+         * {@link #TYPE_ACTIVITY_REPARENTED_TO_TASK}.
          */
         public Change setActivityIntent(@NonNull Intent intent) {
             mActivityIntent = requireNonNull(intent);
@@ -255,7 +270,7 @@
         }
 
         /**
-         * Token of the reparent activity for {@link #TYPE_ACTIVITY_REPARENT_TO_TASK}.
+         * Token of the reparent activity for {@link #TYPE_ACTIVITY_REPARENTED_TO_TASK}.
          * If the activity belongs to the same process as the organizer, this will be the actual
          * activity token; if the activity belongs to a different process, the server will generate
          * a temporary token that the organizer can use to reparent the activity through
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/BilingualSuggestedLocaleAdapter.java b/core/java/com/android/internal/app/BilingualSuggestedLocaleAdapter.java
index d2cb30e..833d88c 100644
--- a/core/java/com/android/internal/app/BilingualSuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/BilingualSuggestedLocaleAdapter.java
@@ -20,6 +20,8 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
 import android.widget.TextView;
 
 import com.android.internal.R;
@@ -36,11 +38,21 @@
 
     private final Locale mSecondaryLocale;
     private final int mSecondaryLocaleTextDir;
+    private final boolean mShowSelection;
+    private LocaleStore.LocaleInfo mSelectedLocaleInfo;
 
     public BilingualSuggestedLocaleAdapter(
             Set<LocaleStore.LocaleInfo> localeOptions,
             boolean countryMode,
             Locale secondaryLocale) {
+        this(localeOptions, countryMode, secondaryLocale, false);
+    }
+
+    public BilingualSuggestedLocaleAdapter(
+            Set<LocaleStore.LocaleInfo> localeOptions,
+            boolean countryMode,
+            Locale secondaryLocale,
+            boolean showLastSelected) {
         super(localeOptions, countryMode);
         mSecondaryLocale = secondaryLocale;
         if (TextUtils.getLayoutDirectionFromLocale(secondaryLocale) == View.LAYOUT_DIRECTION_RTL) {
@@ -48,6 +60,7 @@
         } else {
             mSecondaryLocaleTextDir = View.TEXT_DIRECTION_LTR;
         }
+        mShowSelection = showLastSelected;
     }
 
     @Override
@@ -90,11 +103,55 @@
                 }
 
                 LocaleStore.LocaleInfo item = (LocaleStore.LocaleInfo) getItem(position);
+                if (mShowSelection) {
+                    setItemState(isSelectedLocaleInfo(item), convertView);
+                }
                 setLocaleToListItem(convertView, item);
         }
         return convertView;
     }
 
+    /**
+     * Set locale info as selected. Selected info can be the only one. Passing null would result to
+     * nothing is selected.
+     */
+    public void setSelectedLocaleInfo(LocaleStore.LocaleInfo info) {
+        mSelectedLocaleInfo = info;
+        notifyDataSetChanged();
+    }
+
+    /** Return selected locale info. */
+    public LocaleStore.LocaleInfo getSelectedLocaleInfo() {
+        return mSelectedLocaleInfo;
+    }
+
+    private boolean isSelectedLocaleInfo(LocaleStore.LocaleInfo item) {
+        return item != null
+                && mSelectedLocaleInfo != null
+                && item.getId().equals(mSelectedLocaleInfo.getId());
+    }
+
+    private void setItemState(boolean selected, View itemView) {
+        RelativeLayout background = (RelativeLayout) itemView;
+        ImageView indicator = itemView.findViewById(R.id.indicator);
+        TextView textNative = itemView.findViewById(R.id.locale_native);
+        TextView textSecondary = itemView.findViewById(R.id.locale_secondary);
+
+        if (indicator == null || textNative == null || textSecondary == null) {
+            return;
+        }
+
+        textNative.setSelected(selected);
+        textSecondary.setSelected(selected);
+        if (selected) {
+            background.setBackgroundResource(R.drawable.language_picker_item_bg_selected);
+            indicator.setVisibility(View.VISIBLE);
+        } else {
+            background.setBackgroundResource(0);
+            indicator.setVisibility(View.GONE);
+        }
+    }
+
     private void setHeaderText(
             TextView textView, int languageStringResourceId, int regionStringResourceId) {
         if (mCountryMode) {
@@ -114,7 +171,7 @@
         textNative.setTextLocale(localeInfo.getLocale());
         textNative.setContentDescription(localeInfo.getContentDescription(mCountryMode));
 
-        TextView textSecondary = (TextView) itemView.findViewById(R.id.locale_secondary);
+        TextView textSecondary = itemView.findViewById(R.id.locale_secondary);
         textSecondary.setText(localeInfo.getLocale().getDisplayLanguage(mSecondaryLocale));
         textSecondary.setTextDirection(mSecondaryLocaleTextDir);
         if (mCountryMode) {
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 2bef10f..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;
 
@@ -968,6 +973,46 @@
         });
     }
 
+    @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)}.
      *
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 72de78c..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;
@@ -292,7 +293,10 @@
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL,
     };
 
-    private static volatile InteractionJankMonitor sInstance;
+    private static class InstanceHolder {
+        public static final InteractionJankMonitor INSTANCE =
+            new InteractionJankMonitor(new HandlerThread(DEFAULT_WORKER_NAME));
+    }
 
     private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
             this::updateProperties;
@@ -384,15 +388,7 @@
      * @return instance of InteractionJankMonitor
      */
     public static InteractionJankMonitor getInstance() {
-        // Use DCL here since this method might be invoked very often.
-        if (sInstance == null) {
-            synchronized (InteractionJankMonitor.class) {
-                if (sInstance == null) {
-                    sInstance = new InteractionJankMonitor(new HandlerThread(DEFAULT_WORKER_NAME));
-                }
-            }
-        }
-        return sInstance;
+        return InstanceHolder.INSTANCE;
     }
 
     /**
@@ -402,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/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index fa6fa55..0a29fc52 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -1169,15 +1169,7 @@
 
     }
 
-    /** @hide */
-    public static void startForWifi(Context context) {
-        new BinderCallsStats.SettingsObserver(
-            context,
-            new BinderCallsStats(
-                new BinderCallsStats.Injector(),
-                com.android.internal.os.BinderLatencyProto.Dims.WIFI));
 
-    }
 
     /**
      * Settings observer for other processes (not system_server).
diff --git a/core/java/com/android/internal/policy/DecorContext.java b/core/java/com/android/internal/policy/DecorContext.java
index 5e34c15..134a917 100644
--- a/core/java/com/android/internal/policy/DecorContext.java
+++ b/core/java/com/android/internal/policy/DecorContext.java
@@ -137,4 +137,13 @@
         }
         return false;
     }
+
+    @Override
+    public boolean isConfigurationContext() {
+        Context context = mContext.get();
+        if (context != null) {
+            return context.isConfigurationContext();
+        }
+        return false;
+    }
 }
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/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index b9243ec..9474f6f 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -1,6 +1,7 @@
 package com.android.internal.util;
 
 import static android.content.Intent.ACTION_USER_SWITCHED;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -27,7 +28,6 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
-import android.view.WindowManager;
 import android.view.WindowManager.ScreenshotSource;
 import android.view.WindowManager.ScreenshotType;
 
@@ -42,10 +42,15 @@
     public static final int SCREENSHOT_MSG_PROCESS_COMPLETE = 2;
 
     /**
-     * Describes a screenshot request (to make it easier to pass data through to the handler).
+     * Describes a screenshot request.
      */
     public static class ScreenshotRequest implements Parcelable {
+        @ScreenshotType
+        private final int mType;
+
+        @ScreenshotSource
         private final int mSource;
+
         private final Bundle mBitmapBundle;
         private final Rect mBoundsInScreen;
         private final Insets mInsets;
@@ -53,20 +58,27 @@
         private final int mUserId;
         private final ComponentName mTopComponent;
 
-        @VisibleForTesting
-        public ScreenshotRequest(int source) {
-            mSource = source;
-            mBitmapBundle = null;
-            mBoundsInScreen = null;
-            mInsets = null;
-            mTaskId = -1;
-            mUserId = -1;
-            mTopComponent = null;
+
+        public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source) {
+            this(type, source, /* topComponent */ null);
         }
 
-        @VisibleForTesting
-        public ScreenshotRequest(int source, Bundle bitmapBundle, Rect boundsInScreen,
-                Insets insets, int taskId, int userId, ComponentName topComponent) {
+        public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
+                ComponentName topComponent) {
+            this(type,
+                source,
+                /* bitmapBundle*/ null,
+                /* boundsInScreen */ null,
+                /* insets */ null,
+                /* taskId */ -1,
+                /* userId */ -1,
+                topComponent);
+        }
+
+        public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
+                Bundle bitmapBundle, Rect boundsInScreen, Insets insets, int taskId, int userId,
+                ComponentName topComponent) {
+            mType = type;
             mSource = source;
             mBitmapBundle = bitmapBundle;
             mBoundsInScreen = boundsInScreen;
@@ -77,6 +89,7 @@
         }
 
         ScreenshotRequest(Parcel in) {
+            mType = in.readInt();
             mSource = in.readInt();
             if (in.readInt() == 1) {
                 mBitmapBundle = in.readBundle(getClass().getClassLoader());
@@ -96,6 +109,12 @@
             }
         }
 
+        @ScreenshotType
+        public int getType() {
+            return mType;
+        }
+
+        @ScreenshotSource
         public int getSource() {
             return mSource;
         }
@@ -131,6 +150,7 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mType);
             dest.writeInt(mSource);
             if (mBitmapBundle == null) {
                 dest.writeInt(0);
@@ -208,8 +228,7 @@
          * Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)} .}
          *
          * <p>This Bitmap contains the HardwareBuffer from the original caller, be careful passing
-         * this
-         * Bitmap on to any other source.
+         * this Bitmap on to any other source.
          *
          * @param bundle containing the bitmap
          * @return a hardware Bitmap
@@ -261,16 +280,16 @@
      * Added to support reducing unit test duration; the method variant without a timeout argument
      * is recommended for general use.
      *
-     * @param screenshotType The type of screenshot, defined by {@link ScreenshotType}
+     * @param type The type of screenshot, defined by {@link ScreenshotType}
      * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
      * @param handler used to process messages received from the screenshot service
      * @param completionConsumer receives the URI of the captured screenshot, once saved or
      *         null if no screenshot was saved
      */
-    public void takeScreenshot(@ScreenshotType int screenshotType, @ScreenshotSource int source,
+    public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
             @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
-        ScreenshotRequest screenshotRequest = new ScreenshotRequest(source);
-        takeScreenshot(screenshotType, handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS,
+        ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
+        takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS,
                 completionConsumer);
     }
 
@@ -280,7 +299,7 @@
      * Added to support reducing unit test duration; the method variant without a timeout argument
      * is recommended for general use.
      *
-     * @param screenshotType The type of screenshot, defined by {@link ScreenshotType}
+     * @param type The type of screenshot, defined by {@link ScreenshotType}
      * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
      * @param handler used to process messages received from the screenshot service
      * @param timeoutMs time limit for processing, intended only for testing
@@ -288,10 +307,10 @@
      *         null if no screenshot was saved
      */
     @VisibleForTesting
-    public void takeScreenshot(@ScreenshotType int screenshotType, @ScreenshotSource int source,
+    public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
             @NonNull Handler handler, long timeoutMs, @Nullable Consumer<Uri> completionConsumer) {
-        ScreenshotRequest screenshotRequest = new ScreenshotRequest(source);
-        takeScreenshot(screenshotType, handler, screenshotRequest, timeoutMs, completionConsumer);
+        ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
+        takeScreenshot(handler, screenshotRequest, timeoutMs, completionConsumer);
     }
 
     /**
@@ -312,14 +331,12 @@
             @NonNull Insets insets, int taskId, int userId, ComponentName topComponent,
             @ScreenshotSource int source, @NonNull Handler handler,
             @Nullable Consumer<Uri> completionConsumer) {
-        ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, screenshotBundle,
-                boundsInScreen, insets, taskId, userId, topComponent);
-        takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, handler, screenshotRequest,
-                SCREENSHOT_TIMEOUT_MS,
-                completionConsumer);
+        ScreenshotRequest screenshotRequest = new ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE,
+                source, screenshotBundle, boundsInScreen, insets, taskId, userId, topComponent);
+        takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS, completionConsumer);
     }
 
-    private void takeScreenshot(@ScreenshotType int screenshotType, @NonNull Handler handler,
+    private void takeScreenshot(@NonNull Handler handler,
             ScreenshotRequest screenshotRequest, long timeoutMs,
             @Nullable Consumer<Uri> completionConsumer) {
         synchronized (mScreenshotLock) {
@@ -337,7 +354,7 @@
                 }
             };
 
-            Message msg = Message.obtain(null, screenshotType, screenshotRequest);
+            Message msg = Message.obtain(null, 0, screenshotRequest);
 
             Handler h = new Handler(handler.getLooper()) {
                 @Override
diff --git a/core/java/com/android/internal/view/inline/InlineTooltipUi.java b/core/java/com/android/internal/view/inline/InlineTooltipUi.java
index 3eae89e..836786d 100644
--- a/core/java/com/android/internal/view/inline/InlineTooltipUi.java
+++ b/core/java/com/android/internal/view/inline/InlineTooltipUi.java
@@ -170,9 +170,9 @@
 
             int delayTimeMs = mShowDelayConfigMs;
             try {
-                final float scale = Settings.Global.getFloat(
+                final float scale = WindowManager.fixScale(Settings.Global.getFloat(
                         anchor.getContext().getContentResolver(),
-                        Settings.Global.ANIMATOR_DURATION_SCALE);
+                        Settings.Global.ANIMATOR_DURATION_SCALE));
                 delayTimeMs *= scale;
             } catch (Settings.SettingNotFoundException e) {
                 // do nothing
diff --git a/core/java/com/android/internal/widget/LocalImageResolver.java b/core/java/com/android/internal/widget/LocalImageResolver.java
index b866723..b11ea29 100644
--- a/core/java/com/android/internal/widget/LocalImageResolver.java
+++ b/core/java/com/android/internal/widget/LocalImageResolver.java
@@ -25,6 +25,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Size;
 
@@ -108,6 +109,12 @@
                 }
                 break;
             case Icon.TYPE_RESOURCE:
+                if (!(TextUtils.isEmpty(icon.getResPackage())
+                        || context.getPackageName().equals(icon.getResPackage()))) {
+                    // We can't properly resolve icons from other packages here, so fall back.
+                    return icon.loadDrawable(context);
+                }
+
                 Drawable result = resolveImage(icon.getResId(), context, maxWidth, maxHeight);
                 if (result != null) {
                     return tintDrawable(icon, result);
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/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
index 8c2eb10..8787c39 100644
--- a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
+++ b/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.annotation.UiThread;
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -107,6 +108,7 @@
     private int mSuggestedWidth;
     private final Rect mScreenViewPort = new Rect();
     private boolean mWidthChanged = true;
+    private final boolean mIsLightTheme;
 
     private final int[] mCoordsOnScreen = new int[2];
     private final int[] mCoordsOnWindow = new int[2];
@@ -116,9 +118,17 @@
         mPopupWindow = createPopupWindow(context);
         mSelectionToolbarManager = context.getSystemService(SelectionToolbarManager.class);
         mSelectionToolbarCallback = new SelectionToolbarCallbackImpl(this);
+        mIsLightTheme = isLightTheme(context);
         mFloatingToolbarToken = NO_TOOLBAR_ID;
     }
 
+    private boolean isLightTheme(Context context) {
+        TypedArray a = context.obtainStyledAttributes(new int[]{R.attr.isLightTheme});
+        boolean isLightTheme = a.getBoolean(0, true);
+        a.recycle();
+        return isLightTheme;
+    }
+
     @UiThread
     @Override
     public void show(List<MenuItem> menuItems,
@@ -155,7 +165,7 @@
                 contentRect,
                 suggestWidth,
                 mScreenViewPort,
-                mParent.getViewRootImpl().getInputToken());
+                mParent.getViewRootImpl().getInputToken(), mIsLightTheme);
         if (DEBUG) {
             Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
                     "RemoteFloatingToolbarPopup.show() for " + showInfo);
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index ff97ab0..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
@@ -68,7 +68,7 @@
 
 ### Graphics ###
 per-file android_graphics_* = file:/graphics/java/android/graphics/OWNERS
-per-file android_hardware_HardwareBuffer.cpp = file:/graphics/java/android/graphics/OWNERS
+per-file *HardwareBuffer* = file:/graphics/java/android/graphics/OWNERS
 per-file android_hardware_SyncFence.cpp = file:/graphics/java/android/graphics/OWNERS
 per-file android_os_GraphicsEnvironment.cpp = file:platform/frameworks/native:/opengl/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_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp
index f462523..5fcc46e 100644
--- a/core/jni/android_hardware_HardwareBuffer.cpp
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -163,7 +163,7 @@
 static jlong android_hardware_HardwareBuffer_getUsage(JNIEnv* env,
     jobject clazz, jlong nativeObject) {
     GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
-    return AHardwareBuffer_convertFromGrallocUsageBits(buffer->getUsage());
+    return static_cast<jlong>(AHardwareBuffer_convertFromGrallocUsageBits(buffer->getUsage()));
 }
 
 static jlong android_hardware_HardwareBuffer_estimateSize(jlong nativeObject) {
@@ -177,7 +177,12 @@
 
     const uint32_t bufferStride =
             buffer->getStride() > 0 ? buffer->getStride() : buffer->getWidth();
-    return static_cast<jlong>(buffer->getHeight() * bufferStride * bpp);
+    return static_cast<jlong>(static_cast<uint64_t>(buffer->getHeight() * bufferStride * bpp));
+}
+
+static jlong android_hardware_HardwareBuffer_getId(jlong nativeObject) {
+    GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
+    return static_cast<jlong>(buffer->getId());
 }
 
 // ----------------------------------------------------------------------------
@@ -223,16 +228,6 @@
     }
 }
 
-GraphicBuffer* android_hardware_HardwareBuffer_getNativeGraphicBuffer(
-        JNIEnv* env, jobject hardwareBufferObj) {
-    if (env->IsInstanceOf(hardwareBufferObj, gHardwareBufferClassInfo.clazz)) {
-        return GraphicBufferWrapper_to_GraphicBuffer(
-                env->GetLongField(hardwareBufferObj, gHardwareBufferClassInfo.mNativeObject));
-    } else {
-        return nullptr;
-    }
-}
-
 jobject android_hardware_HardwareBuffer_createFromAHardwareBuffer(
         JNIEnv* env, AHardwareBuffer* hardwareBuffer) {
     GraphicBuffer* buffer = AHardwareBuffer_to_GraphicBuffer(hardwareBuffer);
@@ -295,6 +290,7 @@
 
     // --------------- @CriticalNative ----------------------
     { "nEstimateSize", "(J)J",  (void*) android_hardware_HardwareBuffer_estimateSize },
+    { "nGetId", "(J)J",  (void*) android_hardware_HardwareBuffer_getId },
 };
 // clang-format on
 
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/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h b/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
index dfd8035..964c28f 100644
--- a/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
+++ b/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
@@ -28,10 +28,6 @@
 extern AHardwareBuffer* android_hardware_HardwareBuffer_getNativeHardwareBuffer(
         JNIEnv* env, jobject hardwareBufferObj);
 
-/* Gets the underlying GraphicBuffer for a HardwareBuffer. */
-extern GraphicBuffer* android_hardware_HardwareBuffer_getNativeGraphicBuffer(
-        JNIEnv* env, jobject hardwareBufferObj);
-
 /* Returns a HardwareBuffer wrapper for the underlying AHardwareBuffer. */
 extern jobject android_hardware_HardwareBuffer_createFromAHardwareBuffer(
         JNIEnv* env, AHardwareBuffer* hardwareBuffer);
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/core/proto/android/os/tombstone.proto
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
copy to core/proto/android/os/tombstone.proto
index 7c9df10..2d3b1f3 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
+++ b/core/proto/android/os/tombstone.proto
@@ -14,10 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.common.data.model
+syntax = "proto2";
 
-/** Models a two-dimensional position */
-data class Position(
-    val x: Int,
-    val y: Int,
-)
+package android.os;
+
+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/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 322354b..789ceff 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -89,6 +89,14 @@
         // Setting for accessibility magnification for following typing.
         optional SettingProto accessibility_magnification_follow_typing_enabled = 43 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto accessibility_software_cursor_enabled = 44 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+        message SoftwareCursorSettings {
+            optional SettingProto trigger_hints_enabled = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+            optional SettingProto keyboard_shift_enabled = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        }
+
+        optional SoftwareCursorSettings accessibility_software_cursor_settings = 45 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
     }
     optional Accessibility accessibility = 2;
 
diff --git a/core/proto/android/server/peopleservice.proto b/core/proto/android/server/peopleservice.proto
index c465233..a96ec41 100644
--- a/core/proto/android/server/peopleservice.proto
+++ b/core/proto/android/server/peopleservice.proto
@@ -62,7 +62,10 @@
   // The timestamp of the last event in millis.
   optional int64 last_event_timestamp = 9;
 
-  // Next tag: 10
+  // The timestamp this conversation was created in millis.
+  optional int64 creation_timestamp = 10;
+
+  // Next tag: 11
 }
 
 // On disk data of events.
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
index 2a625b027..25a1f68 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -86,7 +86,7 @@
     optional int32 flags = 3;
 }
 
-// Next id: 8
+// Next Tag: 9
 message VibrationProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
     optional int64 start_time = 1;
@@ -94,11 +94,43 @@
     optional CombinedVibrationEffectProto effect = 3;
     optional CombinedVibrationEffectProto original_effect = 4;
     optional VibrationAttributesProto attributes = 5;
-    optional int32 status = 6;
     optional int64 duration_ms = 7;
+    optional Status status = 8;
+    reserved 6; // prev int32 status
+
+    // Also used by VibrationReported from frameworks/proto_logging/stats/atoms.proto.
+    // Next Tag: 26
+    enum Status {
+        UNKNOWN = 0;
+        RUNNING = 1;
+        FINISHED = 2;
+        FINISHED_UNEXPECTED = 3;  // Didn't terminate in the usual way.
+        FORWARDED_TO_INPUT_DEVICES = 4;
+        CANCELLED_BINDER_DIED = 5;
+        CANCELLED_BY_SCREEN_OFF = 6;
+        CANCELLED_BY_SETTINGS_UPDATE = 7;
+        CANCELLED_BY_USER = 8;
+        CANCELLED_BY_UNKNOWN_REASON = 9;
+        CANCELLED_SUPERSEDED = 10;
+        IGNORED_ERROR_APP_OPS = 11;
+        IGNORED_ERROR_CANCELLING = 12;
+        IGNORED_ERROR_SCHEDULING = 13;
+        IGNORED_ERROR_TOKEN= 14;
+        IGNORED_APP_OPS = 15;
+        IGNORED_BACKGROUND = 16;
+        IGNORED_UNKNOWN_VIBRATION = 17;
+        IGNORED_UNSUPPORTED = 18;
+        IGNORED_FOR_EXTERNAL = 19;
+        IGNORED_FOR_HIGHER_IMPORTANCE = 20;
+        IGNORED_FOR_ONGOING = 21;
+        IGNORED_FOR_POWER = 22;
+        IGNORED_FOR_RINGER_MODE = 23;
+        IGNORED_FOR_SETTINGS = 24;
+        IGNORED_SUPERSEDED = 25;
+    }
 }
 
-// Next id: 25
+// Next Tag: 25
 message VibratorManagerServiceDumpProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
     repeated int32 vibrator_ids = 1;
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/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml b/core/res/res/color/system_bar_background_semi_transparent.xml
similarity index 89%
copy from libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml
copy to core/res/res/color/system_bar_background_semi_transparent.xml
index ce8640d..839d58a 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml
+++ b/core/res/res/color/system_bar_background_semi_transparent.xml
@@ -15,5 +15,5 @@
   ~ limitations under the License.
   -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@color/tv_pip_menu_icon_unfocused" />
-</selector>
\ No newline at end of file
+    <item android:color="@color/system_neutral2_900" android:alpha="0.5" />
+</selector>
diff --git a/core/res/res/drawable/bilingual_language_item_selection_indicator.xml b/core/res/res/drawable/bilingual_language_item_selection_indicator.xml
new file mode 100644
index 0000000..78f26cc
--- /dev/null
+++ b/core/res/res/drawable/bilingual_language_item_selection_indicator.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp" android:height="24dp"
+    android:viewportWidth="24" android:viewportHeight="24">
+  <path android:fillColor="@color/language_picker_item_selected_indicator"
+      android:pathData="M9.55,18l-5.7,-5.7 1.425,-1.425L9.55,15.15l9.175,-9.175L20.15,7.4z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/language_picker_item_bg_selected.xml b/core/res/res/drawable/language_picker_item_bg_selected.xml
new file mode 100644
index 0000000..ef9a8e7
--- /dev/null
+++ b/core/res/res/drawable/language_picker_item_bg_selected.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+
+  <item
+      android:bottom="-5dp"
+      android:right="-5dp"
+      android:top="-5dp">
+    <shape android:shape="rectangle" >
+      <solid android:color="@color/language_picker_item_selected_bg" />
+
+      <stroke
+          android:width="2dp"
+          android:color="@color/language_picker_item_selected_stroke" />
+    </shape>
+  </item>
+
+</layer-list>
\ No newline at end of file
diff --git a/core/res/res/drawable/language_picker_item_text_color2_selector.xml b/core/res/res/drawable/language_picker_item_text_color2_selector.xml
new file mode 100644
index 0000000..624ae29
--- /dev/null
+++ b/core/res/res/drawable/language_picker_item_text_color2_selector.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:state_selected="true"
+      android:color="@color/language_picker_item_text_color_secondary_selected" />
+  <item android:color="@color/language_picker_item_text_color_secondary" />
+</selector>
\ No newline at end of file
diff --git a/core/res/res/drawable/language_picker_item_text_color_selector.xml b/core/res/res/drawable/language_picker_item_text_color_selector.xml
new file mode 100644
index 0000000..8d419f8
--- /dev/null
+++ b/core/res/res/drawable/language_picker_item_text_color_selector.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+   <item android:state_selected="true"
+         android:color="@color/language_picker_item_text_color_selected" />
+   <item android:color="@color/language_picker_item_text_color" />
+</selector>
\ No newline at end of file
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/layout/language_picker_bilingual_item.xml b/core/res/res/layout/language_picker_bilingual_item.xml
index f56dda9..def0cccf 100644
--- a/core/res/res/layout/language_picker_bilingual_item.xml
+++ b/core/res/res/layout/language_picker_bilingual_item.xml
@@ -1,20 +1,27 @@
 <?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
+<RelativeLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/language_item"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:paddingTop="12dp"
+    android:paddingBottom="12dp"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    >
+
+<LinearLayout
     android:id="@+id/frame"
     android:layout_height="wrap_content"
     android:layout_width="match_parent"
-    android:paddingTop="8dp"
-    android:paddingBottom="8dp"
-    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:orientation="vertical">
+    android:orientation="vertical"
+    >
 
     <TextView
         android:id="@+id/locale_native"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textColor="@color/language_picker_item_text_color"
+        android:textColor="@drawable/language_picker_item_text_color_selector"
         android:textSize="18sp"
         android:textAppearance="?android:attr/textAppearanceListItem"
         />
@@ -23,9 +30,20 @@
         android:id="@+id/locale_secondary"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textColor="@color/language_picker_item_text_color_secondary"
+        android:textColor="@drawable/language_picker_item_text_color2_selector"
         android:textSize="16sp"
         android:textAppearance="?android:attr/textAppearanceListItem"
         />
+</LinearLayout>
 
-</LinearLayout>
\ No newline at end of file
+<ImageView
+    android:id="@+id/indicator"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_alignParentEnd="true"
+    android:adjustViewBounds="true"
+    android:layout_marginEnd="10dp"
+    android:layout_centerVertical="true"
+    android:src="@drawable/bilingual_language_item_selection_indicator"
+    android:visibility="gone" />
+</RelativeLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/side_fps_toast.xml b/core/res/res/layout/side_fps_toast.xml
index 58b8cc9..96860b0 100644
--- a/core/res/res/layout/side_fps_toast.xml
+++ b/core/res/res/layout/side_fps_toast.xml
@@ -20,13 +20,14 @@
               android:layout_height="wrap_content"
               android:minWidth="350dp"
               android:layout_gravity="center"
-              android:theme="?attr/alertDialogTheme">
+              android:background="@color/side_fps_toast_background">
     <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="@string/fp_power_button_enrollment_title"
         android:singleLine="true"
         android:ellipsize="end"
+        android:textColor="@color/side_fps_text_color"
         android:paddingLeft="20dp"/>
     <Space
         android:layout_width="wrap_content"
@@ -39,5 +40,6 @@
         android:text="@string/fp_power_button_enrollment_button_text"
         android:paddingRight="20dp"
         style="?android:attr/buttonBarNegativeButtonStyle"
+        android:textColor="@color/side_fps_button_color"
         android:maxLines="1"/>
 </LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml
index e2db49c..d3f998f 100644
--- a/core/res/res/values-night/colors.xml
+++ b/core/res/res/values-night/colors.xml
@@ -41,4 +41,14 @@
     <!-- Lily Language Picker language item view colors -->
     <color name="language_picker_item_text_color">#F1F3F4</color>
     <color name="language_picker_item_text_color_secondary">#BDC1C6</color>
+    <color name="language_picker_item_text_color_selected">#202124</color>
+    <color name="language_picker_item_text_color_secondary_selected">#202124</color>
+    <color name="language_picker_item_selected_indicator">#202124</color>
+    <color name="language_picker_item_selected_bg">#92B3F2</color>
+    <color name="language_picker_item_selected_stroke">#185ABC</color>
+
+    <!-- Color for side fps toast dark theme-->
+    <color name="side_fps_toast_background">#2E3132</color>
+    <color name="side_fps_text_color">#EFF1F2</color>
+    <color name="side_fps_button_color">#33B9DB</color>
 </resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d43a6c5..c7153fc 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5542,22 +5542,22 @@
              ignores some hyphen character related typographic features, e.g. kerning. -->
             <enum name="fullFast" value="4" />
         </attr>
-        <!-- Indicates the line break strategies can be used when calculating the text wrapping. -->
+        <!-- Specifies the line-break strategies for text wrapping. -->
         <attr name="lineBreakStyle">
-            <!-- No line break style specific. -->
+            <!-- No line-break rules are used for line breaking. -->
             <enum name="none" value="0" />
-            <!-- Use the least restrictive rule for line-breaking. -->
+            <!-- The least restrictive line-break rules are used for line breaking. -->
             <enum name="loose" value="1" />
-            <!-- Indicates breaking text with the most comment set of line-breaking rules. -->
+            <!-- The most common line-break rules are used for line breaking. -->
             <enum name="normal" value="2" />
-            <!-- Indicates breaking text with the most strictest line-breaking rules. -->
+            <!-- The most strict line-break rules are used for line breaking. -->
             <enum name="strict" value="3" />
         </attr>
-        <!-- Specify the phrase-based line break can be used when calculating the text wrapping.-->
+        <!-- Specifies the line-break word strategies for text wrapping.-->
         <attr name="lineBreakWordStyle">
-            <!-- No line break word style specific. -->
+            <!-- No line-break word style is used for line breaking. -->
             <enum name="none" value="0" />
-            <!-- Specify the phrase based breaking. -->
+            <!-- Line breaking is based on phrases, which results in text wrapping only on meaningful words. -->
             <enum name="phrase" value="1" />
         </attr>
         <!-- Specify the type of auto-size. Note that this feature is not supported by EditText,
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 062523e..b5c7ea6 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2175,6 +2175,11 @@
         tag; often this is one of the {@link android.Manifest.permission standard
         system permissions}. -->
         <attr name="name" />
+        <!-- Optional: specify the minimum version of the Android OS for which the
+             application wishes to request the permission.  When running on a version
+             of Android lower than the number given here, the permission will not
+             be requested. -->
+        <attr name="minSdkVersion" format="integer|string" />
         <!-- Optional: specify the maximum version of the Android OS for which the
              application wishes to request the permission.  When running on a version
              of Android higher than the number given here, the permission will not
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index faf48f3..77d7c43 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -211,9 +211,6 @@
     <color name="Red_700">#ffc53929</color>
     <color name="Red_800">#ffb93221</color>
 
-    <!-- Status bar color for semi transparent mode. -->
-    <color name="system_bar_background_semi_transparent">#66000000</color> <!-- 40% black -->
-
     <color name="resize_shadow_start_color">#2a000000</color>
     <color name="resize_shadow_end_color">#00000000</color>
 
@@ -454,4 +451,14 @@
     <!-- Lily Language Picker language item view colors -->
     <color name="language_picker_item_text_color">#202124</color>
     <color name="language_picker_item_text_color_secondary">#5F6368</color>
+    <color name="language_picker_item_text_color_selected">#202124</color>
+    <color name="language_picker_item_text_color_secondary_selected">#5F6368</color>
+    <color name="language_picker_item_selected_indicator">#4285F4</color>
+    <color name="language_picker_item_selected_bg">#E8F0FE</color>
+    <color name="language_picker_item_selected_stroke">#4C8DF6</color>
+
+    <!-- Color for side fps toast light theme -->
+    <color name="side_fps_toast_background">#F7F9FA</color>
+    <color name="side_fps_text_color">#191C1D</color>
+    <color name="side_fps_button_color">#00677E</color>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 215376a..116b150 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2000,10 +2000,25 @@
          are controlled together (aliasing is true) or not. -->
     <bool name="config_alias_ring_notif_stream_types">true</bool>
 
+    <!-- The number of volume steps for the notification stream -->
+    <integer name="config_audio_notif_vol_steps">7</integer>
+
+    <!-- The default volume for the notification stream -->
+    <integer name="config_audio_notif_vol_default">5</integer>
+
+    <!-- The number of volume steps for the ring stream -->
+    <integer name="config_audio_ring_vol_steps">7</integer>
+
+    <!-- The default volume for the ring stream -->
+    <integer name="config_audio_ring_vol_default">5</integer>
+
     <!-- Flag indicating whether platform level volume adjustments are enabled for remote sessions
          on grouped devices. -->
     <bool name="config_volumeAdjustmentForRemoteGroupSessions">true</bool>
 
+    <!-- Flag indicating current media Output Switcher version. -->
+    <integer name="config_mediaOutputSwitchDialogVersion">1</integer>
+
     <!-- Flag indicating that an outbound call must have a call capable phone account
          that has declared it can process the call's handle. -->
     <bool name="config_requireCallCapableAccountForHandle">false</bool>
@@ -2377,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>
@@ -5857,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 d3d0493..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] -->
@@ -6321,6 +6330,8 @@
     <string name="vdm_camera_access_denied" product="default">Can’t access the phone’s camera from your <xliff:g id="device" example="Chromebook">%1$s</xliff:g></string>
     <!-- Error message indicating the camera cannot be accessed when running on a virtual device. [CHAR LIMIT=NONE] -->
     <string name="vdm_camera_access_denied" product="tablet">Can’t access the tablet’s camera from your <xliff:g id="device" example="Chromebook">%1$s</xliff:g></string>
+    <!-- Error message indicating the user cannot access secure content when running on a virtual device. [CHAR LIMIT=NONE] -->
+    <string name="vdm_secure_window">This can’t be accessed while streaming. Try on your phone instead.</string>
 
     <!-- Title for preference of the system default locale. [CHAR LIMIT=50]-->
     <string name="system_locale_title">System default</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c45db7e..d7836c1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -97,10 +97,12 @@
   <java-symbol type="id" name="icon" />
   <java-symbol type="id" name="image" />
   <java-symbol type="id" name="increment" />
+  <java-symbol type="id" name="indicator" />
   <java-symbol type="id" name="internalEmpty" />
   <java-symbol type="id" name="inputExtractAccessories" />
   <java-symbol type="id" name="inputExtractAction" />
   <java-symbol type="id" name="issued_on" />
+  <java-symbol type="id" name="language_item" />
   <java-symbol type="id" name="left_icon" />
   <java-symbol type="id" name="leftSpacer" />
   <java-symbol type="id" name="line1" />
@@ -276,6 +278,10 @@
   <java-symbol type="bool" name="action_bar_embed_tabs" />
   <java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" />
   <java-symbol type="bool" name="config_alias_ring_notif_stream_types" />
+  <java-symbol type="integer" name="config_audio_notif_vol_default" />
+  <java-symbol type="integer" name="config_audio_notif_vol_steps" />
+  <java-symbol type="integer" name="config_audio_ring_vol_default" />
+  <java-symbol type="integer" name="config_audio_ring_vol_steps" />
   <java-symbol type="bool" name="config_avoidGfxAccel" />
   <java-symbol type="bool" name="config_bluetooth_address_validation" />
   <java-symbol type="integer" name="config_chooser_max_targets_per_row" />
@@ -919,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" />
@@ -2222,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" />
@@ -3122,6 +3128,12 @@
   <java-symbol type="id" name="locale_search_menu" />
   <java-symbol type="layout" name="language_picker_item" />
   <java-symbol type="layout" name="language_picker_bilingual_item" />
+  <java-symbol type="color" name="language_picker_item_text_color" />
+  <java-symbol type="color" name="language_picker_item_text_color_selected" />
+  <java-symbol type="color" name="language_picker_item_text_color_secondary_selected" />
+  <java-symbol type="drawable" name="language_picker_item_text_color_selector" />
+  <java-symbol type="drawable" name="language_picker_item_text_color2_selector" />
+  <java-symbol type="drawable" name="language_picker_item_bg_selected" />
   <java-symbol type="layout" name="language_picker_section_header" />
   <java-symbol type="layout" name="language_picker_bilingual_section_header" />
   <java-symbol type="menu" name="language_selection_list" />
@@ -3129,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" />
@@ -4709,6 +4722,8 @@
 
   <java-symbol type="bool" name="config_volumeAdjustmentForRemoteGroupSessions" />
 
+  <java-symbol type="integer" name="config_mediaOutputSwitchDialogVersion" />
+
   <!-- List of shared library packages that should be loaded by the classloader after the
        code and resources provided by applications. -->
   <java-symbol type="array" name="config_sharedLibrariesLoadedAfterApp" />
@@ -4789,6 +4804,7 @@
 
   <!-- For VirtualDeviceManager -->
   <java-symbol type="string" name="vdm_camera_access_denied" />
+  <java-symbol type="string" name="vdm_secure_window" />
 
   <java-symbol type="color" name="camera_privacy_light_day"/>
   <java-symbol type="color" name="camera_privacy_light_night"/>
@@ -4815,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/hardware/input/OWNERS b/core/tests/coretests/src/android/hardware/input/OWNERS
new file mode 100644
index 0000000..3f8a602
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/input/OWNERS
@@ -0,0 +1,2 @@
+include /core/java/android/hardware/input/OWNERS
+
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index ed6a649..cc68fce 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -236,6 +236,21 @@
     }
 
     @Test
+    public void testSystemDrivenInsetsAnimationLoggingListener_onReady() {
+        prepareControls();
+        // only the original thread that created view hierarchy can touch its views
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            WindowInsetsAnimationControlListener loggingListener =
+                    mock(WindowInsetsAnimationControlListener.class);
+            mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
+            mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
+            // since there is no focused view, forcefully make IME visible.
+            mController.show(Type.ime(), true /* fromIme */);
+            verify(loggingListener).onReady(notNull(), anyInt());
+        });
+    }
+
+    @Test
     public void testAnimationEndState() {
         InsetsSourceControl[] controls = prepareControls();
         InsetsSourceControl navBar = controls[0];
@@ -914,6 +929,23 @@
         });
     }
 
+    @Test
+    public void testImeRequestedVisibleWhenImeNotControllable() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            // Simulate IME insets is not controllable
+            mController.onControlsChanged(new InsetsSourceControl[0]);
+            final InsetsSourceConsumer imeInsetsConsumer = mController.getSourceConsumer(ITYPE_IME);
+            assertNull(imeInsetsConsumer.getControl());
+
+            // Verify IME requested visibility should be updated to IME consumer from controller.
+            mController.show(ime());
+            assertTrue(imeInsetsConsumer.isRequestedVisible());
+
+            mController.hide(ime());
+            assertFalse(imeInsetsConsumer.isRequestedVisible());
+        });
+    }
+
     private void waitUntilNextFrame() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         Choreographer.getMainThreadInstance().postCallback(Choreographer.CALLBACK_COMMIT,
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 2054b4f..8cf118c 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -18,8 +18,10 @@
 
 import static android.view.InsetsController.ANIMATION_TYPE_NONE;
 import static android.view.InsetsController.ANIMATION_TYPE_USER;
+import static android.view.InsetsSourceConsumer.ShowResult.SHOW_IMMEDIATELY;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.statusBars;
 
 import static junit.framework.Assert.assertEquals;
@@ -28,6 +30,7 @@
 
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
@@ -75,6 +78,7 @@
     private boolean mRemoveSurfaceCalled = false;
     private InsetsController mController;
     private InsetsState mState;
+    private ViewRootImpl mViewRoot;
 
     @Before
     public void setup() {
@@ -86,10 +90,9 @@
         instrumentation.runOnMainSync(() -> {
             final Context context = instrumentation.getTargetContext();
             // cannot mock ViewRootImpl since it's final.
-            final ViewRootImpl viewRootImpl = new ViewRootImpl(context,
-                    context.getDisplayNoVerify());
+            mViewRoot = new ViewRootImpl(context, context.getDisplayNoVerify());
             try {
-                viewRootImpl.setView(new TextView(context), new LayoutParams(), null);
+                mViewRoot.setView(new TextView(context), new LayoutParams(), null);
             } catch (BadTokenException e) {
                 // activity isn't running, lets ignore BadTokenException.
             }
@@ -97,7 +100,7 @@
             mSpyInsetsSource = Mockito.spy(new InsetsSource(ITYPE_STATUS_BAR));
             mState.addSource(mSpyInsetsSource);
 
-            mController = new InsetsController(new ViewRootInsetsControllerHost(viewRootImpl));
+            mController = new InsetsController(new ViewRootInsetsControllerHost(mViewRoot));
             mConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, mState,
                     () -> mMockTransaction, mController) {
                 @Override
@@ -207,4 +210,40 @@
         });
 
     }
+
+    @Test
+    public void testWontUpdateImeLeashVisibility_whenAnimation() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            InsetsState state = new InsetsState();
+            ViewRootInsetsControllerHost host = new ViewRootInsetsControllerHost(mViewRoot);
+            InsetsController insetsController = new InsetsController(host, (controller, type) -> {
+                if (type == ITYPE_IME) {
+                    return new InsetsSourceConsumer(ITYPE_IME, state,
+                            () -> mMockTransaction, controller) {
+                        @Override
+                        public int requestShow(boolean fromController) {
+                            return SHOW_IMMEDIATELY;
+                        }
+                    };
+                }
+                return new InsetsSourceConsumer(type, controller.getState(), Transaction::new,
+                        controller);
+            }, host.getHandler());
+            InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(ITYPE_IME);
+
+            // Initial IME insets source control with its leash.
+            imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, mLeash,
+                    false /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
+            reset(mMockTransaction);
+
+            // Verify when the app requests controlling show IME animation, the IME leash
+            // visibility won't be updated when the consumer received the same leash in setControl.
+            insetsController.controlWindowInsetsAnimation(ime(), 0L,
+                    null /* interpolator */, null /* cancellationSignal */, null /* listener */);
+            assertTrue(insetsController.getAnimationType(ITYPE_IME) == ANIMATION_TYPE_USER);
+            imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, mLeash,
+                    true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
+            verify(mMockTransaction, never()).show(mLeash);
+        });
+    }
 }
diff --git a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
index 03c8b1b..690b3587 100644
--- a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
@@ -213,6 +213,25 @@
     }
 
     @Test
+    public void testSystemDrivenInsetsAnimationLoggingListener() {
+        WindowInsetsAnimationControlListener listener =
+                mock(WindowInsetsAnimationControlListener.class);
+        mPendingInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener);
+        mPendingInsetsController.replayAndAttach(mReplayedController);
+        verify(mReplayedController).setSystemDrivenInsetsAnimationLoggingListener(eq(listener));
+    }
+
+    @Test
+    public void testSystemDrivenInsetsAnimationLoggingListener_direct() {
+        mPendingInsetsController.replayAndAttach(mReplayedController);
+        WindowInsetsAnimationControlListener listener =
+                mock(WindowInsetsAnimationControlListener.class);
+        mPendingInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener);
+        verify(mReplayedController).setSystemDrivenInsetsAnimationLoggingListener(
+                eq(listener));
+    }
+
+    @Test
     public void testDetachReattach() {
         mPendingInsetsController.show(systemBars());
         mPendingInsetsController.setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
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/coretests/src/com/android/internal/widget/LocalImageResolverTest.java b/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
index c63d18b..0cee526 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
@@ -270,4 +270,13 @@
         assertThat(bd.getBitmap().getHeight()).isEqualTo(originalHeight);
 
     }
+
+    @Test
+    public void resolveImage_iconWithOtherPackageResource_usesPackageContextDefinition()
+            throws IOException {
+        Icon icon = Icon.createWithResource("this_is_invalid", R.drawable.test32x24);
+        Drawable d = LocalImageResolver.resolveImage(icon, mContext);
+        // This drawable must not be loaded - if it was, the code ignored the package specification.
+        assertThat(d).isNull();
+    }
 }
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/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 857af11..318cd32 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -277,7 +277,7 @@
      * @see #setHeight(int)
      * @see #setConfig(Config)
      */
-    public void reconfigure(int width, int height, Config config) {
+    public void reconfigure(int width, int height, @NonNull Config config) {
         checkRecycled("Can't call reconfigure() on a recycled bitmap");
         if (width <= 0 || height <= 0) {
             throw new IllegalArgumentException("width and height must be > 0");
@@ -336,7 +336,7 @@
      * @see #setWidth(int)
      * @see #setHeight(int)
      */
-    public void setConfig(Config config) {
+    public void setConfig(@NonNull Config config) {
         reconfigure(getWidth(), getHeight(), config);
     }
 
@@ -590,7 +590,7 @@
      * in the buffer.</p>
      * @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
      */
-    public void copyPixelsToBuffer(Buffer dst) {
+    public void copyPixelsToBuffer(@NonNull Buffer dst) {
         checkHardware("unable to copyPixelsToBuffer, "
                 + "pixel access is not supported on Config#HARDWARE bitmaps");
         int elements = dst.remaining();
@@ -632,7 +632,7 @@
      * first rewind the buffer.</p>
      * @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
      */
-    public void copyPixelsFromBuffer(Buffer src) {
+    public void copyPixelsFromBuffer(@NonNull Buffer src) {
         checkRecycled("copyPixelsFromBuffer called on recycled bitmap");
         checkHardware("unable to copyPixelsFromBuffer, Config#HARDWARE bitmaps are immutable");
 
@@ -686,7 +686,7 @@
      * @return the new bitmap, or null if the copy could not be made.
      * @throws IllegalArgumentException if config is {@link Config#HARDWARE} and isMutable is true
      */
-    public Bitmap copy(Config config, boolean isMutable) {
+    public Bitmap copy(@NonNull Config config, boolean isMutable) {
         checkRecycled("Can't copy a recycled bitmap");
         if (config == Config.HARDWARE && isMutable) {
             throw new IllegalArgumentException("Hardware bitmaps are always immutable");
@@ -791,6 +791,7 @@
      * @return The new scaled bitmap or the source bitmap if no scaling is required.
      * @throws IllegalArgumentException if width is <= 0, or height is <= 0
      */
+    @NonNull
     public static Bitmap createScaledBitmap(@NonNull Bitmap src, int dstWidth, int dstHeight,
             boolean filter) {
         Matrix m = new Matrix();
@@ -810,6 +811,7 @@
      * be the same object as source, or a copy may have been made.  It is
      * initialized with the same density and color space as the original bitmap.
      */
+    @NonNull
     public static Bitmap createBitmap(@NonNull Bitmap src) {
         return createBitmap(src, 0, 0, src.getWidth(), src.getHeight());
     }
@@ -830,6 +832,7 @@
      *         outside of the dimensions of the source bitmap, or width is <= 0,
      *         or height is <= 0
      */
+    @NonNull
     public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height) {
         return createBitmap(source, x, y, width, height, null, false);
     }
@@ -865,6 +868,7 @@
      *         outside of the dimensions of the source bitmap, or width is <= 0,
      *         or height is <= 0, or if the source bitmap has already been recycled
      */
+    @NonNull
     public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height,
             @Nullable Matrix m, boolean filter) {
 
@@ -985,6 +989,7 @@
      * @throws IllegalArgumentException if the width or height are <= 0, or if
      *         Config is Config.HARDWARE, because hardware bitmaps are always immutable
      */
+    @NonNull
     public static Bitmap createBitmap(int width, int height, @NonNull Config config) {
         return createBitmap(width, height, config, true);
     }
@@ -1003,6 +1008,7 @@
      * @throws IllegalArgumentException if the width or height are <= 0, or if
      *         Config is Config.HARDWARE, because hardware bitmaps are always immutable
      */
+    @NonNull
     public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width,
             int height, @NonNull Config config) {
         return createBitmap(display, width, height, config, true);
@@ -1023,6 +1029,7 @@
      * @throws IllegalArgumentException if the width or height are <= 0, or if
      *         Config is Config.HARDWARE, because hardware bitmaps are always immutable
      */
+    @NonNull
     public static Bitmap createBitmap(int width, int height,
             @NonNull Config config, boolean hasAlpha) {
         return createBitmap(null, width, height, config, hasAlpha);
@@ -1050,6 +1057,7 @@
      *         {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}, or if
      *         the color space is null
      */
+    @NonNull
     public static Bitmap createBitmap(int width, int height, @NonNull Config config,
             boolean hasAlpha, @NonNull ColorSpace colorSpace) {
         return createBitmap(null, width, height, config, hasAlpha, colorSpace);
@@ -1073,6 +1081,7 @@
      * @throws IllegalArgumentException if the width or height are <= 0, or if
      *         Config is Config.HARDWARE, because hardware bitmaps are always immutable
      */
+    @NonNull
     public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width, int height,
             @NonNull Config config, boolean hasAlpha) {
         return createBitmap(display, width, height, config, hasAlpha,
@@ -1105,6 +1114,7 @@
      *         {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}, or if
      *         the color space is null
      */
+    @NonNull
     public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width, int height,
             @NonNull Config config, boolean hasAlpha, @NonNull ColorSpace colorSpace) {
         if (width <= 0 || height <= 0) {
@@ -1152,6 +1162,7 @@
      * @throws IllegalArgumentException if the width or height are <= 0, or if
      *         the color array's length is less than the number of pixels.
      */
+    @NonNull
     public static Bitmap createBitmap(@NonNull @ColorInt int[] colors, int offset, int stride,
             int width, int height, @NonNull Config config) {
         return createBitmap(null, colors, offset, stride, width, height, config);
@@ -1179,6 +1190,7 @@
      * @throws IllegalArgumentException if the width or height are <= 0, or if
      *         the color array's length is less than the number of pixels.
      */
+    @NonNull
     public static Bitmap createBitmap(@NonNull DisplayMetrics display,
             @NonNull @ColorInt int[] colors, int offset, int stride,
             int width, int height, @NonNull Config config) {
@@ -1221,6 +1233,7 @@
      * @throws IllegalArgumentException if the width or height are <= 0, or if
      *         the color array's length is less than the number of pixels.
      */
+    @NonNull
     public static Bitmap createBitmap(@NonNull @ColorInt int[] colors,
             int width, int height, Config config) {
         return createBitmap(null, colors, 0, width, width, height, config);
@@ -1245,6 +1258,7 @@
      * @throws IllegalArgumentException if the width or height are <= 0, or if
      *         the color array's length is less than the number of pixels.
      */
+    @NonNull
     public static Bitmap createBitmap(@Nullable DisplayMetrics display,
             @NonNull @ColorInt int colors[], int width, int height, @NonNull Config config) {
         return createBitmap(display, colors, 0, width, width, height, config);
@@ -1262,7 +1276,8 @@
      * @return An immutable bitmap with a HARDWARE config whose contents are created
      * from the recorded drawing commands in the Picture source.
      */
-    public static @NonNull Bitmap createBitmap(@NonNull Picture source) {
+    @NonNull
+    public static Bitmap createBitmap(@NonNull Picture source) {
         return createBitmap(source, source.getWidth(), source.getHeight(), Config.HARDWARE);
     }
 
@@ -1283,7 +1298,8 @@
      *
      * @return An immutable bitmap with a configuration specified by the config parameter
      */
-    public static @NonNull Bitmap createBitmap(@NonNull Picture source, int width, int height,
+    @NonNull
+    public static Bitmap createBitmap(@NonNull Picture source, int width, int height,
             @NonNull Config config) {
         if (width <= 0 || height <= 0) {
             throw new IllegalArgumentException("width & height must be > 0");
@@ -1330,6 +1346,7 @@
      * Returns an optional array of private data, used by the UI system for
      * some bitmaps. Not intended to be called by applications.
      */
+    @Nullable
     public byte[] getNinePatchChunk() {
         return mNinePatchChunk;
     }
@@ -1431,7 +1448,8 @@
      * @return true if successfully compressed to the specified stream.
      */
     @WorkerThread
-    public boolean compress(CompressFormat format, int quality, OutputStream stream) {
+    public boolean compress(@NonNull CompressFormat format, int quality,
+                            @NonNull OutputStream stream) {
         checkRecycled("Can't compress a recycled bitmap");
         // do explicit check before calling the native method
         if (stream == null) {
@@ -1548,7 +1566,7 @@
      * Convenience for calling {@link #getScaledWidth(int)} with the target
      * density of the given {@link Canvas}.
      */
-    public int getScaledWidth(Canvas canvas) {
+    public int getScaledWidth(@NonNull Canvas canvas) {
         return scaleFromDensity(getWidth(), mDensity, canvas.mDensity);
     }
 
@@ -1556,7 +1574,7 @@
      * Convenience for calling {@link #getScaledHeight(int)} with the target
      * density of the given {@link Canvas}.
      */
-    public int getScaledHeight(Canvas canvas) {
+    public int getScaledHeight(@NonNull Canvas canvas) {
         return scaleFromDensity(getHeight(), mDensity, canvas.mDensity);
     }
 
@@ -1564,7 +1582,7 @@
      * Convenience for calling {@link #getScaledWidth(int)} with the target
      * density of the given {@link DisplayMetrics}.
      */
-    public int getScaledWidth(DisplayMetrics metrics) {
+    public int getScaledWidth(@NonNull DisplayMetrics metrics) {
         return scaleFromDensity(getWidth(), mDensity, metrics.densityDpi);
     }
 
@@ -1572,7 +1590,7 @@
      * Convenience for calling {@link #getScaledHeight(int)} with the target
      * density of the given {@link DisplayMetrics}.
      */
-    public int getScaledHeight(DisplayMetrics metrics) {
+    public int getScaledHeight(@NonNull DisplayMetrics metrics) {
         return scaleFromDensity(getHeight(), mDensity, metrics.densityDpi);
     }
 
@@ -1682,6 +1700,7 @@
      * If the bitmap's internal config is in one of the public formats, return
      * that config, otherwise return null.
      */
+    @NonNull
     public final Config getConfig() {
         if (mRecycled) {
             Log.w(TAG, "Called getConfig() on a recycle()'d bitmap! This is undefined behavior!");
@@ -1967,7 +1986,7 @@
      *         to receive the specified number of pixels.
      * @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
      */
-    public void getPixels(@ColorInt int[] pixels, int offset, int stride,
+    public void getPixels(@NonNull @ColorInt int[] pixels, int offset, int stride,
                           int x, int y, int width, int height) {
         checkRecycled("Can't call getPixels() on a recycled bitmap");
         checkHardware("unable to getPixels(), "
@@ -2084,7 +2103,7 @@
      * @throws ArrayIndexOutOfBoundsException if the pixels array is too small
      *         to receive the specified number of pixels.
      */
-    public void setPixels(@ColorInt int[] pixels, int offset, int stride,
+    public void setPixels(@NonNull @ColorInt int[] pixels, int offset, int stride,
             int x, int y, int width, int height) {
         checkRecycled("Can't call setPixels() on a recycled bitmap");
         if (!isMutable()) {
@@ -2098,7 +2117,7 @@
                         x, y, width, height);
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<Bitmap> CREATOR
+    public static final @NonNull Parcelable.Creator<Bitmap> CREATOR
             = new Parcelable.Creator<Bitmap>() {
         /**
          * Rebuilds a bitmap previously stored with writeToParcel().
@@ -2134,7 +2153,7 @@
      * by the final pixel format
      * @param p    Parcel object to write the bitmap data into
      */
-    public void writeToParcel(Parcel p, int flags) {
+    public void writeToParcel(@NonNull Parcel p, int flags) {
         checkRecycled("Can't parcel a recycled bitmap");
         noteHardwareBitmapSlowCall();
         if (!nativeWriteToParcel(mNativePtr, mDensity, p)) {
@@ -2150,6 +2169,7 @@
      * @return new bitmap containing the alpha channel of the original bitmap.
      */
     @CheckResult
+    @NonNull
     public Bitmap extractAlpha() {
         return extractAlpha(null, null);
     }
@@ -2180,7 +2200,8 @@
      *         paint that is passed to the draw call.
      */
     @CheckResult
-    public Bitmap extractAlpha(Paint paint, int[] offsetXY) {
+    @NonNull
+    public Bitmap extractAlpha(@Nullable Paint paint, int[] offsetXY) {
         checkRecycled("Can't extractAlpha on a recycled bitmap");
         long nativePaint = paint != null ? paint.getNativeInstance() : 0;
         noteHardwareBitmapSlowCall();
@@ -2197,12 +2218,12 @@
      *  and pixel data as this bitmap. If any of those differ, return false.
      *  If other is null, return false.
      */
-    public boolean sameAs(Bitmap other) {
+    @WorkerThread
+    public boolean sameAs(@Nullable Bitmap other) {
+        StrictMode.noteSlowCall("sameAs compares pixel data, not expected to be fast");
         checkRecycled("Can't call sameAs on a recycled bitmap!");
-        noteHardwareBitmapSlowCall();
         if (this == other) return true;
         if (other == null) return false;
-        other.noteHardwareBitmapSlowCall();
         if (other.isRecycled()) {
             throw new IllegalArgumentException("Can't compare to a recycled bitmap!");
         }
@@ -2247,7 +2268,8 @@
      * @throws IllegalStateException if the bitmap's config is not {@link Config#HARDWARE}
      * or if the bitmap has been recycled.
      */
-    public @NonNull HardwareBuffer getHardwareBuffer() {
+    @NonNull
+    public HardwareBuffer getHardwareBuffer() {
         checkRecycled("Can't getHardwareBuffer from a recycled bitmap");
         HardwareBuffer hardwareBuffer = mHardwareBuffer == null ? null : mHardwareBuffer.get();
         if (hardwareBuffer == null || hardwareBuffer.isClosed()) {
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 7ad9aec..d0327159 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -24,29 +24,32 @@
 import java.util.Objects;
 
 /**
- * Indicates the strategies can be used when calculating the text wrapping.
+ * Specifies the line-break strategies for text wrapping.
  *
- * See <a href="https://www.w3.org/TR/css-text-3/#line-break-property">the line-break property</a>
+ * <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.
  */
 public final class LineBreakConfig {
 
     /**
-     * No line break style specified.
+     * No line-break rules are used for line breaking.
      */
     public static final int LINE_BREAK_STYLE_NONE = 0;
 
     /**
-     * Use the least restrictive rule for line-breaking. This is usually used for short lines.
+     * The least restrictive line-break rules are used for line breaking. This
+     * setting is typically used for short lines.
      */
     public static final int LINE_BREAK_STYLE_LOOSE = 1;
 
     /**
-     * Indicate breaking text with the most comment set of line-breaking rules.
+     * The most common line-break rules are used for line breaking.
      */
     public static final int LINE_BREAK_STYLE_NORMAL = 2;
 
     /**
-     * Indicates breaking text with the most strictest line-breaking rules.
+     * The most strict line-break rules are used for line breaking.
      */
     public static final int LINE_BREAK_STYLE_STRICT = 3;
 
@@ -59,15 +62,17 @@
     public @interface LineBreakStyle {}
 
     /**
-     * No line break word style specified.
+     * No line-break word style is used for line breaking.
      */
     public static final int LINE_BREAK_WORD_STYLE_NONE = 0;
 
     /**
-     * Indicates the line breaking is based on the phrased. This makes text wrapping only on
-     * meaningful words. The support of the text wrapping word style varies depending on the
-     * locales. If the locale does not support the phrase based text wrapping,
-     * there will be no effect.
+     * Line breaking is based on phrases, which results in text wrapping only on
+     * meaningful words.
+     *
+     * <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.
      */
     public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1;
 
@@ -79,7 +84,7 @@
     public @interface LineBreakWordStyle {}
 
     /**
-     * A builder for creating {@link LineBreakConfig}.
+     * A builder for creating a {@code LineBreakConfig} instance.
      */
     public static final class Builder {
         // The line break style for the LineBreakConfig.
@@ -95,16 +100,16 @@
         private boolean mAutoPhraseBreaking = false;
 
         /**
-         * Builder constructor with line break parameters.
+         * Builder constructor.
          */
         public Builder() {
         }
 
         /**
-         * Set the line break style.
+         * Sets the line-break style.
          *
-         * @param lineBreakStyle the new line break style.
-         * @return this Builder
+         * @param lineBreakStyle The new line-break style.
+         * @return This {@code Builder}.
          */
         public @NonNull Builder setLineBreakStyle(@LineBreakStyle int lineBreakStyle) {
             mLineBreakStyle = lineBreakStyle;
@@ -112,10 +117,10 @@
         }
 
         /**
-         * Set the line break word style.
+         * Sets the line-break word style.
          *
-         * @param lineBreakWordStyle the new line break word style.
-         * @return this Builder
+         * @param lineBreakWordStyle The new line-break word style.
+         * @return This {@code Builder}.
          */
         public @NonNull Builder setLineBreakWordStyle(@LineBreakWordStyle int lineBreakWordStyle) {
             mLineBreakWordStyle = lineBreakWordStyle;
@@ -123,7 +128,7 @@
         }
 
         /**
-         * Enable or disable the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE}.
+         * Enables or disables the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE}.
          *
          * @hide
          */
@@ -133,9 +138,9 @@
         }
 
         /**
-         * Build the {@link LineBreakConfig}
+         * Builds a {@link LineBreakConfig} instance.
          *
-         * @return the LineBreakConfig instance.
+         * @return The {@code LineBreakConfig} instance.
          */
         public @NonNull LineBreakConfig build() {
             return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, mAutoPhraseBreaking);
@@ -143,11 +148,12 @@
     }
 
     /**
-     * Create the LineBreakConfig instance.
+     * Creates a {@code LineBreakConfig} instance with the provided line break
+     * parameters.
      *
-     * @param lineBreakStyle the line break style for text wrapping.
-     * @param lineBreakWordStyle the line break word style for text wrapping.
-     * @return the {@link LineBreakConfig} instance.
+     * @param lineBreakStyle The line-break style for text wrapping.
+     * @param lineBreakWordStyle The line-break word style for text wrapping.
+     * @return The {@code LineBreakConfig} instance.
      * @hide
      */
     public static @NonNull LineBreakConfig getLineBreakConfig(@LineBreakStyle int lineBreakStyle,
@@ -185,8 +191,10 @@
     private final boolean mAutoPhraseBreaking;
 
     /**
-     * Constructor with the line break parameters.
-     * Use the {@link LineBreakConfig.Builder} to create the LineBreakConfig instance.
+     * Constructor with line-break parameters.
+     *
+     * <p>Use {@link LineBreakConfig.Builder} to create the
+     * {@code LineBreakConfig} instance.
      */
     private LineBreakConfig(@LineBreakStyle int lineBreakStyle,
             @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) {
@@ -196,18 +204,18 @@
     }
 
     /**
-     * Get the line break style.
+     * Gets the current line-break style.
      *
-     * @return The current line break style to be used for the text wrapping.
+     * @return The line-break style to be used for text wrapping.
      */
     public @LineBreakStyle int getLineBreakStyle() {
         return mLineBreakStyle;
     }
 
     /**
-     * Get the line break word style.
+     * Gets the current line-break word style.
      *
-     * @return The current line break word style to be used for the text wrapping.
+     * @return The line-break word style to be used for text wrapping.
      */
     public @LineBreakWordStyle int getLineBreakWordStyle() {
         return mLineBreakWordStyle;
diff --git a/ktfmt_includes.txt b/ktfmt_includes.txt
new file mode 100644
index 0000000..96da8c9
--- /dev/null
+++ b/ktfmt_includes.txt
@@ -0,0 +1,9 @@
+packages/SystemUI/compose/
+packages/SystemUI/screenshot/
+packages/SystemUI/src/com/android/systemui/people/data
+packages/SystemUI/src/com/android/systemui/people/ui
+packages/SystemUI/src/com/android/systemui/keyguard/data
+packages/SystemUI/src/com/android/systemui/keyguard/dagger
+packages/SystemUI/src/com/android/systemui/keyguard/domain
+packages/SystemUI/src/com/android/systemui/keyguard/shared
+packages/SystemUI/src/com/android/systemui/keyguard/ui
\ No newline at end of file
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/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index d42fca2..1335e5e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -51,28 +51,37 @@
     @VisibleForTesting
     final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>();
 
+    @NonNull
     private final TaskFragmentCallback mCallback;
+
     @VisibleForTesting
+    @Nullable
     TaskFragmentAnimationController mAnimationController;
 
     /**
      * Callback that notifies the controller about changes to task fragments.
      */
     interface TaskFragmentCallback {
-        void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo);
-        void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo);
-        void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo);
-        void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig);
-        void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
-                @NonNull IBinder activityToken);
-        void onTaskFragmentError(@Nullable TaskFragmentInfo taskFragmentInfo, int opType);
+        void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
+                @NonNull TaskFragmentInfo taskFragmentInfo);
+        void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
+                @NonNull TaskFragmentInfo taskFragmentInfo);
+        void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
+                @NonNull TaskFragmentInfo taskFragmentInfo);
+        void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
+                int taskId, @NonNull Configuration parentConfig);
+        void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
+                int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken);
+        void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
+                @Nullable TaskFragmentInfo taskFragmentInfo, int opType);
     }
 
     /**
      * @param executor  callbacks from WM Core are posted on this executor. It should be tied to the
      *                  UI thread that all other calls into methods of this class are also on.
      */
-    JetpackTaskFragmentOrganizer(@NonNull Executor executor, TaskFragmentCallback callback) {
+    JetpackTaskFragmentOrganizer(@NonNull Executor executor,
+            @NonNull TaskFragmentCallback callback) {
         super(executor);
         mCallback = callback;
     }
@@ -147,41 +156,31 @@
      * @param wct WindowContainerTransaction in which the task fragment should be resized.
      * @param fragmentToken token of an existing TaskFragment.
      */
-    void expandTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) {
+    void expandTaskFragment(@NonNull WindowContainerTransaction wct,
+            @NonNull IBinder fragmentToken) {
         resizeTaskFragment(wct, fragmentToken, new Rect());
         setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */);
         updateWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED);
     }
 
     /**
-     * Expands an existing TaskFragment to fill parent.
-     * @param fragmentToken token of an existing TaskFragment.
-     */
-    void expandTaskFragment(IBinder fragmentToken) {
-        WindowContainerTransaction wct = new WindowContainerTransaction();
-        expandTaskFragment(wct, fragmentToken);
-        applyTransaction(wct);
-    }
-
-    /**
      * Expands an Activity to fill parent by moving it to a new TaskFragment.
      * @param fragmentToken token to create new TaskFragment with.
      * @param activity      activity to move to the fill-parent TaskFragment.
      */
-    void expandActivity(IBinder fragmentToken, Activity activity) {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
+    void expandActivity(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
+            @NonNull Activity activity) {
         createTaskFragmentAndReparentActivity(
                 wct, fragmentToken, activity.getActivityToken(), new Rect(),
                 WINDOWING_MODE_UNDEFINED, activity);
-        applyTransaction(wct);
     }
 
     /**
      * @param ownerToken The token of the activity that creates this task fragment. It does not
      *                   have to be a child of this task fragment, but must belong to the same task.
      */
-    void createTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken,
-            IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
+    void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
+            @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
         final TaskFragmentCreationParams fragmentOptions =
                 createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode);
         wct.createTaskFragment(fragmentOptions);
@@ -191,9 +190,9 @@
      * @param ownerToken The token of the activity that creates this task fragment. It does not
      *                   have to be a child of this task fragment, but must belong to the same task.
      */
-    private void createTaskFragmentAndReparentActivity(
-            WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
-            @NonNull Rect bounds, @WindowingMode int windowingMode, Activity activity) {
+    private void createTaskFragmentAndReparentActivity(@NonNull WindowContainerTransaction wct,
+            @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
+            @WindowingMode int windowingMode, @NonNull Activity activity) {
         createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
         wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
     }
@@ -202,9 +201,9 @@
      * @param ownerToken The token of the activity that creates this task fragment. It does not
      *                   have to be a child of this task fragment, but must belong to the same task.
      */
-    private void createTaskFragmentAndStartActivity(
-            WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
-            @NonNull Rect bounds, @WindowingMode int windowingMode, Intent activityIntent,
+    private void createTaskFragmentAndStartActivity(@NonNull WindowContainerTransaction wct,
+            @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
+            @WindowingMode int windowingMode, @NonNull Intent activityIntent,
             @Nullable Bundle activityOptions) {
         createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
         wct.startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent, activityOptions);
@@ -225,8 +224,8 @@
         wct.setAdjacentTaskFragments(primary, secondary, adjacentParams);
     }
 
-    TaskFragmentCreationParams createFragmentOptions(IBinder fragmentToken, IBinder ownerToken,
-            Rect bounds, @WindowingMode int windowingMode) {
+    TaskFragmentCreationParams createFragmentOptions(@NonNull IBinder fragmentToken,
+            @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
         if (mFragmentInfos.containsKey(fragmentToken)) {
             throw new IllegalArgumentException(
                     "There is an existing TaskFragment with fragmentToken=" + fragmentToken);
@@ -241,7 +240,7 @@
                 .build();
     }
 
-    void resizeTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken,
+    void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
             @Nullable Rect bounds) {
         if (!mFragmentInfos.containsKey(fragmentToken)) {
             throw new IllegalArgumentException(
@@ -253,8 +252,8 @@
         wct.setBounds(mFragmentInfos.get(fragmentToken).getToken(), bounds);
     }
 
-    void updateWindowingMode(WindowContainerTransaction wct, IBinder fragmentToken,
-            @WindowingMode int windowingMode) {
+    void updateWindowingMode(@NonNull WindowContainerTransaction wct,
+            @NonNull IBinder fragmentToken, @WindowingMode int windowingMode) {
         if (!mFragmentInfos.containsKey(fragmentToken)) {
             throw new IllegalArgumentException(
                     "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
@@ -262,7 +261,8 @@
         wct.setWindowingMode(mFragmentInfos.get(fragmentToken).getToken(), windowingMode);
     }
 
-    void deleteTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) {
+    void deleteTaskFragment(@NonNull WindowContainerTransaction wct,
+            @NonNull IBinder fragmentToken) {
         if (!mFragmentInfos.containsKey(fragmentToken)) {
             throw new IllegalArgumentException(
                     "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
@@ -271,60 +271,49 @@
     }
 
     @Override
-    public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
+    public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
+            @NonNull TaskFragmentInfo taskFragmentInfo) {
         final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
         mFragmentInfos.put(fragmentToken, taskFragmentInfo);
-
-        if (mCallback != null) {
-            mCallback.onTaskFragmentAppeared(taskFragmentInfo);
-        }
+        mCallback.onTaskFragmentAppeared(wct, taskFragmentInfo);
     }
 
     @Override
-    public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
+    public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
+            @NonNull TaskFragmentInfo taskFragmentInfo) {
         final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
         mFragmentInfos.put(fragmentToken, taskFragmentInfo);
-
-        if (mCallback != null) {
-            mCallback.onTaskFragmentInfoChanged(taskFragmentInfo);
-        }
+        mCallback.onTaskFragmentInfoChanged(wct, taskFragmentInfo);
     }
 
     @Override
-    public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
+    public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
+            @NonNull TaskFragmentInfo taskFragmentInfo) {
         mFragmentInfos.remove(taskFragmentInfo.getFragmentToken());
-
-        if (mCallback != null) {
-            mCallback.onTaskFragmentVanished(taskFragmentInfo);
-        }
+        mCallback.onTaskFragmentVanished(wct, taskFragmentInfo);
     }
 
     @Override
-    public void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) {
-        if (mCallback != null) {
-            mCallback.onTaskFragmentParentInfoChanged(taskId, parentConfig);
-        }
+    public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
+            int taskId, @NonNull Configuration parentConfig) {
+        mCallback.onTaskFragmentParentInfoChanged(wct, taskId, parentConfig);
     }
 
     @Override
-    public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
-            @NonNull IBinder activityToken) {
-        if (mCallback != null) {
-            mCallback.onActivityReparentToTask(taskId, activityIntent, activityToken);
-        }
+    public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
+            int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) {
+        mCallback.onActivityReparentedToTask(wct, taskId, activityIntent, activityToken);
     }
 
     @Override
-    public void onTaskFragmentError(@NonNull IBinder errorCallbackToken,
+    public void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
+            @NonNull IBinder errorCallbackToken,
             @Nullable TaskFragmentInfo taskFragmentInfo,
             int opType, @NonNull Throwable exception) {
         if (taskFragmentInfo != null) {
             final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
             mFragmentInfos.put(fragmentToken, taskFragmentInfo);
         }
-
-        if (mCallback != null) {
-            mCallback.onTaskFragmentError(taskFragmentInfo, opType);
-        }
+        mCallback.onTaskFragmentError(wct, taskFragmentInfo, opType);
     }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index f09a910..c8ac0fc 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -16,17 +16,21 @@
 
 package androidx.window.extensions.embedding;
 
-import android.annotation.NonNull;
 import android.app.Activity;
 import android.util.Pair;
 import android.util.Size;
 
+import androidx.annotation.NonNull;
+
 /**
  * Client-side descriptor of a split that holds two containers.
  */
 class SplitContainer {
+    @NonNull
     private final TaskFragmentContainer mPrimaryContainer;
+    @NonNull
     private final TaskFragmentContainer mSecondaryContainer;
+    @NonNull
     private final SplitRule mSplitRule;
 
     SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index dad0739..0597809f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -145,35 +145,36 @@
     }
 
     @Override
-    public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
+    public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
+            @NonNull TaskFragmentInfo taskFragmentInfo) {
         synchronized (mLock) {
             TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
             if (container == null) {
                 return;
             }
 
-            container.setInfo(taskFragmentInfo);
+            container.setInfo(wct, taskFragmentInfo);
             if (container.isFinished()) {
-                mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
+                mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
             } else {
                 // Update with the latest Task configuration.
-                mPresenter.updateContainer(container);
+                updateContainer(wct, container);
             }
             updateCallbackIfNecessary();
         }
     }
 
     @Override
-    public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
+    public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
+            @NonNull TaskFragmentInfo taskFragmentInfo) {
         synchronized (mLock) {
             TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
             if (container == null) {
                 return;
             }
 
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
             final boolean wasInPip = isInPictureInPicture(container);
-            container.setInfo(taskFragmentInfo);
+            container.setInfo(wct, taskFragmentInfo);
             final boolean isInPip = isInPictureInPicture(container);
             // Check if there are no running activities - consider the container empty if there are
             // no non-finishing activities left.
@@ -183,15 +184,15 @@
                     // Instead, the original split should be cleanup, and the dependent may be
                     // expanded to fullscreen.
                     cleanupForEnterPip(wct, container);
-                    mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
+                    mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
                 } else if (taskFragmentInfo.isTaskClearedForReuse()) {
                     // Do not finish the dependents if this TaskFragment was cleared due to
                     // launching activity in the Task.
-                    mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
+                    mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
                 } else if (!container.isWaitingActivityAppear()) {
                     // Do not finish the container before the expected activity appear until
                     // timeout.
-                    mPresenter.cleanupContainer(container, true /* shouldFinishDependent */, wct);
+                    mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */);
                 }
             } else if (wasInPip && isInPip) {
                 // No update until exit PIP.
@@ -208,13 +209,13 @@
                 // needed.
                 updateContainer(wct, container);
             }
-            mPresenter.applyTransaction(wct);
             updateCallbackIfNecessary();
         }
     }
 
     @Override
-    public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
+    public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
+            @NonNull TaskFragmentInfo taskFragmentInfo) {
         synchronized (mLock) {
             final TaskFragmentContainer container = getContainer(
                     taskFragmentInfo.getFragmentToken());
@@ -225,9 +226,7 @@
                 final TaskFragmentContainer newTopContainer = getTopActiveContainer(
                         container.getTaskId());
                 if (newTopContainer != null) {
-                    final WindowContainerTransaction wct = new WindowContainerTransaction();
                     updateContainer(wct, newTopContainer);
-                    mPresenter.applyTransaction(wct);
                 }
                 updateCallbackIfNecessary();
             }
@@ -236,7 +235,8 @@
     }
 
     @Override
-    public void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) {
+    public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
+            int taskId, @NonNull Configuration parentConfig) {
         synchronized (mLock) {
             onTaskConfigurationChanged(taskId, parentConfig);
             if (isInPictureInPicture(parentConfig)) {
@@ -256,7 +256,7 @@
                 final TaskFragmentContainer container = containers.get(i);
                 // Wait until onTaskFragmentAppeared to update new container.
                 if (!container.isFinished() && !container.isWaitingActivityAppear()) {
-                    mPresenter.updateContainer(container);
+                    updateContainer(wct, container);
                 }
             }
             updateCallbackIfNecessary();
@@ -264,7 +264,8 @@
     }
 
     @Override
-    public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
+    public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
+            int taskId, @NonNull Intent activityIntent,
             @NonNull IBinder activityToken) {
         synchronized (mLock) {
             // If the activity belongs to the current app process, we treat it as a new activity
@@ -275,10 +276,10 @@
                 // launching to top. We allow split as primary for activity reparent because the
                 // activity may be split as primary before it is reparented out. In that case, we
                 // want to show it as primary again when it is reparented back.
-                if (!resolveActivityToContainer(activity, true /* isOnReparent */)) {
+                if (!resolveActivityToContainer(wct, activity, true /* isOnReparent */)) {
                     // When there is no embedding rule matched, try to place it in the top container
                     // like a normal launch.
-                    placeActivityInTopContainer(activity);
+                    placeActivityInTopContainer(wct, activity);
                 }
                 updateCallbackIfNecessary();
                 return;
@@ -293,7 +294,6 @@
             // If the activity belongs to a different app process, we treat it as starting new
             // intent, since both actions might result in a new activity that should appear in an
             // organized TaskFragment.
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
             TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId,
                     activityIntent, null /* launchingActivity */);
             if (targetContainer == null) {
@@ -306,14 +306,14 @@
             }
             wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
                     activityToken);
-            mPresenter.applyTransaction(wct);
             // Because the activity does not belong to the organizer process, we wait until
             // onTaskFragmentAppeared to trigger updateCallbackIfNecessary().
         }
     }
 
     @Override
-    public void onTaskFragmentError(@Nullable TaskFragmentInfo taskFragmentInfo, int opType) {
+    public void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
+            @Nullable TaskFragmentInfo taskFragmentInfo, int opType) {
         synchronized (mLock) {
             switch (opType) {
                 case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
@@ -329,10 +329,11 @@
                     }
 
                     // Update the latest taskFragmentInfo and perform necessary clean-up
-                    container.setInfo(taskFragmentInfo);
+                    container.setInfo(wct, taskFragmentInfo);
                     container.clearPendingAppearedActivities();
                     if (container.isEmpty()) {
-                        mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
+                        mPresenter.cleanupContainer(wct, container,
+                                false /* shouldFinishDependent */);
                     }
                     break;
                 }
@@ -343,7 +344,7 @@
         }
     }
 
-    /** Called on receiving {@link #onTaskFragmentVanished(TaskFragmentInfo)} for cleanup. */
+    /** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */
     private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
             final TaskContainer taskContainer = mTaskContainers.valueAt(i);
@@ -422,10 +423,12 @@
     }
 
     @VisibleForTesting
-    void onActivityCreated(@NonNull Activity launchedActivity) {
+    @GuardedBy("mLock")
+    void onActivityCreated(@NonNull WindowContainerTransaction wct,
+            @NonNull Activity launchedActivity) {
         // TODO(b/229680885): we don't support launching into primary yet because we want to always
         // launch the new activity on top.
-        resolveActivityToContainer(launchedActivity, false /* isOnReparent */);
+        resolveActivityToContainer(wct, launchedActivity, false /* isOnReparent */);
         updateCallbackIfNecessary();
     }
 
@@ -440,7 +443,8 @@
      */
     @VisibleForTesting
     @GuardedBy("mLock")
-    boolean resolveActivityToContainer(@NonNull Activity activity, boolean isOnReparent) {
+    boolean resolveActivityToContainer(@NonNull WindowContainerTransaction wct,
+            @NonNull Activity activity, boolean isOnReparent) {
         if (isInPictureInPicture(activity) || activity.isFinishing()) {
             // We don't embed activity when it is in PIP, or finishing. Return true since we don't
             // want any extra handling.
@@ -472,12 +476,12 @@
 
         // 1. Whether the new launched activity should always expand.
         if (shouldExpand(activity, null /* intent */)) {
-            expandActivity(activity);
+            expandActivity(wct, activity);
             return true;
         }
 
         // 2. Whether the new launched activity should launch a placeholder.
-        if (launchPlaceholderIfNecessary(activity, !isOnReparent)) {
+        if (launchPlaceholderIfNecessary(wct, activity, !isOnReparent)) {
             return true;
         }
 
@@ -492,11 +496,11 @@
             // Can't find any activity below.
             return false;
         }
-        if (putActivitiesIntoSplitIfNecessary(activityBelow, activity)) {
+        if (putActivitiesIntoSplitIfNecessary(wct, activityBelow, activity)) {
             // Have split rule of [ activityBelow | launchedActivity ].
             return true;
         }
-        if (isOnReparent && putActivitiesIntoSplitIfNecessary(activity, activityBelow)) {
+        if (isOnReparent && putActivitiesIntoSplitIfNecessary(wct, activity, activityBelow)) {
             // Have split rule of [ launchedActivity | activityBelow].
             return true;
         }
@@ -519,19 +523,20 @@
             // Can't find the top activity on the other split TaskFragment.
             return false;
         }
-        if (putActivitiesIntoSplitIfNecessary(otherTopActivity, activity)) {
+        if (putActivitiesIntoSplitIfNecessary(wct, otherTopActivity, activity)) {
             // Have split rule of [ otherTopActivity | launchedActivity ].
             return true;
         }
         // Have split rule of [ launchedActivity | otherTopActivity].
-        return isOnReparent && putActivitiesIntoSplitIfNecessary(activity, otherTopActivity);
+        return isOnReparent && putActivitiesIntoSplitIfNecessary(wct, activity, otherTopActivity);
     }
 
     /**
      * Places the given activity to the top most TaskFragment in the task if there is any.
      */
     @VisibleForTesting
-    void placeActivityInTopContainer(@NonNull Activity activity) {
+    void placeActivityInTopContainer(@NonNull WindowContainerTransaction wct,
+            @NonNull Activity activity) {
         if (getContainerWithActivity(activity) != null) {
             // The activity has already been put in a TaskFragment. This is likely to be done by
             // the server when the activity is started.
@@ -547,20 +552,20 @@
             return;
         }
         targetContainer.addPendingAppearedActivity(activity);
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
         wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
                 activity.getActivityToken());
-        mPresenter.applyTransaction(wct);
     }
 
     /**
      * Starts an activity to side of the launchingActivity with the provided split config.
      */
-    private void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent,
+    @GuardedBy("mLock")
+    private void startActivityToSide(@NonNull WindowContainerTransaction wct,
+            @NonNull Activity launchingActivity, @NonNull Intent intent,
             @Nullable Bundle options, @NonNull SplitRule sideRule,
             @Nullable Consumer<Exception> failureCallback, boolean isPlaceholder) {
         try {
-            mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule,
+            mPresenter.startActivityToSide(wct, launchingActivity, intent, options, sideRule,
                     isPlaceholder);
         } catch (Exception e) {
             if (failureCallback != null) {
@@ -573,15 +578,17 @@
      * Expands the given activity by either expanding the TaskFragment it is currently in or putting
      * it into a new expanded TaskFragment.
      */
-    private void expandActivity(@NonNull Activity activity) {
+    @GuardedBy("mLock")
+    private void expandActivity(@NonNull WindowContainerTransaction wct,
+            @NonNull Activity activity) {
         final TaskFragmentContainer container = getContainerWithActivity(activity);
         if (shouldContainerBeExpanded(container)) {
             // Make sure that the existing container is expanded.
-            mPresenter.expandTaskFragment(container.getTaskFragmentToken());
+            mPresenter.expandTaskFragment(wct, container.getTaskFragmentToken());
         } else {
             // Put activity into a new expanded container.
             final TaskFragmentContainer newContainer = newContainer(activity, getTaskId(activity));
-            mPresenter.expandActivity(newContainer.getTaskFragmentToken(), activity);
+            mPresenter.expandActivity(wct, newContainer.getTaskFragmentToken(), activity);
         }
     }
 
@@ -667,8 +674,8 @@
      * and returns {@code true}. Otherwise, returns {@code false}.
      */
     @GuardedBy("mLock")
-    private boolean putActivitiesIntoSplitIfNecessary(@NonNull Activity primaryActivity,
-            @NonNull Activity secondaryActivity) {
+    private boolean putActivitiesIntoSplitIfNecessary(@NonNull WindowContainerTransaction wct,
+            @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) {
         final SplitPairRule splitRule = getSplitRule(primaryActivity, secondaryActivity);
         if (splitRule == null) {
             return false;
@@ -686,23 +693,23 @@
                 return true;
             }
             secondaryContainer.addPendingAppearedActivity(secondaryActivity);
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
             if (mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
                     secondaryActivity, null /* secondaryIntent */)
                     != RESULT_EXPAND_FAILED_NO_TF_INFO) {
                 wct.reparentActivityToTaskFragment(
                         secondaryContainer.getTaskFragmentToken(),
                         secondaryActivity.getActivityToken());
-                mPresenter.applyTransaction(wct);
                 return true;
             }
         }
         // Create new split pair.
-        mPresenter.createNewSplitContainer(primaryActivity, secondaryActivity, splitRule);
+        mPresenter.createNewSplitContainer(wct, primaryActivity, secondaryActivity, splitRule);
         return true;
     }
 
-    private void onActivityConfigurationChanged(@NonNull Activity activity) {
+    @GuardedBy("mLock")
+    private void onActivityConfigurationChanged(@NonNull WindowContainerTransaction wct,
+            @NonNull Activity activity) {
         if (activity.isFinishing()) {
             // Do nothing if the activity is currently finishing.
             return;
@@ -721,7 +728,7 @@
         }
 
         // Check if activity requires a placeholder
-        launchPlaceholderIfNecessary(activity, false /* isOnCreated */);
+        launchPlaceholderIfNecessary(wct, activity, false /* isOnCreated */);
     }
 
     @VisibleForTesting
@@ -741,7 +748,22 @@
      * creation.
      */
     void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) {
-        mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
+        synchronized (mLock) {
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            onTaskFragmentAppearEmptyTimeout(wct, container);
+            mPresenter.applyTransaction(wct);
+        }
+    }
+
+    /**
+     * Called when we have been waiting too long for the TaskFragment to become non-empty after
+     * creation.
+     */
+    void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct,
+            @NonNull TaskFragmentContainer container) {
+        synchronized (mLock) {
+            mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
+        }
     }
 
     /**
@@ -971,6 +993,7 @@
     }
 
     /** Cleanups all the dependencies when the TaskFragment is entering PIP. */
+    @GuardedBy("mLock")
     private void cleanupForEnterPip(@NonNull WindowContainerTransaction wct,
             @NonNull TaskFragmentContainer container) {
         final TaskContainer taskContainer = container.getTaskContainer();
@@ -1084,9 +1107,10 @@
      * Updates the presentation of the container. If the container is part of the split or should
      * have a placeholder, it will also update the other part of the split.
      */
+    @GuardedBy("mLock")
     void updateContainer(@NonNull WindowContainerTransaction wct,
             @NonNull TaskFragmentContainer container) {
-        if (launchPlaceholderIfNecessary(container)) {
+        if (launchPlaceholderIfNecessary(wct, container)) {
             // Placeholder was launched, the positions will be updated when the activity is added
             // to the secondary container.
             return;
@@ -1111,7 +1135,7 @@
             // Skip position update - one or both containers are finished.
             return;
         }
-        if (dismissPlaceholderIfNecessary(splitContainer)) {
+        if (dismissPlaceholderIfNecessary(wct, splitContainer)) {
             // Placeholder was finished, the positions will be updated when its container is emptied
             return;
         }
@@ -1173,16 +1197,20 @@
     /**
      * Checks if the container requires a placeholder and launches it if necessary.
      */
-    private boolean launchPlaceholderIfNecessary(@NonNull TaskFragmentContainer container) {
+    @GuardedBy("mLock")
+    private boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
+            @NonNull TaskFragmentContainer container) {
         final Activity topActivity = container.getTopNonFinishingActivity();
         if (topActivity == null) {
             return false;
         }
 
-        return launchPlaceholderIfNecessary(topActivity, false /* isOnCreated */);
+        return launchPlaceholderIfNecessary(wct, topActivity, false /* isOnCreated */);
     }
 
-    boolean launchPlaceholderIfNecessary(@NonNull Activity activity, boolean isOnCreated) {
+    @GuardedBy("mLock")
+    boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
+            @NonNull Activity activity, boolean isOnCreated) {
         if (activity.isFinishing()) {
             return false;
         }
@@ -1216,7 +1244,7 @@
 
         // TODO(b/190433398): Handle failed request
         final Bundle options = getPlaceholderOptions(activity, isOnCreated);
-        startActivityToSide(activity, placeholderRule.getPlaceholderIntent(), options,
+        startActivityToSide(wct, activity, placeholderRule.getPlaceholderIntent(), options,
                 placeholderRule, null /* failureCallback */, true /* isPlaceholder */);
         return true;
     }
@@ -1243,7 +1271,9 @@
     }
 
     @VisibleForTesting
-    boolean dismissPlaceholderIfNecessary(@NonNull SplitContainer splitContainer) {
+    @GuardedBy("mLock")
+    boolean dismissPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
+            @NonNull SplitContainer splitContainer) {
         if (!splitContainer.isPlaceholderContainer()) {
             return false;
         }
@@ -1257,7 +1287,7 @@
             return false;
         }
 
-        mPresenter.cleanupContainer(splitContainer.getSecondaryContainer(),
+        mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(),
                 false /* shouldFinishDependent */);
         return true;
     }
@@ -1523,7 +1553,8 @@
     private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
 
         @Override
-        public void onActivityPreCreated(Activity activity, Bundle savedInstanceState) {
+        public void onActivityPreCreated(@NonNull Activity activity,
+                @Nullable Bundle savedInstanceState) {
             synchronized (mLock) {
                 final IBinder activityToken = activity.getActivityToken();
                 final IBinder initialTaskFragmentToken = getInitialTaskFragmentToken(activity);
@@ -1552,25 +1583,30 @@
         }
 
         @Override
-        public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) {
+        public void onActivityPostCreated(@NonNull Activity activity,
+                @Nullable Bundle savedInstanceState) {
             // Calling after Activity#onCreate is complete to allow the app launch something
             // first. In case of a configured placeholder activity we want to make sure
             // that we don't launch it if an activity itself already requested something to be
             // launched to side.
             synchronized (mLock) {
-                SplitController.this.onActivityCreated(activity);
+                final WindowContainerTransaction wct = new WindowContainerTransaction();
+                SplitController.this.onActivityCreated(wct, activity);
+                mPresenter.applyTransaction(wct);
             }
         }
 
         @Override
-        public void onActivityConfigurationChanged(Activity activity) {
+        public void onActivityConfigurationChanged(@NonNull Activity activity) {
             synchronized (mLock) {
-                SplitController.this.onActivityConfigurationChanged(activity);
+                final WindowContainerTransaction wct = new WindowContainerTransaction();
+                SplitController.this.onActivityConfigurationChanged(wct, activity);
+                mPresenter.applyTransaction(wct);
             }
         }
 
         @Override
-        public void onActivityPostDestroyed(Activity activity) {
+        public void onActivityPostDestroyed(@NonNull Activity activity) {
             synchronized (mLock) {
                 SplitController.this.onActivityDestroyed(activity);
             }
@@ -1582,7 +1618,7 @@
         private final Handler mHandler = new Handler(Looper.getMainLooper());
 
         @Override
-        public void execute(Runnable r) {
+        public void execute(@NonNull Runnable r) {
             mHandler.post(r);
         }
     }
@@ -1662,7 +1698,7 @@
      * If the two rules have the same presentation, we can reuse the same {@link SplitContainer} if
      * there is any.
      */
-    private static boolean canReuseContainer(SplitRule rule1, SplitRule rule2) {
+    private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2) {
         if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) {
             return false;
         }
@@ -1670,7 +1706,8 @@
     }
 
     /** Whether the two rules have the same presentation. */
-    private static boolean haveSamePresentation(SplitPairRule rule1, SplitPairRule rule2) {
+    private static boolean haveSamePresentation(@NonNull SplitPairRule rule1,
+            @NonNull SplitPairRule rule2) {
         // TODO(b/231655482): add util method to do the comparison in SplitPairRule.
         return rule1.getSplitRatio() == rule2.getSplitRatio()
                 && rule1.getLayoutDirection() == rule2.getLayoutDirection()
@@ -1684,7 +1721,7 @@
      * Whether it is ok for other rule to reuse the {@link TaskFragmentContainer} of the given
      * rule.
      */
-    private static boolean isContainerReusableRule(SplitRule rule) {
+    private static boolean isContainerReusableRule(@NonNull SplitRule rule) {
         // We don't expect to reuse the placeholder rule.
         if (!(rule instanceof SplitPairRule)) {
             return false;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index a89847a..2b069d7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -102,37 +102,18 @@
 
     private final SplitController mController;
 
-    SplitPresenter(@NonNull Executor executor, SplitController controller) {
+    SplitPresenter(@NonNull Executor executor, @NonNull SplitController controller) {
         super(executor, controller);
         mController = controller;
         registerOrganizer();
     }
 
     /**
-     * Updates the presentation of the provided container.
-     */
-    void updateContainer(@NonNull TaskFragmentContainer container) {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        mController.updateContainer(wct, container);
-        applyTransaction(wct);
-    }
-
-    /**
      * Deletes the specified container and all other associated and dependent containers in the same
      * transaction.
      */
-    void cleanupContainer(@NonNull TaskFragmentContainer container, boolean shouldFinishDependent) {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        cleanupContainer(container, shouldFinishDependent, wct);
-        applyTransaction(wct);
-    }
-
-    /**
-     * Deletes the specified container and all other associated and dependent containers in the same
-     * transaction.
-     */
-    void cleanupContainer(@NonNull TaskFragmentContainer container, boolean shouldFinishDependent,
-            @NonNull WindowContainerTransaction wct) {
+    void cleanupContainer(@NonNull WindowContainerTransaction wct,
+            @NonNull TaskFragmentContainer container, boolean shouldFinishDependent) {
         container.finish(shouldFinishDependent, this, wct, mController);
 
         final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer(
@@ -190,10 +171,9 @@
      *                          created and the activity will be re-parented to it.
      * @param rule The split rule to be applied to the container.
      */
-    void createNewSplitContainer(@NonNull Activity primaryActivity,
-            @NonNull Activity secondaryActivity, @NonNull SplitPairRule rule) {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-
+    void createNewSplitContainer(@NonNull WindowContainerTransaction wct,
+            @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity,
+            @NonNull SplitPairRule rule) {
         final Rect parentBounds = getParentContainerBounds(primaryActivity);
         final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity,
                 secondaryActivity);
@@ -219,8 +199,6 @@
                 minDimensionsPair);
 
         mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
-
-        applyTransaction(wct);
     }
 
     /**
@@ -262,7 +240,8 @@
      * @param rule              The split rule to be applied to the container.
      * @param isPlaceholder     Whether the launch is a placeholder.
      */
-    void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent activityIntent,
+    void startActivityToSide(@NonNull WindowContainerTransaction wct,
+            @NonNull Activity launchingActivity, @NonNull Intent activityIntent,
             @Nullable Bundle activityOptions, @NonNull SplitRule rule, boolean isPlaceholder) {
         final Rect parentBounds = getParentContainerBounds(launchingActivity);
         final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
@@ -284,7 +263,6 @@
                 launchingActivity, taskId);
         final int windowingMode = mController.getTaskContainer(taskId)
                 .getWindowingModeForSplitTaskFragment(primaryRectBounds);
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
         mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
                 rule);
         startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
@@ -294,7 +272,6 @@
             // When placeholder is launched in split, we should keep the focus on the primary.
             wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
         }
-        applyTransaction(wct);
     }
 
     /**
@@ -502,14 +479,14 @@
     }
 
     @NonNull
-    static Pair<Size, Size> getActivitiesMinDimensionsPair(Activity primaryActivity,
-            Activity secondaryActivity) {
+    static Pair<Size, Size> getActivitiesMinDimensionsPair(@NonNull Activity primaryActivity,
+            @NonNull Activity secondaryActivity) {
         return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryActivity));
     }
 
     @NonNull
-    static Pair<Size, Size> getActivityIntentMinDimensionsPair(Activity primaryActivity,
-            Intent secondaryIntent) {
+    static Pair<Size, Size> getActivityIntentMinDimensionsPair(@NonNull Activity primaryActivity,
+            @NonNull Intent secondaryIntent) {
         return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryIntent));
     }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 0ea5603..77e26c0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -21,8 +21,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.WindowConfiguration;
 import android.app.WindowConfiguration.WindowingMode;
@@ -31,6 +29,9 @@
 import android.util.ArraySet;
 import android.window.TaskFragmentInfo;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
index f721341..ee2e139 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
@@ -30,6 +30,8 @@
 import android.view.RemoteAnimationDefinition;
 import android.window.TaskFragmentOrganizer;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 
 /** Controls the TaskFragment remote animations. */
@@ -45,7 +47,7 @@
     /** Task Ids that we have registered for remote animation. */
     private final ArraySet<Integer> mRegisterTasks = new ArraySet<>();
 
-    TaskFragmentAnimationController(TaskFragmentOrganizer organizer) {
+    TaskFragmentAnimationController(@NonNull TaskFragmentOrganizer organizer) {
         mOrganizer = organizer;
         mDefinition = new RemoteAnimationDefinition();
         final RemoteAnimationAdapter animationAdapter =
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index c4f3709..8af2d9c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -112,6 +112,7 @@
     }
 
     /** Creates the animator given the transition type and windows. */
+    @NonNull
     private Animator createAnimator(@WindowManager.TransitionOldType int transit,
             @NonNull RemoteAnimationTarget[] targets,
             @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
@@ -161,6 +162,7 @@
     }
 
     /** List of {@link TaskFragmentAnimationAdapter} to handle animations on all window targets. */
+    @NonNull
     private List<TaskFragmentAnimationAdapter> createAnimationAdapters(
             @WindowManager.TransitionOldType int transit,
             @NonNull RemoteAnimationTarget[] targets) {
@@ -180,12 +182,14 @@
         }
     }
 
+    @NonNull
     private List<TaskFragmentAnimationAdapter> createOpenAnimationAdapters(
             @NonNull RemoteAnimationTarget[] targets) {
         return createOpenCloseAnimationAdapters(targets, true /* isOpening */,
                 mAnimationSpec::loadOpenAnimation);
     }
 
+    @NonNull
     private List<TaskFragmentAnimationAdapter> createCloseAnimationAdapters(
             @NonNull RemoteAnimationTarget[] targets) {
         return createOpenCloseAnimationAdapters(targets, false /* isOpening */,
@@ -196,6 +200,7 @@
      * Creates {@link TaskFragmentAnimationAdapter} for OPEN and CLOSE types of transition.
      * @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type.
      */
+    @NonNull
     private List<TaskFragmentAnimationAdapter> createOpenCloseAnimationAdapters(
             @NonNull RemoteAnimationTarget[] targets, boolean isOpening,
             @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider) {
@@ -238,6 +243,7 @@
         return adapters;
     }
 
+    @NonNull
     private TaskFragmentAnimationAdapter createOpenCloseAnimationAdapter(
             @NonNull RemoteAnimationTarget target,
             @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider,
@@ -259,6 +265,7 @@
         return new TaskFragmentAnimationAdapter(animation, target);
     }
 
+    @NonNull
     private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
             @NonNull RemoteAnimationTarget[] targets) {
         final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
index 5cc496a..97d42391b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -26,6 +26,7 @@
 import android.os.Handler;
 import android.provider.Settings;
 import android.view.RemoteAnimationTarget;
+import android.view.WindowManager;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
 import android.view.animation.AnimationSet;
@@ -68,16 +69,14 @@
 
         // The transition animation should be adjusted based on the developer option.
         final ContentResolver resolver = mContext.getContentResolver();
-        mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
-                Settings.Global.TRANSITION_ANIMATION_SCALE,
-                mContext.getResources().getFloat(
-                        R.dimen.config_appTransitionAnimationDurationScaleDefault));
+        mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
         resolver.registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
                 new SettingsObserver(handler));
     }
 
     /** For target that doesn't need to be animated. */
+    @NonNull
     static Animation createNoopAnimation(@NonNull RemoteAnimationTarget target) {
         // Noop but just keep the target showing/hiding.
         final float alpha = target.mode == MODE_CLOSING ? 0f : 1f;
@@ -85,6 +84,7 @@
     }
 
     /** Animation for target that is opening in a change transition. */
+    @NonNull
     Animation createChangeBoundsOpenAnimation(@NonNull RemoteAnimationTarget target) {
         final Rect bounds = target.localBounds;
         // The target will be animated in from left or right depends on its position.
@@ -101,6 +101,7 @@
     }
 
     /** Animation for target that is closing in a change transition. */
+    @NonNull
     Animation createChangeBoundsCloseAnimation(@NonNull RemoteAnimationTarget target) {
         final Rect bounds = target.localBounds;
         // The target will be animated out to left or right depends on its position.
@@ -121,6 +122,7 @@
      * @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 RemoteAnimationTarget target) {
         // Both start bounds and end bounds are in screen coordinates. We will post translate
         // to the local coordinates in TaskFragmentAnimationAdapter#onAnimationUpdate
@@ -177,6 +179,7 @@
         return new Animation[]{startSet, endSet};
     }
 
+    @NonNull
     Animation loadOpenAnimation(@NonNull RemoteAnimationTarget target,
             @NonNull Rect wholeAnimationBounds) {
         final boolean isEnter = target.mode != MODE_CLOSING;
@@ -198,6 +201,7 @@
         return animation;
     }
 
+    @NonNull
     Animation loadCloseAnimation(@NonNull RemoteAnimationTarget target,
             @NonNull Rect wholeAnimationBounds) {
         final boolean isEnter = target.mode != MODE_CLOSING;
@@ -217,6 +221,12 @@
         return animation;
     }
 
+    private float getTransitionAnimationScaleSetting() {
+        return WindowManager.fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+                Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
+                                R.dimen.config_appTransitionAnimationDurationScaleDefault)));
+    }
+
     private class SettingsObserver extends ContentObserver {
         SettingsObserver(@NonNull Handler handler) {
             super(handler);
@@ -224,9 +234,7 @@
 
         @Override
         public void onChange(boolean selfChange) {
-            mTransitionAnimationScaleSetting = Settings.Global.getFloat(
-                    mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE,
-                    mTransitionAnimationScaleSetting);
+            mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
         }
     }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 37f5b6d..11c0db3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -18,8 +18,6 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.WindowConfiguration.WindowingMode;
 import android.content.Intent;
@@ -30,6 +28,9 @@
 import android.window.TaskFragmentInfo;
 import android.window.WindowContainerTransaction;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
@@ -175,6 +176,7 @@
                 && mInfo.getActivities().size() == collectNonFinishingActivities().size();
     }
 
+    @NonNull
     ActivityStack toActivityStack() {
         return new ActivityStack(collectNonFinishingActivities(), isEmpty());
     }
@@ -249,19 +251,22 @@
         return mInfo;
     }
 
-    void setInfo(@NonNull TaskFragmentInfo info) {
+    void setInfo(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo info) {
         if (!mIsFinished && mInfo == null && info.isEmpty()) {
             // onTaskFragmentAppeared with empty info. We will remove the TaskFragment if no
             // pending appeared intent/activities. Otherwise, wait and removing the TaskFragment if
             // it is still empty after timeout.
-            mAppearEmptyTimeout = () -> {
-                mAppearEmptyTimeout = null;
-                mController.onTaskFragmentAppearEmptyTimeout(this);
-            };
             if (mPendingAppearedIntent != null || !mPendingAppearedActivities.isEmpty()) {
+                mAppearEmptyTimeout = () -> {
+                    mAppearEmptyTimeout = null;
+                    // Call without the pass-in wct when timeout. We need to applyWct directly
+                    // in this case.
+                    mController.onTaskFragmentAppearEmptyTimeout(this);
+                };
                 mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS);
             } else {
-                mAppearEmptyTimeout.run();
+                mAppearEmptyTimeout = null;
+                mController.onTaskFragmentAppearEmptyTimeout(wct, this);
             }
         } else if (mAppearEmptyTimeout != null && !info.isEmpty()) {
             mController.getHandler().removeCallbacks(mAppearEmptyTimeout);
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/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 4d25952..21cf7a6 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -56,6 +56,8 @@
  * Build/Install/Run:
  *  atest WMJetpackUnitTests:JetpackTaskFragmentOrganizerTest
  */
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -119,7 +121,7 @@
                 new Intent(), taskContainer, mSplitController);
         final TaskFragmentInfo info = createMockInfo(container);
         mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info);
-        container.setInfo(info);
+        container.setInfo(mTransaction, info);
 
         mOrganizer.expandTaskFragment(mTransaction, container.getTaskFragmentToken());
 
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 4bc5033..07758d2 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -89,6 +89,8 @@
  * Build/Install/Run:
  *  atest WMJetpackUnitTests:SplitControllerTest
  */
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -158,14 +160,14 @@
         final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
         doReturn(new ArrayList<>()).when(info).getActivities();
         doReturn(true).when(info).isEmpty();
-        tf1.setInfo(info);
+        tf1.setInfo(mTransaction, info);
 
         assertWithMessage("Must return tf because we are waiting for tf1 to become non-empty after"
                 + " creation.")
                 .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
 
         doReturn(false).when(info).isEmpty();
-        tf1.setInfo(info);
+        tf1.setInfo(mTransaction, info);
 
         assertWithMessage("Must return null because tf1 becomes empty.")
                 .that(mSplitController.getTopActiveContainer(TASK_ID)).isNull();
@@ -177,7 +179,7 @@
         doReturn(tf.getTaskFragmentToken()).when(mInfo).getFragmentToken();
 
         // The TaskFragment has been removed in the server, we only need to cleanup the reference.
-        mSplitController.onTaskFragmentVanished(mInfo);
+        mSplitController.onTaskFragmentVanished(mTransaction, mInfo);
 
         verify(mSplitPresenter, never()).deleteTaskFragment(any(), any());
         verify(mSplitController).removeContainer(tf);
@@ -187,9 +189,10 @@
     @Test
     public void testOnTaskFragmentAppearEmptyTimeout() {
         final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
-        mSplitController.onTaskFragmentAppearEmptyTimeout(tf);
+        mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf);
 
-        verify(mSplitPresenter).cleanupContainer(tf, false /* shouldFinishDependent */);
+        verify(mSplitPresenter).cleanupContainer(mTransaction, tf,
+                false /* shouldFinishDependent */);
     }
 
     @Test
@@ -229,8 +232,8 @@
         spyOn(tf);
         doReturn(mActivity).when(tf).getTopNonFinishingActivity();
         doReturn(true).when(tf).isEmpty();
-        doReturn(true).when(mSplitController).launchPlaceholderIfNecessary(mActivity,
-                false /* isOnCreated */);
+        doReturn(true).when(mSplitController).launchPlaceholderIfNecessary(mTransaction,
+                mActivity, false /* isOnCreated */);
         doNothing().when(mSplitPresenter).updateSplitContainer(any(), any(), any());
 
         mSplitController.updateContainer(mTransaction, tf);
@@ -250,7 +253,7 @@
 
         mSplitController.updateContainer(mTransaction, tf);
 
-        verify(mSplitController, never()).dismissPlaceholderIfNecessary(any());
+        verify(mSplitController, never()).dismissPlaceholderIfNecessary(any(), any());
 
         // Verify if tf is not in the top splitContainer,
         final SplitContainer splitContainer = mock(SplitContainer.class);
@@ -264,7 +267,7 @@
 
         mSplitController.updateContainer(mTransaction, tf);
 
-        verify(mSplitController, never()).dismissPlaceholderIfNecessary(any());
+        verify(mSplitController, never()).dismissPlaceholderIfNecessary(any(), any());
 
         // Verify if one or both containers in the top SplitContainer are finished,
         // dismissPlaceholder() won't be called.
@@ -273,12 +276,12 @@
 
         mSplitController.updateContainer(mTransaction, tf);
 
-        verify(mSplitController, never()).dismissPlaceholderIfNecessary(any());
+        verify(mSplitController, never()).dismissPlaceholderIfNecessary(any(), any());
 
         // Verify if placeholder should be dismissed, updateSplitContainer() won't be called.
         doReturn(false).when(tf).isFinished();
         doReturn(true).when(mSplitController)
-                .dismissPlaceholderIfNecessary(splitContainer);
+                .dismissPlaceholderIfNecessary(mTransaction, splitContainer);
 
         mSplitController.updateContainer(mTransaction, tf);
 
@@ -286,7 +289,7 @@
 
         // Verify if the top active split is updated if both of its containers are not finished.
         doReturn(false).when(mSplitController)
-                        .dismissPlaceholderIfNecessary(splitContainer);
+                        .dismissPlaceholderIfNecessary(mTransaction, splitContainer);
 
         mSplitController.updateContainer(mTransaction, tf);
 
@@ -315,34 +318,36 @@
 
     @Test
     public void testOnActivityCreated() {
-        mSplitController.onActivityCreated(mActivity);
+        mSplitController.onActivityCreated(mTransaction, mActivity);
 
         // Disallow to split as primary because we want the new launch to be always on top.
-        verify(mSplitController).resolveActivityToContainer(mActivity, false /* isOnReparent */);
+        verify(mSplitController).resolveActivityToContainer(mTransaction, mActivity,
+                false /* isOnReparent */);
     }
 
     @Test
-    public void testOnActivityReparentToTask_sameProcess() {
-        mSplitController.onActivityReparentToTask(TASK_ID, new Intent(),
+    public void testOnActivityReparentedToTask_sameProcess() {
+        mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, new Intent(),
                 mActivity.getActivityToken());
 
         // Treated as on activity created, but allow to split as primary.
-        verify(mSplitController).resolveActivityToContainer(mActivity, true /* isOnReparent */);
+        verify(mSplitController).resolveActivityToContainer(mTransaction,
+                mActivity, true /* isOnReparent */);
         // Try to place the activity to the top TaskFragment when there is no matched rule.
-        verify(mSplitController).placeActivityInTopContainer(mActivity);
+        verify(mSplitController).placeActivityInTopContainer(mTransaction, mActivity);
     }
 
     @Test
-    public void testOnActivityReparentToTask_diffProcess() {
+    public void testOnActivityReparentedToTask_diffProcess() {
         // Create an empty TaskFragment to initialize for the Task.
         mSplitController.newContainer(new Intent(), mActivity, TASK_ID);
         final IBinder activityToken = new Binder();
         final Intent intent = new Intent();
 
-        mSplitController.onActivityReparentToTask(TASK_ID, intent, activityToken);
+        mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, intent, activityToken);
 
         // Treated as starting new intent
-        verify(mSplitController, never()).resolveActivityToContainer(any(), anyBoolean());
+        verify(mSplitController, never()).resolveActivityToContainer(any(), any(), anyBoolean());
         verify(mSplitController).resolveStartActivityIntent(any(), eq(TASK_ID), eq(intent),
                 isNull());
     }
@@ -504,26 +509,29 @@
 
     @Test
     public void testPlaceActivityInTopContainer() {
-        mSplitController.placeActivityInTopContainer(mActivity);
+        mSplitController.placeActivityInTopContainer(mTransaction, mActivity);
 
-        verify(mSplitPresenter, never()).applyTransaction(any());
+        verify(mTransaction, never()).reparentActivityToTaskFragment(any(), any());
 
-        mSplitController.newContainer(new Intent(), mActivity, TASK_ID);
-        mSplitController.placeActivityInTopContainer(mActivity);
+        // Place in the top container if there is no other rule matched.
+        final TaskFragmentContainer topContainer = mSplitController
+                .newContainer(new Intent(), mActivity, TASK_ID);
+        mSplitController.placeActivityInTopContainer(mTransaction, mActivity);
 
-        verify(mSplitPresenter).applyTransaction(any());
+        verify(mTransaction).reparentActivityToTaskFragment(topContainer.getTaskFragmentToken(),
+                mActivity.getActivityToken());
 
         // Not reparent if activity is in a TaskFragment.
-        clearInvocations(mSplitPresenter);
+        clearInvocations(mTransaction);
         mSplitController.newContainer(mActivity, TASK_ID);
-        mSplitController.placeActivityInTopContainer(mActivity);
+        mSplitController.placeActivityInTopContainer(mTransaction, mActivity);
 
-        verify(mSplitPresenter, never()).applyTransaction(any());
+        verify(mTransaction, never()).reparentActivityToTaskFragment(any(), any());
     }
 
     @Test
     public void testResolveActivityToContainer_noRuleMatched() {
-        final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
         assertFalse(result);
@@ -535,7 +543,7 @@
         setupExpandRule(mActivity);
 
         // When the activity is not in any TaskFragment, create a new expanded TaskFragment for it.
-        final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
         final TaskFragmentContainer container = mSplitController.getContainerWithActivity(
                 mActivity);
@@ -543,7 +551,8 @@
         assertTrue(result);
         assertNotNull(container);
         verify(mSplitController).newContainer(mActivity, TASK_ID);
-        verify(mSplitPresenter).expandActivity(container.getTaskFragmentToken(), mActivity);
+        verify(mSplitPresenter).expandActivity(mTransaction, container.getTaskFragmentToken(),
+                mActivity);
     }
 
     @Test
@@ -552,11 +561,11 @@
 
         // When the activity is not in any TaskFragment, create a new expanded TaskFragment for it.
         final TaskFragmentContainer container = mSplitController.newContainer(mActivity, TASK_ID);
-        final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
         assertTrue(result);
-        verify(mSplitPresenter).expandTaskFragment(container.getTaskFragmentToken());
+        verify(mSplitPresenter).expandTaskFragment(mTransaction, container.getTaskFragmentToken());
     }
 
     @Test
@@ -566,14 +575,15 @@
         // When the activity is not in any TaskFragment, create a new expanded TaskFragment for it.
         final Activity activity = createMockActivity();
         addSplitTaskFragments(activity, mActivity);
-        final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
         final TaskFragmentContainer container = mSplitController.getContainerWithActivity(
                 mActivity);
 
         assertTrue(result);
         assertNotNull(container);
-        verify(mSplitPresenter).expandActivity(container.getTaskFragmentToken(), mActivity);
+        verify(mSplitPresenter).expandActivity(mTransaction, container.getTaskFragmentToken(),
+                mActivity);
     }
 
     @Test
@@ -583,11 +593,11 @@
                 (SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
 
         // Launch placeholder if the activity is not in any TaskFragment.
-        final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
         assertTrue(result);
-        verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT,
+        verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
                 mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
                 placeholderRule, true /* isPlaceholder */);
     }
@@ -600,11 +610,11 @@
         final Activity activity = createMockActivity();
         mSplitController.newContainer(mActivity, TASK_ID);
         mSplitController.newContainer(activity, TASK_ID);
-        final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
         assertFalse(result);
-        verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(),
+        verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
                 anyBoolean());
     }
 
@@ -616,11 +626,11 @@
 
         // Launch placeholder if the activity is in the topmost expanded TaskFragment.
         mSplitController.newContainer(mActivity, TASK_ID);
-        final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
         assertTrue(result);
-        verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT,
+        verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
                 mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
                 placeholderRule, true /* isPlaceholder */);
     }
@@ -632,11 +642,11 @@
         // Don't launch placeholder if the activity is in primary split.
         final Activity secondaryActivity = createMockActivity();
         addSplitTaskFragments(mActivity, secondaryActivity);
-        final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
         assertFalse(result);
-        verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(),
+        verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
                 anyBoolean());
     }
 
@@ -649,11 +659,11 @@
         // Launch placeholder if the activity is in secondary split.
         final Activity primaryActivity = createMockActivity();
         addSplitTaskFragments(primaryActivity, mActivity);
-        final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
         assertTrue(result);
-        verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT,
+        verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
                 mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
                 placeholderRule, true /* isPlaceholder */);
     }
@@ -676,7 +686,7 @@
                 secondaryContainer,
                 splitRule);
         clearInvocations(mSplitController);
-        final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
         assertTrue(result);
@@ -705,7 +715,7 @@
         final Activity launchedActivity = createMockActivity();
         primaryContainer.addPendingAppearedActivity(launchedActivity);
 
-        assertFalse(mSplitController.resolveActivityToContainer(launchedActivity,
+        assertFalse(mSplitController.resolveActivityToContainer(mTransaction, launchedActivity,
                 false /* isOnReparent */));
     }
 
@@ -717,7 +727,7 @@
         // Activity is already in secondary split, no need to create new split.
         addSplitTaskFragments(primaryActivity, mActivity);
         clearInvocations(mSplitController);
-        final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
         assertTrue(result);
@@ -735,7 +745,7 @@
         addSplitTaskFragments(primaryActivity, secondaryActivity);
         mSplitController.getContainerWithActivity(secondaryActivity)
                 .addPendingAppearedActivity(mActivity);
-        final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
         assertFalse(result);
@@ -760,7 +770,7 @@
                 mActivity,
                 secondaryContainer,
                 placeholderRule);
-        final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
         assertTrue(result);
@@ -774,7 +784,7 @@
         final TaskFragmentContainer container = mSplitController.newContainer(activityBelow,
                 TASK_ID);
         container.addPendingAppearedActivity(mActivity);
-        final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
         assertTrue(result);
@@ -790,14 +800,15 @@
         final TaskFragmentContainer container = mSplitController.newContainer(activityBelow,
                 TASK_ID);
         container.addPendingAppearedActivity(mActivity);
-        boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
         assertFalse(result);
         assertEquals(container, mSplitController.getContainerWithActivity(mActivity));
 
         // Allow to split as primary.
-        result = mSplitController.resolveActivityToContainer(mActivity, true /* isOnReparent */);
+        result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
+                true /* isOnReparent */);
 
         assertTrue(result);
         assertSplitPair(mActivity, activityBelow);
@@ -815,7 +826,7 @@
         final TaskFragmentContainer secondaryContainer = mSplitController.getContainerWithActivity(
                 activityBelow);
         secondaryContainer.addPendingAppearedActivity(mActivity);
-        final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
         final TaskFragmentContainer container = mSplitController.getContainerWithActivity(
                 mActivity);
@@ -836,14 +847,15 @@
         final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity(
                 primaryActivity);
         primaryContainer.addPendingAppearedActivity(mActivity);
-        boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
         assertFalse(result);
         assertEquals(primaryContainer, mSplitController.getContainerWithActivity(mActivity));
 
 
-        result = mSplitController.resolveActivityToContainer(mActivity, true /* isOnReparent */);
+        result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
+                true /* isOnReparent */);
 
         assertTrue(result);
         assertSplitPair(mActivity, primaryActivity);
@@ -861,7 +873,7 @@
         container.addPendingAppearedActivity(mActivity);
 
         // Allow to split as primary.
-        boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 true /* isOnReparent */);
 
         assertTrue(result);
@@ -879,15 +891,13 @@
                 TASK_ID);
         container.addPendingAppearedActivity(mActivity);
 
-        boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
         assertTrue(result);
         assertSplitPair(activityBelow, mActivity, true /* matchParentBounds */);
     }
 
-    // Suppress GuardedBy warning on unit tests
-    @SuppressWarnings("GuardedBy")
     @Test
     public void testResolveActivityToContainer_minDimensions_shouldExpandSplitContainer() {
         final Activity primaryActivity = createMockActivity();
@@ -899,14 +909,14 @@
         doReturn(secondaryActivity).when(mSplitController).findActivityBelow(eq(mActivity));
 
         clearInvocations(mSplitPresenter);
-        boolean result = mSplitController.resolveActivityToContainer(mActivity,
+        boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
         assertTrue(result);
         assertSplitPair(primaryActivity, mActivity, true /* matchParentBounds */);
         assertEquals(mSplitController.getContainerWithActivity(secondaryActivity),
                 mSplitController.getContainerWithActivity(mActivity));
-        verify(mSplitPresenter, never()).createNewSplitContainer(any(), any(), any());
+        verify(mSplitPresenter, never()).createNewSplitContainer(any(), any(), any(), any());
     }
 
     @Test
@@ -914,7 +924,7 @@
         doReturn(new Binder()).when(mSplitController).getInitialTaskFragmentToken(mActivity);
 
         // No need to handle when the new launched activity is in an unknown TaskFragment.
-        assertTrue(mSplitController.resolveActivityToContainer(mActivity,
+        assertTrue(mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */));
     }
 
@@ -993,7 +1003,7 @@
     private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container,
             @NonNull Activity activity) {
         final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity);
-        container.setInfo(info);
+        container.setInfo(mTransaction, info);
         mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info);
     }
 
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index d7931966..3fdf8e5 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -78,6 +78,8 @@
  * Build/Install/Run:
  *  atest WMJetpackUnitTests:SplitPresenterTest
  */
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -226,8 +228,9 @@
                 mTransaction, splitContainer, mActivity, secondaryActivity,
                 null /* secondaryIntent */));
 
-        primaryTf.setInfo(createMockTaskFragmentInfo(primaryTf, mActivity));
-        secondaryTf.setInfo(createMockTaskFragmentInfo(secondaryTf, secondaryActivity));
+        primaryTf.setInfo(mTransaction, createMockTaskFragmentInfo(primaryTf, mActivity));
+        secondaryTf.setInfo(mTransaction,
+                createMockTaskFragmentInfo(secondaryTf, secondaryActivity));
 
         assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
                 splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */));
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 44c7e6c..6cbecff 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -19,6 +19,8 @@
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
 import static org.junit.Assert.assertEquals;
@@ -36,7 +38,6 @@
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Binder;
-import android.os.Handler;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
 import android.window.TaskFragmentInfo;
@@ -62,25 +63,27 @@
  * Build/Install/Run:
  *  atest WMJetpackUnitTests:TaskFragmentContainerTest
  */
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class TaskFragmentContainerTest {
     @Mock
     private SplitPresenter mPresenter;
-    @Mock
     private SplitController mController;
     @Mock
     private TaskFragmentInfo mInfo;
     @Mock
-    private Handler mHandler;
+    private WindowContainerTransaction mTransaction;
     private Activity mActivity;
     private Intent mIntent;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        doReturn(mHandler).when(mController).getHandler();
+        mController = new SplitController();
+        spyOn(mController);
         mActivity = createMockActivity();
         mIntent = new Intent();
     }
@@ -123,7 +126,7 @@
 
         // Remove all references after the container has appeared in server.
         doReturn(new ArrayList<>()).when(mInfo).getActivities();
-        container.setInfo(mInfo);
+        container.setInfo(mTransaction, mInfo);
         container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController);
 
         verify(mActivity, never()).finish();
@@ -137,7 +140,7 @@
         final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity,
                 null /* pendingAppearedIntent */, taskContainer, mController);
         final TaskFragmentInfo info = createMockTaskFragmentInfo(container0, mActivity);
-        container0.setInfo(info);
+        container0.setInfo(mTransaction, info);
         // Request to reparent the activity to a new TaskFragment.
         final TaskFragmentContainer container1 = new TaskFragmentContainer(mActivity,
                 null /* pendingAppearedIntent */, taskContainer, mController);
@@ -163,7 +166,7 @@
 
         final TaskFragmentInfo info0 = createMockTaskFragmentInfo(pendingActivityContainer,
                 mActivity);
-        pendingActivityContainer.setInfo(info0);
+        pendingActivityContainer.setInfo(mTransaction, info0);
 
         assertTrue(pendingActivityContainer.mPendingAppearedActivities.isEmpty());
 
@@ -175,7 +178,7 @@
 
         final TaskFragmentInfo info1 = createMockTaskFragmentInfo(pendingIntentContainer,
                 mActivity);
-        pendingIntentContainer.setInfo(info1);
+        pendingIntentContainer.setInfo(mTransaction, info1);
 
         assertNull(pendingIntentContainer.getPendingAppearedIntent());
     }
@@ -191,18 +194,19 @@
         final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
         doReturn(new ArrayList<>()).when(info).getActivities();
         doReturn(true).when(info).isEmpty();
-        container.setInfo(info);
+        container.setInfo(mTransaction, info);
 
         assertTrue(container.isWaitingActivityAppear());
 
         doReturn(false).when(info).isEmpty();
-        container.setInfo(info);
+        container.setInfo(mTransaction, info);
 
         assertFalse(container.isWaitingActivityAppear());
     }
 
     @Test
     public void testAppearEmptyTimeout() {
+        doNothing().when(mController).onTaskFragmentAppearEmptyTimeout(any(), any());
         final TaskContainer taskContainer = new TaskContainer(TASK_ID);
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
                 mIntent, taskContainer, mController);
@@ -213,20 +217,20 @@
         final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
         container.mInfo = null;
         doReturn(true).when(info).isEmpty();
-        container.setInfo(info);
+        container.setInfo(mTransaction, info);
 
         assertNotNull(container.mAppearEmptyTimeout);
 
         // Not set if it is not appeared empty.
         doReturn(new ArrayList<>()).when(info).getActivities();
         doReturn(false).when(info).isEmpty();
-        container.setInfo(info);
+        container.setInfo(mTransaction, info);
 
         assertNull(container.mAppearEmptyTimeout);
 
         // Remove timeout after the container becomes non-empty.
         doReturn(false).when(info).isEmpty();
-        container.setInfo(info);
+        container.setInfo(mTransaction, info);
 
         assertNull(container.mAppearEmptyTimeout);
 
@@ -234,7 +238,7 @@
         container.mInfo = null;
         container.setPendingAppearedIntent(mIntent);
         doReturn(true).when(info).isEmpty();
-        container.setInfo(info);
+        container.setInfo(mTransaction, info);
         container.mAppearEmptyTimeout.run();
 
         assertNull(container.mAppearEmptyTimeout);
@@ -260,7 +264,7 @@
         final List<IBinder> runningActivities = Lists.newArrayList(activity0.getActivityToken(),
                 activity1.getActivityToken());
         doReturn(runningActivities).when(mInfo).getActivities();
-        container.setInfo(mInfo);
+        container.setInfo(mTransaction, mInfo);
         activities = container.collectNonFinishingActivities();
 
         assertEquals(3, activities.size());
@@ -295,7 +299,7 @@
         final Activity activity = createMockActivity();
         final List<IBinder> runningActivities = Lists.newArrayList(activity.getActivityToken());
         doReturn(runningActivities).when(mInfo).getActivities();
-        container.setInfo(mInfo);
+        container.setInfo(mTransaction, mInfo);
 
         assertEquals(activity, container.getBottomMostActivity());
     }
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/animator/tv_pip_menu_action_button_animator.xml b/libs/WindowManager/Shell/res/animator/tv_window_menu_action_button_animator.xml
similarity index 100%
rename from libs/WindowManager/Shell/res/animator/tv_pip_menu_action_button_animator.xml
rename to libs/WindowManager/Shell/res/animator/tv_window_menu_action_button_animator.xml
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml b/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml
deleted file mode 100644
index 2758704..0000000
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true"
-          android:color="@color/tv_pip_menu_icon_focused" />
-    <item android:state_enabled="false"
-          android:color="@color/tv_pip_menu_icon_disabled" />
-    <item android:color="@color/tv_pip_menu_icon_unfocused" />
-</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml b/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml
deleted file mode 100644
index 4f5e63d..0000000
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true"
-          android:color="@color/tv_pip_menu_icon_bg_focused" />
-    <item android:color="@color/tv_pip_menu_icon_bg_unfocused" />
-</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon.xml
similarity index 91%
rename from libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml
rename to libs/WindowManager/Shell/res/color/tv_window_menu_close_icon.xml
index ce8640d..67467bb 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon.xml
@@ -15,5 +15,5 @@
   ~ limitations under the License.
   -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@color/tv_pip_menu_icon_unfocused" />
+    <item android:color="@color/tv_window_menu_icon_unfocused" />
 </selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon_bg.xml
similarity index 84%
rename from libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
rename to libs/WindowManager/Shell/res/color/tv_window_menu_close_icon_bg.xml
index 6cbf66f..4182bfe 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon_bg.xml
@@ -16,6 +16,6 @@
   -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_focused="true"
-          android:color="@color/tv_pip_menu_close_icon_bg_focused" />
-    <item android:color="@color/tv_pip_menu_close_icon_bg_unfocused" />
+        android:color="@color/tv_window_menu_close_icon_bg_focused" />
+    <item android:color="@color/tv_window_menu_close_icon_bg_unfocused" />
 </selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_icon.xml
similarity index 77%
copy from libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
copy to libs/WindowManager/Shell/res/color/tv_window_menu_icon.xml
index 6cbf66f..45205d2 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_icon.xml
@@ -16,6 +16,8 @@
   -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_focused="true"
-          android:color="@color/tv_pip_menu_close_icon_bg_focused" />
-    <item android:color="@color/tv_pip_menu_close_icon_bg_unfocused" />
+        android:color="@color/tv_window_menu_icon_focused" />
+    <item android:state_enabled="false"
+        android:color="@color/tv_window_menu_icon_disabled" />
+    <item android:color="@color/tv_window_menu_icon_unfocused" />
 </selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_icon_bg.xml
similarity index 84%
copy from libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
copy to libs/WindowManager/Shell/res/color/tv_window_menu_icon_bg.xml
index 6cbf66f..1bd26e1 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_icon_bg.xml
@@ -16,6 +16,6 @@
   -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_focused="true"
-          android:color="@color/tv_pip_menu_close_icon_bg_focused" />
-    <item android:color="@color/tv_pip_menu_close_icon_bg_unfocused" />
+        android:color="@color/tv_window_menu_icon_bg_focused" />
+    <item android:color="@color/tv_window_menu_icon_bg_unfocused" />
 </selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml b/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml
similarity index 64%
copy from libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
copy to libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml
index 6cbf66f..0bcaa53 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2022 The Android Open Source Project
   ~
@@ -14,8 +13,12 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true"
-          android:color="@color/tv_pip_menu_close_icon_bg_focused" />
-    <item android:color="@color/tv_pip_menu_close_icon_bg_unfocused" />
-</selector>
\ No newline at end of file
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white" android:pathData="M6,21V19H18V21Z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
index 846fdb3..7085a2c 100644
--- a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
+++ b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License.
   -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:exitFadeDuration="@integer/pip_menu_fade_animation_duration">
+    android:exitFadeDuration="@integer/tv_window_menu_fade_animation_duration">
     <item android:state_activated="true">
         <shape android:shape="rectangle">
             <corners android:radius="@dimen/pip_menu_border_corner_radius" />
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml b/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
similarity index 73%
copy from libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml
copy to libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
index ce8640d..2dba37d 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml
+++ b/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
@@ -14,6 +14,8 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@color/tv_pip_menu_icon_unfocused" />
-</selector>
\ No newline at end of file
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/tv_window_menu_button_radius" />
+    <solid android:color="@color/tv_window_menu_icon_bg" />
+</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
index a112f19..d183e42 100644
--- a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
+++ b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
@@ -22,6 +22,17 @@
     android:gravity="end"
     android:background="@drawable/decor_caption_title">
     <Button
+        android:id="@+id/minimize_window"
+        android:visibility="gone"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:layout_margin="5dp"
+        android:padding="4dp"
+        android:layout_gravity="top|end"
+        android:contentDescription="@string/maximize_button_text"
+        android:background="@drawable/decor_minimize_button_dark"
+        android:duplicateParentState="true"/>
+    <Button
         android:id="@+id/maximize_window"
         android:layout_width="32dp"
         android:layout_height="32dp"
@@ -42,4 +53,3 @@
         android:background="@drawable/decor_close_button_dark"
         android:duplicateParentState="true"/>
 </com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
-
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index 2d50d3f..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.pip.tv.TvPipMenuActionButton
-                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.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" />
+                <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.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" />
+                <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.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" />
+                <!-- 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,6 +153,7 @@
         android:layout_margin="@dimen/pip_menu_outer_space_frame"
         android:background="@drawable/tv_pip_menu_border"/>
 
+    <!-- Move menu -->
     <com.android.wm.shell.pip.tv.TvPipMenuActionButton
         android:id="@+id/tv_pip_menu_done_button"
         android:layout_width="wrap_content"
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
deleted file mode 100644
index db96d8d..0000000
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2020 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<!-- Layout for TvPipMenuActionButton -->
-<FrameLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/button"
-        android:layout_width="@dimen/pip_menu_button_size"
-        android:layout_height="@dimen/pip_menu_button_size"
-        android:padding="@dimen/pip_menu_button_margin"
-        android:stateListAnimator="@animator/tv_pip_menu_action_button_animator"
-        android:focusable="true">
-
-        <View android:id="@+id/background"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:layout_gravity="center"
-              android:duplicateParentState="true"
-              android:background="@drawable/tv_pip_button_bg"/>
-
-        <ImageView android:id="@+id/icon"
-                   android:layout_width="@dimen/pip_menu_icon_size"
-                   android:layout_height="@dimen/pip_menu_icon_size"
-                   android:layout_gravity="center"
-                   android:duplicateParentState="true"
-                   android:tint="@color/tv_pip_menu_icon" />
-</FrameLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
new file mode 100644
index 0000000..c4dbd39
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
@@ -0,0 +1,40 @@
+<?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.
+  -->
+<!-- Layout for TvWindowMenuActionButton -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/button"
+    android:layout_width="@dimen/tv_window_menu_button_size"
+    android:layout_height="@dimen/tv_window_menu_button_size"
+    android:padding="@dimen/tv_window_menu_button_margin"
+    android:stateListAnimator="@animator/tv_window_menu_action_button_animator"
+    android:focusable="true">
+
+    <View android:id="@+id/background"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center"
+        android:duplicateParentState="true"
+        android:background="@drawable/tv_window_button_bg"/>
+
+    <ImageView android:id="@+id/icon"
+        android:layout_width="@dimen/tv_window_menu_icon_size"
+        android:layout_height="@dimen/tv_window_menu_icon_size"
+        android:layout_gravity="center"
+        android:duplicateParentState="true"
+        android:tint="@color/tv_window_menu_icon" />
+</FrameLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values-es/strings_tv.xml b/libs/WindowManager/Shell/res/values-es/strings_tv.xml
index 7993e03..75db421 100644
--- a/libs/WindowManager/Shell/res/values-es/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings_tv.xml
@@ -23,7 +23,7 @@
     <string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string>
     <string name="pip_move" msgid="158770205886688553">"Mover"</string>
     <string name="pip_expand" msgid="1051966011679297308">"Mostrar"</string>
-    <string name="pip_collapse" msgid="3903295106641385962">"Ocultar"</string>
+    <string name="pip_collapse" msgid="3903295106641385962">"Contraer"</string>
     <string name="pip_edu_text" msgid="3672999496647508701">" Pulsa dos veces "<annotation icon="home_icon">"INICIO"</annotation>" para ver los controles"</string>
     <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menú de imagen en imagen."</string>
     <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mover hacia la izquierda"</string>
diff --git a/libs/WindowManager/Shell/res/values-television/dimen.xml b/libs/WindowManager/Shell/res/values-television/dimen.xml
index 14e89f8..376cc4f 100644
--- a/libs/WindowManager/Shell/res/values-television/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-television/dimen.xml
@@ -21,4 +21,7 @@
 
     <!-- Padding between PIP and keep clear areas that caused it to move. -->
     <dimen name="pip_keep_clear_area_padding">16dp</dimen>
+
+    <!-- The corner radius for PiP window. -->
+    <dimen name="pip_corner_radius">0dp</dimen>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 02e726f..b45b9ec 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -15,20 +15,20 @@
     limitations under the License.
 -->
 <resources>
-    <!-- The dimensions to user for picture-in-picture action buttons. -->
-    <dimen name="pip_menu_button_size">48dp</dimen>
-    <dimen name="pip_menu_button_radius">20dp</dimen>
-    <dimen name="pip_menu_icon_size">20dp</dimen>
-    <dimen name="pip_menu_button_margin">4dp</dimen>
-    <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
-    <dimen name="pip_menu_border_width">4dp</dimen>
-    <integer name="pip_menu_fade_animation_duration">500</integer>
+    <!-- The dimensions to use for tv window menu action buttons. -->
+    <dimen name="tv_window_menu_button_size">48dp</dimen>
+    <dimen name="tv_window_menu_button_radius">20dp</dimen>
+    <dimen name="tv_window_menu_icon_size">20dp</dimen>
+    <dimen name="tv_window_menu_button_margin">4dp</dimen>
+    <integer name="tv_window_menu_fade_animation_duration">500</integer>
     <!-- The pip menu front border corner radius is 2dp smaller than
         the background corner radius to hide the background from
         showing through. -->
     <dimen name="pip_menu_border_corner_radius">4dp</dimen>
     <dimen name="pip_menu_background_corner_radius">6dp</dimen>
+    <dimen name="pip_menu_border_width">4dp</dimen>
     <dimen name="pip_menu_outer_space">24dp</dimen>
+    <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
 
     <!-- outer space minus border width -->
     <dimen name="pip_menu_outer_space_frame">20dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml
index fa90fe3..e6933ca 100644
--- a/libs/WindowManager/Shell/res/values/colors_tv.xml
+++ b/libs/WindowManager/Shell/res/values/colors_tv.xml
@@ -15,14 +15,17 @@
   ~ limitations under the License.
   -->
 <resources>
-    <color name="tv_pip_menu_icon_focused">#0E0E0F</color>
-    <color name="tv_pip_menu_icon_unfocused">#F8F9FA</color>
-    <color name="tv_pip_menu_icon_disabled">#80868B</color>
-    <color name="tv_pip_menu_close_icon_bg_focused">#D93025</color>
-    <color name="tv_pip_menu_close_icon_bg_unfocused">#D69F261F</color>
-    <color name="tv_pip_menu_icon_bg_focused">#E8EAED</color>
-    <color name="tv_pip_menu_icon_bg_unfocused">#990E0E0F</color>
+    <color name="tv_window_menu_icon_focused">#0E0E0F</color>
+    <color name="tv_window_menu_icon_unfocused">#F8F9FA</color>
+
+    <color name="tv_window_menu_icon_disabled">#80868B</color>
+    <color name="tv_window_menu_close_icon_bg_focused">#D93025</color>
+    <color name="tv_window_menu_close_icon_bg_unfocused">#D69F261F</color>
+    <color name="tv_window_menu_icon_bg_focused">#E8EAED</color>
+    <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/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 2a38766..679bfb9 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -197,6 +197,8 @@
     <!-- Freeform window caption strings -->
     <!-- Accessibility text for the maximize window button [CHAR LIMIT=NONE] -->
     <string name="maximize_button_text">Maximize</string>
+     <!-- Accessibility text for the minimize window button [CHAR LIMIT=NONE] -->
+     <string name="minimize_button_text">Minimize</string>
     <!-- Accessibility text for the close window button [CHAR LIMIT=NONE] -->
     <string name="close_button_text">Close</string>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
index 764e650..b085b73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
@@ -16,14 +16,20 @@
 
 package com.android.wm.shell;
 
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+
+import android.app.WindowConfiguration;
 import android.util.SparseArray;
 import android.view.SurfaceControl;
 import android.window.DisplayAreaAppearedInfo;
 import android.window.DisplayAreaInfo;
 import android.window.DisplayAreaOrganizer;
+import android.window.WindowContainerTransaction;
 
 import androidx.annotation.NonNull;
 
+import com.android.internal.protolog.common.ProtoLog;
+
 import java.io.PrintWriter;
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -102,10 +108,44 @@
         mDisplayAreasInfo.put(displayId, displayAreaInfo);
     }
 
+    /**
+     * Create a {@link WindowContainerTransaction} to update display windowing mode.
+     *
+     * @param displayId display id to update windowing mode for
+     * @param windowingMode target {@link WindowConfiguration.WindowingMode}
+     * @return {@link WindowContainerTransaction} with pending operation to set windowing mode
+     */
+    public WindowContainerTransaction prepareWindowingModeChange(int displayId,
+            @WindowConfiguration.WindowingMode int windowingMode) {
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        DisplayAreaInfo displayAreaInfo = mDisplayAreasInfo.get(displayId);
+        if (displayAreaInfo == null) {
+            ProtoLog.e(WM_SHELL_DESKTOP_MODE,
+                    "unable to update windowing mode for display %d display not found", displayId);
+            return wct;
+        }
+
+        ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+                "setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
+                displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
+                windowingMode);
+
+        wct.setWindowingMode(displayAreaInfo.token, windowingMode);
+        return wct;
+    }
+
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         final String childPrefix = innerPrefix + "  ";
         pw.println(prefix + this);
+
+        for (int i = 0; i < mDisplayAreasInfo.size(); i++) {
+            int displayId = mDisplayAreasInfo.keyAt(i);
+            DisplayAreaInfo displayAreaInfo = mDisplayAreasInfo.get(displayId);
+            int windowingMode =
+                    displayAreaInfo.configuration.windowConfiguration.getWindowingMode();
+            pw.println(innerPrefix + "# displayId=" + displayId + " wmMode=" + windowingMode);
+        }
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 6ae0f9b..d5d4935 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -22,6 +22,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
 
@@ -46,6 +47,7 @@
 import android.window.StartingWindowRemovalInfo;
 import android.window.TaskAppearedInfo;
 import android.window.TaskOrganizer;
+import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
@@ -690,6 +692,49 @@
         taskListener.reparentChildSurfaceToTask(taskId, sc, t);
     }
 
+    /**
+     * Create a {@link WindowContainerTransaction} to clear task bounds.
+     *
+     * @param displayId display id for tasks that will have bounds cleared
+     * @return {@link WindowContainerTransaction} with pending operations to clear bounds
+     */
+    public WindowContainerTransaction prepareClearBoundsForTasks(int displayId) {
+        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks: displayId=%d", displayId);
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        for (int i = 0; i < mTasks.size(); i++) {
+            RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
+            if (taskInfo.displayId == displayId) {
+                ProtoLog.d(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
+                        taskInfo.token, taskInfo);
+                wct.setBounds(taskInfo.token, null);
+            }
+        }
+        return wct;
+    }
+
+    /**
+     * Create a {@link WindowContainerTransaction} to clear task level freeform setting.
+     *
+     * @param displayId display id for tasks that will have windowing mode reset to {@link
+     *                  WindowConfiguration#WINDOWING_MODE_UNDEFINED}
+     * @return {@link WindowContainerTransaction} with pending operations to clear windowing mode
+     */
+    public WindowContainerTransaction prepareClearFreeformForTasks(int displayId) {
+        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks: displayId=%d", displayId);
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        for (int i = 0; i < mTasks.size(); i++) {
+            RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
+            if (taskInfo.displayId == displayId
+                    && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+                ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+                        "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
+                        taskInfo);
+                wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+            }
+        }
+        return wct;
+    }
+
     private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
             int event) {
         ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
@@ -816,7 +861,14 @@
                 final int key = mTasks.keyAt(i);
                 final TaskAppearedInfo info = mTasks.valueAt(i);
                 final TaskListener listener = getTaskListener(info.getTaskInfo());
-                pw.println(innerPrefix + "#" + i + " task=" + key + " listener=" + listener);
+                final int windowingMode = info.getTaskInfo().getWindowingMode();
+                String pkg = "";
+                if (info.getTaskInfo().baseActivity != null) {
+                    pkg = info.getTaskInfo().baseActivity.getPackageName();
+                }
+                Rect bounds = info.getTaskInfo().getConfiguration().windowConfiguration.getBounds();
+                pw.println(innerPrefix + "#" + i + " task=" + key + " listener=" + listener
+                        + " wmMode=" + windowingMode + " pkg=" + pkg + " bounds=" + bounds);
             }
 
             pw.println();
@@ -826,6 +878,7 @@
                 final TaskListener listener = mLaunchCookieToListener.valueAt(i);
                 pw.println(innerPrefix + "#" + i + " cookie=" + key + " listener=" + listener);
             }
+
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index d28a68a..a8764e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -54,7 +54,10 @@
 
     /** Callback for listening task state. */
     public interface Listener {
-        /** Called when the container is ready for launching activities. */
+        /**
+         * Only called once when the surface has been created & the container is ready for
+         * launching activities.
+         */
         default void onInitialized() {}
 
         /** Called when the container can no longer launch activities. */
@@ -80,12 +83,13 @@
     private final SyncTransactionQueue mSyncQueue;
     private final TaskViewTransitions mTaskViewTransitions;
 
-    private ActivityManager.RunningTaskInfo mTaskInfo;
+    protected ActivityManager.RunningTaskInfo mTaskInfo;
     private WindowContainerToken mTaskToken;
     private SurfaceControl mTaskLeash;
     private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
     private boolean mSurfaceCreated;
     private boolean mIsInitialized;
+    private boolean mNotifiedForInitialized;
     private Listener mListener;
     private Executor mListenerExecutor;
     private Region mObscuredTouchRegion;
@@ -110,6 +114,13 @@
         mGuard.open("release");
     }
 
+    /**
+     * @return {@code True} when the TaskView's surface has been created, {@code False} otherwise.
+     */
+    public boolean isInitialized() {
+        return mIsInitialized;
+    }
+
     /** Until all users are converted, we may have mixed-use (eg. Car). */
     private boolean isUsingShellTransitions() {
         return mTaskViewTransitions != null && Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -269,11 +280,17 @@
             resetTaskInfo();
         });
         mGuard.close();
-        if (mListener != null && mIsInitialized) {
+        mIsInitialized = false;
+        notifyReleased();
+    }
+
+    /** Called when the {@link TaskView} has been released. */
+    protected void notifyReleased() {
+        if (mListener != null && mNotifiedForInitialized) {
             mListenerExecutor.execute(() -> {
                 mListener.onReleased();
             });
-            mIsInitialized = false;
+            mNotifiedForInitialized = false;
         }
     }
 
@@ -407,12 +424,8 @@
     @Override
     public void surfaceCreated(SurfaceHolder holder) {
         mSurfaceCreated = true;
-        if (mListener != null && !mIsInitialized) {
-            mIsInitialized = true;
-            mListenerExecutor.execute(() -> {
-                mListener.onInitialized();
-            });
-        }
+        mIsInitialized = true;
+        notifyInitialized();
         mShellExecutor.execute(() -> {
             if (mTaskToken == null) {
                 // Nothing to update, task is not yet available
@@ -430,6 +443,16 @@
         });
     }
 
+    /** Called when the {@link TaskView} is initialized. */
+    protected void notifyInitialized() {
+        if (mListener != null && !mNotifiedForInitialized) {
+            mNotifiedForInitialized = true;
+            mListenerExecutor.execute(() -> {
+                mListener.onInitialized();
+            });
+        }
+    }
+
     @Override
     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
         if (mTaskToken == null) {
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 8771ceb..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);
@@ -1094,13 +1088,16 @@
     }
 
     void updateNotNotifyingEntry(Bubble b, BubbleEntry entry, boolean showInShade) {
+        boolean showInShadeBefore = b.showInShade();
         boolean isBubbleSelected = Objects.equals(b, mBubbleData.getSelectedBubble());
         boolean isBubbleExpandedAndSelected = isStackExpanded() && isBubbleSelected;
         b.setEntry(entry);
         boolean suppress = isBubbleExpandedAndSelected || !showInShade || !b.showInShade();
         b.setSuppressNotification(suppress);
         b.setShowDot(!isBubbleExpandedAndSelected);
-        mImpl.mCachedState.updateBubbleSuppressedState(b);
+        if (showInShadeBefore != b.showInShade()) {
+            mImpl.mCachedState.updateBubbleSuppressedState(b);
+        }
     }
 
     @VisibleForTesting
@@ -1516,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);
     }
 
     /**
@@ -1708,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();
@@ -1758,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) {
@@ -1879,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/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index d5875c0..e270edb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -221,8 +221,7 @@
             }
             final Display display = mDisplayController.getDisplay(mDisplayId);
             SurfaceControlViewHost viewRoot =
-                    new SurfaceControlViewHost(
-                            view.getContext(), display, wwm, true /* useSfChoreographer */);
+                    new SurfaceControlViewHost(view.getContext(), display, wwm);
             attrs.flags |= FLAG_HARDWARE_ACCELERATED;
             viewRoot.setView(view, attrs);
             mViewRoots.put(view, viewRoot);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
similarity index 76%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
index a09aab6..572e333 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.pip.tv;
+package com.android.wm.shell.common;
 
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -28,33 +28,32 @@
 import com.android.wm.shell.R;
 
 /**
- * A View that represents Pip Menu action button, such as "Fullscreen" and "Close" as well custom
- * (provided by the application in Pip) and media buttons.
+ * A common action button for TV window menu layouts.
  */
-public class TvPipMenuActionButton extends RelativeLayout implements View.OnClickListener {
+public class TvWindowMenuActionButton extends RelativeLayout implements View.OnClickListener {
     private final ImageView mIconImageView;
     private final View mButtonBackgroundView;
     private final View mButtonView;
     private OnClickListener mOnClickListener;
 
-    public TvPipMenuActionButton(Context context) {
+    public TvWindowMenuActionButton(Context context) {
         this(context, null, 0, 0);
     }
 
-    public TvPipMenuActionButton(Context context, AttributeSet attrs) {
+    public TvWindowMenuActionButton(Context context, AttributeSet attrs) {
         this(context, attrs, 0, 0);
     }
 
-    public TvPipMenuActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
+    public TvWindowMenuActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
         this(context, attrs, defStyleAttr, 0);
     }
 
-    public TvPipMenuActionButton(
+    public TvWindowMenuActionButton(
             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         final LayoutInflater inflater = (LayoutInflater) getContext()
                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        inflater.inflate(R.layout.tv_pip_menu_action_button, this);
+        inflater.inflate(R.layout.tv_window_menu_action_button, this);
 
         mIconImageView = findViewById(R.id.icon);
         mButtonView = findViewById(R.id.button);
@@ -129,20 +128,27 @@
         return mButtonView.isEnabled();
     }
 
-    void setIsCustomCloseAction(boolean isCustomCloseAction) {
+    /**
+     * Marks this button as a custom close action button.
+     * This changes the style of the action button to highlight that this action finishes the
+     * Picture-in-Picture activity.
+     *
+     * @param isCustomCloseAction sets or unsets this button as a custom close action button.
+     */
+    public void setIsCustomCloseAction(boolean isCustomCloseAction) {
         mIconImageView.setImageTintList(
                 getResources().getColorStateList(
-                        isCustomCloseAction ? R.color.tv_pip_menu_close_icon
-                                : R.color.tv_pip_menu_icon));
+                        isCustomCloseAction ? R.color.tv_window_menu_close_icon
+                                : R.color.tv_window_menu_icon));
         mButtonBackgroundView.setBackgroundTintList(getResources()
-                .getColorStateList(isCustomCloseAction ? R.color.tv_pip_menu_close_icon_bg
-                        : R.color.tv_pip_menu_icon_bg));
+                .getColorStateList(isCustomCloseAction ? R.color.tv_window_menu_close_icon_bg
+                        : R.color.tv_window_menu_icon_bg));
     }
 
     @Override
     public String toString() {
         if (mButtonView.getContentDescription() == null) {
-            return TvPipMenuActionButton.class.getSimpleName();
+            return TvWindowMenuActionButton.class.getSimpleName();
         }
         return mButtonView.getContentDescription().toString();
     }
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/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
index 35a309a..0cc545a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
@@ -20,7 +20,6 @@
 import static android.os.Process.THREAD_PRIORITY_DISPLAY;
 import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
 
-import android.animation.AnimationHandler;
 import android.content.Context;
 import android.os.Build;
 import android.os.Handler;
@@ -31,11 +30,9 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.HandlerExecutor;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
 import com.android.wm.shell.common.annotations.ExternalMainThread;
 import com.android.wm.shell.common.annotations.ShellAnimationThread;
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
@@ -195,30 +192,6 @@
     }
 
     /**
-     * Provide a Shell main-thread AnimationHandler.  The AnimationHandler can be set on
-     * {@link android.animation.ValueAnimator}s and will ensure that the animation will run on
-     * the Shell main-thread with the SF vsync.
-     */
-    @WMSingleton
-    @Provides
-    @ChoreographerSfVsync
-    public static AnimationHandler provideShellMainExecutorSfVsyncAnimationHandler(
-            @ShellMainThread ShellExecutor mainExecutor) {
-        try {
-            AnimationHandler handler = new AnimationHandler();
-            mainExecutor.executeBlocking(() -> {
-                // This is called on the animation thread since it calls
-                // Choreographer.getSfInstance() which returns a thread-local Choreographer instance
-                // that uses the SF vsync
-                handler.setProvider(new SfVsyncFrameCallbackProvider());
-            });
-            return handler;
-        } catch (InterruptedException e) {
-            throw new RuntimeException("Failed to initialize SfVsync animation handler in 1s", e);
-        }
-    }
-
-    /**
      * Provides a Shell background thread Handler for low priority background tasks.
      */
     @WMSingleton
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 2ca9c3b..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
@@ -27,6 +27,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.TaskViewTransitions;
@@ -48,10 +49,13 @@
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.desktopmode.DesktopModeConstants;
+import com.android.wm.shell.desktopmode.DesktopModeController;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 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;
@@ -142,6 +146,7 @@
     @Provides
     static BubbleController provideBubbleController(Context context,
             ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
             ShellController shellController,
             BubbleData data,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -162,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,
@@ -229,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
@@ -236,7 +242,7 @@
                 ? shellInit
                 : null;
         return new FreeformTaskTransitionHandler(init, transitions,
-                windowDecorViewModel, freeformTaskListener);
+                windowDecorViewModel, fullscreenTaskListener, freeformTaskListener);
     }
 
     //
@@ -574,6 +580,27 @@
     }
 
     //
+    // Desktop mode (optional feature)
+    //
+
+    @WMSingleton
+    @Provides
+    static Optional<DesktopModeController> provideDesktopModeController(
+            Context context, ShellInit shellInit,
+            ShellTaskOrganizer shellTaskOrganizer,
+            RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
+            @ShellMainThread Handler mainHandler
+    ) {
+        if (DesktopModeConstants.IS_FEATURE_ENABLED) {
+            return Optional.of(new DesktopModeController(context, shellInit, shellTaskOrganizer,
+                    rootDisplayAreaOrganizer,
+                    mainHandler));
+        } else {
+            return Optional.empty();
+        }
+    }
+
+    //
     // Misc
     //
 
@@ -583,7 +610,8 @@
     @ShellCreateTriggerOverride
     @Provides
     static Object provideIndependentShellComponentsToCreate(
-            SplitscreenPipMixedHandler splitscreenPipMixedHandler) {
+            SplitscreenPipMixedHandler splitscreenPipMixedHandler,
+            Optional<DesktopModeController> desktopModeController) {
         return new Object();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeConstants.java
similarity index 61%
copy from packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
copy to libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeConstants.java
index 7c9df10..e62a63a 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeConstants.java
@@ -14,10 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.systemui.common.data.model
+package com.android.wm.shell.desktopmode;
 
-/** Models a two-dimensional position */
-data class Position(
-    val x: Int,
-    val y: Int,
-)
+import android.os.SystemProperties;
+
+/**
+ * Constants for desktop mode feature
+ */
+public class DesktopModeConstants {
+
+    /**
+     * Flag to indicate whether desktop mode is available on the device
+     */
+    public static final boolean IS_FEATURE_ENABLED = SystemProperties.getBoolean(
+            "persist.wm.debug.desktop_mode", false);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
new file mode 100644
index 0000000..5849e16
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
+
+/**
+ * Handles windowing changes when desktop mode system setting changes
+ */
+public class DesktopModeController {
+
+    private final Context mContext;
+    private final ShellTaskOrganizer mShellTaskOrganizer;
+    private final RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
+    private final SettingsObserver mSettingsObserver;
+
+    public DesktopModeController(Context context, ShellInit shellInit,
+            ShellTaskOrganizer shellTaskOrganizer,
+            RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
+            @ShellMainThread Handler mainHandler) {
+        mContext = context;
+        mShellTaskOrganizer = shellTaskOrganizer;
+        mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
+        mSettingsObserver = new SettingsObserver(mContext, mainHandler);
+        shellInit.addInitCallback(this::onInit, this);
+    }
+
+    private void onInit() {
+        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
+        mSettingsObserver.observe();
+    }
+
+    @VisibleForTesting
+    void updateDesktopModeEnabled(boolean enabled) {
+        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeState: enabled=%s", enabled);
+
+        int displayId = mContext.getDisplayId();
+
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        // Reset freeform windowing mode that is set per task level (tasks should inherit
+        // container value)
+        wct.merge(mShellTaskOrganizer.prepareClearFreeformForTasks(displayId), true /* transfer */);
+        int targetWindowingMode;
+        if (enabled) {
+            targetWindowingMode = WINDOWING_MODE_FREEFORM;
+        } else {
+            targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
+            // Clear any resized bounds
+            wct.merge(mShellTaskOrganizer.prepareClearBoundsForTasks(displayId),
+                    true /* transfer */);
+        }
+        wct.merge(mRootDisplayAreaOrganizer.prepareWindowingModeChange(displayId,
+                targetWindowingMode), true /* transfer */);
+        mRootDisplayAreaOrganizer.applyTransaction(wct);
+    }
+
+    /**
+     * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
+     */
+    private final class SettingsObserver extends ContentObserver {
+
+        private final Uri mDesktopModeSetting = Settings.System.getUriFor(
+                Settings.System.DESKTOP_MODE);
+
+        private final Context mContext;
+
+        SettingsObserver(Context context, Handler handler) {
+            super(handler);
+            mContext = context;
+        }
+
+        public void observe() {
+            // TODO(b/242867463): listen for setting change for all users
+            mContext.getContentResolver().registerContentObserver(mDesktopModeSetting,
+                    false /* notifyForDescendants */, this /* observer */, UserHandle.USER_CURRENT);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, @Nullable Uri uri) {
+            if (mDesktopModeSetting.equals(uri)) {
+                ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Received update for desktop mode setting");
+                desktopModeSettingChanged();
+            }
+        }
+
+        private void desktopModeSettingChanged() {
+            boolean enabled = isDesktopModeEnabled();
+            updateDesktopModeEnabled(enabled);
+        }
+
+        private boolean isDesktopModeEnabled() {
+            try {
+                int result = Settings.System.getIntForUser(mContext.getContentResolver(),
+                        Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
+                ProtoLog.d(WM_SHELL_DESKTOP_MODE, "isDesktopModeEnabled=%s", result);
+                return result != 0;
+            } catch (Settings.SettingNotFoundException e) {
+                ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
+                return false;
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index ff3c083..497a6f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -105,6 +105,10 @@
                 MATCH_PARENT));
         ((LayoutParams) mDropZoneView1.getLayoutParams()).weight = 1;
         ((LayoutParams) mDropZoneView2.getLayoutParams()).weight = 1;
+        int orientation = getResources().getConfiguration().orientation;
+        setOrientation(orientation == Configuration.ORIENTATION_LANDSCAPE
+                ? LinearLayout.HORIZONTAL
+                : LinearLayout.VERTICAL);
         updateContainerMargins(getResources().getConfiguration().orientation);
     }
 
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 af205ed..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) {
@@ -92,6 +96,12 @@
     }
 
     @Override
+    public void startMinimizedModeTransition(WindowContainerTransaction wct) {
+        final int type = WindowManager.TRANSIT_TO_BACK;
+        mPendingTransitionTokens.add(mTransitions.startTransition(type, wct, this));
+    }
+
+    @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startT,
             @NonNull SurfaceControl.Transaction finishT,
@@ -121,6 +131,8 @@
                             transition, info.getType(), change, startT, finishT);
                     break;
                 case WindowManager.TRANSIT_TO_BACK:
+                    transitionHandled |= startMinimizeTransition(transition);
+                    break;
                 case WindowManager.TRANSIT_TO_FRONT:
                     break;
             }
@@ -142,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;
@@ -156,19 +174,33 @@
             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;
     }
 
+    private boolean startMinimizeTransition(IBinder transition) {
+        if (!mPendingTransitionTokens.contains(transition)) {
+            return false;
+        }
+        return true;
+    }
+
     private boolean startChangeTransition(
             IBinder transition,
             int type,
@@ -182,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;
     }
 
@@ -210,7 +247,7 @@
             releaseWindowDecor(windowDecor);
         }
         mFreeformTaskListener.onTaskTransitionFinished();
-        // TODO(b/235638450): Dispatch it to fullscreen task listener.
+        mFullscreenTaskListener.onTaskTransitionFinished();
         finishCallback.onTransitionFinished(null, null);
     }
 
@@ -243,4 +280,5 @@
                 return null;
         }
     }
+
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
index 25eaa0e..c947cf1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
@@ -29,6 +29,15 @@
      *
      * @param targetWindowingMode the target windowing mode
      * @param wct the {@link WindowContainerTransaction} that changes the windowing mode
+     *
      */
     void startWindowingModeTransition(int targetWindowingMode, WindowContainerTransaction wct);
-}
+
+    /**
+     * Starts window minimization transition
+     *
+     * @param wct the {@link WindowContainerTransaction} that changes the windowing mode
+     *
+     */
+    void startMinimizedModeTransition(WindowContainerTransaction wct);
+}
\ No newline at end of file
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/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index 0e32663..7096a64 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -111,9 +111,6 @@
         private final TaskSnapshot mSnapshot;
         private final Rect mSourceRectHint;
 
-        private float mTaskSnapshotScaleX;
-        private float mTaskSnapshotScaleY;
-
         public PipSnapshotOverlay(TaskSnapshot snapshot, Rect sourceRectHint) {
             mSnapshot = snapshot;
             mSourceRectHint = new Rect(sourceRectHint);
@@ -125,16 +122,16 @@
 
         @Override
         public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
-            mTaskSnapshotScaleX = (float) mSnapshot.getTaskSize().x
+            final float taskSnapshotScaleX = (float) mSnapshot.getTaskSize().x
                     / mSnapshot.getHardwareBuffer().getWidth();
-            mTaskSnapshotScaleY = (float) mSnapshot.getTaskSize().y
+            final float taskSnapshotScaleY = (float) mSnapshot.getTaskSize().y
                     / mSnapshot.getHardwareBuffer().getHeight();
             tx.show(mLeash);
             tx.setLayer(mLeash, Integer.MAX_VALUE);
             tx.setBuffer(mLeash, mSnapshot.getHardwareBuffer());
             // Relocate the content to parentLeash's coordinates.
             tx.setPosition(mLeash, -mSourceRectHint.left, -mSourceRectHint.top);
-            tx.setScale(mLeash, mTaskSnapshotScaleX, mTaskSnapshotScaleY);
+            tx.setScale(mLeash, taskSnapshotScaleX, taskSnapshotScaleY);
             tx.reparent(mLeash, parentLeash);
             tx.apply();
         }
@@ -146,20 +143,6 @@
 
         @Override
         public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
-            // Work around to make sure the snapshot overlay is aligned with PiP window before
-            // the atomicTx is committed along with the final WindowContainerTransaction.
-            final SurfaceControl.Transaction nonAtomicTx = new SurfaceControl.Transaction();
-            final float scaleX = (float) destinationBounds.width()
-                    / mSourceRectHint.width();
-            final float scaleY = (float) destinationBounds.height()
-                    / mSourceRectHint.height();
-            final float scale = Math.max(
-                    scaleX * mTaskSnapshotScaleX, scaleY * mTaskSnapshotScaleY);
-            nonAtomicTx.setScale(mLeash, scale, scale);
-            nonAtomicTx.setPosition(mLeash,
-                    -scale * mSourceRectHint.left / mTaskSnapshotScaleX,
-                    -scale * mSourceRectHint.top / mTaskSnapshotScaleY);
-            nonAtomicTx.apply();
             atomicTx.remove(mLeash);
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 504dc02..a0a8f9f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -31,8 +31,6 @@
 import android.util.Size;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
-import android.view.SyncRtSurfaceTransactionApplier;
-import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.WindowManagerGlobal;
 
 import com.android.internal.protolog.common.ProtoLog;
@@ -42,6 +40,7 @@
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipMediaController.ActionListener;
 import com.android.wm.shell.pip.PipMenuController;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip.PipUiEventLogger;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -115,6 +114,10 @@
     private final ShellExecutor mMainExecutor;
     private final Handler mMainHandler;
 
+    private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+            mSurfaceControlTransactionFactory;
+    private final float[] mTmpTransform = new float[9];
+
     private final ArrayList<Listener> mListeners = new ArrayList<>();
     private final SystemWindows mSystemWindows;
     private final Optional<SplitScreenController> mSplitScreenController;
@@ -124,7 +127,6 @@
     private RemoteAction mCloseAction;
     private List<RemoteAction> mMediaActions;
 
-    private SyncRtSurfaceTransactionApplier mApplier;
     private int mMenuState;
 
     private PipMenuView mPipMenuView;
@@ -150,6 +152,9 @@
         mMainHandler = mainHandler;
         mSplitScreenController = splitScreenOptional;
         mPipUiEventLogger = pipUiEventLogger;
+
+        mSurfaceControlTransactionFactory =
+                new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
     }
 
     public boolean isMenuVisible() {
@@ -194,7 +199,6 @@
             return;
         }
 
-        mApplier = null;
         mSystemWindows.removeView(mPipMenuView);
         mPipMenuView = null;
     }
@@ -289,7 +293,7 @@
                     willResizeMenu, withDelay, showResizeHandle, Debug.getCallers(5, "    "));
         }
 
-        if (!maybeCreateSyncApplier()) {
+        if (!checkPipMenuState()) {
             return;
         }
 
@@ -312,7 +316,7 @@
             return;
         }
 
-        if (!maybeCreateSyncApplier()) {
+        if (!checkPipMenuState()) {
             return;
         }
 
@@ -328,18 +332,15 @@
         mTmpSourceRectF.set(mTmpSourceBounds);
         mTmpDestinationRectF.set(destinationBounds);
         mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
-        SurfaceControl surfaceControl = getSurfaceControl();
-        SurfaceParams params = new SurfaceParams.Builder(surfaceControl)
-                .withMatrix(mMoveTransform)
-                .build();
+        final SurfaceControl surfaceControl = getSurfaceControl();
+        final SurfaceControl.Transaction menuTx =
+                mSurfaceControlTransactionFactory.getTransaction();
+        menuTx.setMatrix(surfaceControl, mMoveTransform, mTmpTransform);
         if (pipLeash != null && t != null) {
-            SurfaceParams pipParams = new SurfaceParams.Builder(pipLeash)
-                    .withMergeTransaction(t)
-                    .build();
-            mApplier.scheduleApply(params, pipParams);
-        } else {
-            mApplier.scheduleApply(params);
+            // Merge the two transactions, vsyncId has been set on menuTx.
+            menuTx.merge(t);
         }
+        menuTx.apply();
     }
 
     /**
@@ -353,36 +354,29 @@
             return;
         }
 
-        if (!maybeCreateSyncApplier()) {
+        if (!checkPipMenuState()) {
             return;
         }
 
-        SurfaceControl surfaceControl = getSurfaceControl();
-        SurfaceParams params = new SurfaceParams.Builder(surfaceControl)
-                .withWindowCrop(destinationBounds)
-                .build();
+        final SurfaceControl surfaceControl = getSurfaceControl();
+        final SurfaceControl.Transaction menuTx =
+                mSurfaceControlTransactionFactory.getTransaction();
+        menuTx.setCrop(surfaceControl, destinationBounds);
         if (pipLeash != null && t != null) {
-            SurfaceParams pipParams = new SurfaceParams.Builder(pipLeash)
-                    .withMergeTransaction(t)
-                    .build();
-            mApplier.scheduleApply(params, pipParams);
-        } else {
-            mApplier.scheduleApply(params);
+            // Merge the two transactions, vsyncId has been set on menuTx.
+            menuTx.merge(t);
         }
+        menuTx.apply();
     }
 
-    private boolean maybeCreateSyncApplier() {
+    private boolean checkPipMenuState() {
         if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: Not going to move PiP, either menu or its parent is not created.", TAG);
             return false;
         }
 
-        if (mApplier == null) {
-            mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView);
-        }
-
-        return mApplier != null;
+        return true;
     }
 
     /**
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/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index a0e2201..7619646 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -288,8 +288,10 @@
 
         if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
             mTargetViewContainer.getViewTreeObserver().addOnPreDrawListener(this);
-            mTargetViewContainer.show();
         }
+        // always invoke show, since the target might still be VISIBLE while playing hide animation,
+        // so we want to ensure it will show back again
+        mTargetViewContainer.show();
     }
 
     /** Animates the magnetic dismiss target out and then sets it to GONE. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
index 0f3ff36..8e3376f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
@@ -146,11 +146,8 @@
                     "%s: Failed to create input consumer, %s", TAG, e);
         }
         mMainExecutor.execute(() -> {
-            // Choreographer.getSfInstance() must be called on the thread that the input event
-            // receiver should be receiving events
-            // TODO(b/222697646): remove getSfInstance usage and use vsyncId for transactions
             mInputEventReceiver = new InputEventReceiver(inputChannel,
-                Looper.myLooper(), Choreographer.getSfInstance());
+                Looper.myLooper(), Choreographer.getInstance());
             if (mRegistrationListener != null) {
                 mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
             }
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/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index abf1a95..89d85e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -625,8 +625,7 @@
 
     class PipResizeInputEventReceiver extends BatchedInputEventReceiver {
         PipResizeInputEventReceiver(InputChannel channel, Looper looper) {
-            // TODO(b/222697646): remove getSfInstance usage and use vsyncId for transactions
-            super(channel, looper, Choreographer.getSfInstance());
+            super(channel, looper, Choreographer.getInstance());
         }
 
         public void onInputEvent(InputEvent event) {
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 57d3a44..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
@@ -56,6 +56,7 @@
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
+import com.android.wm.shell.common.TvWindowMenuActionButton;
 import com.android.wm.shell.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
@@ -79,7 +80,7 @@
 
     private final LinearLayout mActionButtonsContainer;
     private final View mMenuFrameView;
-    private final List<TvPipMenuActionButton> mAdditionalButtons = new ArrayList<>();
+    private final List<TvWindowMenuActionButton> mAdditionalButtons = new ArrayList<>();
     private final View mPipFrameView;
     private final View mPipView;
     private final TextView mEduTextView;
@@ -94,7 +95,10 @@
     private final ImageView mArrowRight;
     private final ImageView mArrowDown;
     private final ImageView mArrowLeft;
-    private final TvPipMenuActionButton mA11yDoneButton;
+    private final TvWindowMenuActionButton mA11yDoneButton;
+
+    private final View mPipBackground;
+    private final View mDimLayer;
 
     private final ScrollView mScrollView;
     private final HorizontalScrollView mHorizontalScrollView;
@@ -104,8 +108,8 @@
     private boolean mMoveMenuIsVisible;
     private boolean mButtonMenuIsVisible;
 
-    private final TvPipMenuActionButton mExpandButton;
-    private final TvPipMenuActionButton mCloseButton;
+    private final TvWindowMenuActionButton mExpandButton;
+    private final TvWindowMenuActionButton mCloseButton;
 
     private boolean mSwitchingOrientation;
 
@@ -147,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);
 
@@ -166,7 +173,7 @@
         mResizeAnimationDuration = context.getResources().getInteger(
                 R.integer.config_pipResizeAnimationDuration);
         mPipMenuFadeAnimationDuration = context.getResources()
-                .getInteger(R.integer.pip_menu_fade_animation_duration);
+                .getInteger(R.integer.tv_window_menu_fade_animation_duration);
 
         mPipMenuOuterSpace = context.getResources()
                 .getDimensionPixelSize(R.dimen.pip_menu_outer_space);
@@ -230,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)
@@ -271,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)
@@ -568,7 +575,7 @@
         if (actionsNumber > buttonsNumber) {
             // Add buttons until we have enough to display all the actions.
             while (actionsNumber > buttonsNumber) {
-                TvPipMenuActionButton button = new TvPipMenuActionButton(mContext);
+                TvWindowMenuActionButton button = new TvWindowMenuActionButton(mContext);
                 button.setOnClickListener(this);
 
                 mActionButtonsContainer.addView(button,
@@ -591,7 +598,7 @@
         // "Assign" actions to the buttons.
         for (int index = 0; index < actionsNumber; index++) {
             final RemoteAction action = actions.get(index);
-            final TvPipMenuActionButton button = mAdditionalButtons.get(index);
+            final TvWindowMenuActionButton button = mAdditionalButtons.get(index);
 
             // Remove action if it matches the custom close action.
             if (PipUtils.remoteActionsMatch(action, closeAction)) {
@@ -607,7 +614,7 @@
         }
     }
 
-    private void setActionForButton(RemoteAction action, TvPipMenuActionButton button,
+    private void setActionForButton(RemoteAction action, TvWindowMenuActionButton button,
             Handler mainHandler) {
         button.setVisibility(View.VISIBLE); // Ensure the button is visible.
         if (action.getContentDescription().length() > 0) {
@@ -769,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/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index b296151..93c7529 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -46,6 +46,8 @@
             Consts.TAG_WM_SHELL),
     WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
+    WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM_SHELL),
     TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
 
     private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
index 681d964..7fd03a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
@@ -56,7 +56,7 @@
             return false;
         }
         final int taskId = new Integer(args[1]);
-        final int sideStagePosition = args.length > 3
+        final int sideStagePosition = args.length > 2
                 ? new Integer(args[2]) : SPLIT_POSITION_BOTTOM_OR_RIGHT;
         mController.moveToSideStage(taskId, sideStagePosition);
         return true;
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 4bc8e91..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() {
@@ -1374,21 +1383,20 @@
                 }
             }
         } else if (isSideStage && hasChildren && !mMainStage.isActive()) {
-            if (mFocusingTaskInfo != null && !isValidToEnterSplitScreen(mFocusingTaskInfo)) {
-                final WindowContainerTransaction wct = new WindowContainerTransaction();
-                mSideStage.removeAllTasks(wct, true);
-                wct.reorder(mRootTaskInfo.token, false /* onTop */);
-                mTaskOrganizer.applyTransaction(wct);
-                Slog.i(TAG, "cancel entering split screen, reason = "
-                        + exitReasonToString(EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW));
-            } else {
-                final WindowContainerTransaction wct = new WindowContainerTransaction();
-                mSplitLayout.init();
-                prepareEnterSplitScreen(wct);
-                mSyncQueue.queue(wct);
-                mSyncQueue.runInSync(t ->
-                        updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
-            }
+            // TODO (b/238697912) : Add the validation to prevent entering non-recovered status
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            mSplitLayout.init();
+            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 */);
+
+                mSplitLayout.flingDividerToCenter();
+            });
         }
         if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
             mShouldUpdateRecents = true;
@@ -1830,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/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index b70bde3..7b498e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -246,7 +246,7 @@
         window.setOuter(snapshotSurface);
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
-            session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0,
+            session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0,
                     tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
                     tmpControls, new Bundle());
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
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 08eb2c9..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;
@@ -64,6 +66,7 @@
 import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
@@ -203,14 +206,24 @@
     }
 
     @VisibleForTesting
-    static boolean isRotationSeamless(@NonNull TransitionInfo info,
-            DisplayController displayController) {
+    static int getRotationAnimationHint(@NonNull TransitionInfo.Change displayChange,
+            @NonNull TransitionInfo info, @NonNull DisplayController displayController) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
-                "Display is changing, check if it should be seamless.");
-        boolean checkedDisplayLayout = false;
-        boolean hasTask = false;
-        boolean displayExplicitSeamless = false;
-        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                "Display is changing, resolve the animation hint.");
+        // The explicit request of display has the highest priority.
+        if (displayChange.getRotationAnimation() == ROTATION_ANIMATION_SEAMLESS) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                    "  display requests explicit seamless");
+            return ROTATION_ANIMATION_SEAMLESS;
+        }
+
+        boolean allTasksSeamless = false;
+        boolean rejectSeamless = false;
+        ActivityManager.RunningTaskInfo topTaskInfo = null;
+        int animationHint = ROTATION_ANIMATION_ROTATE;
+        // Traverse in top-to-bottom order so that the first task is top-most.
+        final int size = info.getChanges().size();
+        for (int i = 0; i < size; ++i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
 
             // Only look at changing things. showing/hiding don't need to rotate.
@@ -223,95 +236,69 @@
                 if ((change.getFlags() & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                             "  display has system alert windows, so not seamless.");
-                    return false;
+                    rejectSeamless = true;
                 }
-                displayExplicitSeamless =
-                        change.getRotationAnimation() == ROTATION_ANIMATION_SEAMLESS;
             } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
                 if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                             "  wallpaper is participating but isn't seamless.");
-                    return false;
+                    rejectSeamless = true;
                 }
             } else if (change.getTaskInfo() != null) {
-                hasTask = true;
+                final int anim = change.getRotationAnimation();
+                final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+                final boolean isTopTask = topTaskInfo == null;
+                if (isTopTask) {
+                    topTaskInfo = taskInfo;
+                    if (anim != ROTATION_ANIMATION_UNSPECIFIED
+                            && anim != ROTATION_ANIMATION_SEAMLESS) {
+                        animationHint = anim;
+                    }
+                }
                 // We only enable seamless rotation if all the visible task windows requested it.
-                if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
+                if (anim != ROTATION_ANIMATION_SEAMLESS) {
                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                             "  task %s isn't requesting seamless, so not seamless.",
-                            change.getTaskInfo().taskId);
-                    return false;
-                }
-
-                // This is the only way to get display-id currently, so we will check display
-                // capabilities here
-                if (!checkedDisplayLayout) {
-                    // only need to check display once.
-                    checkedDisplayLayout = true;
-                    final DisplayLayout displayLayout = displayController.getDisplayLayout(
-                            change.getTaskInfo().displayId);
-                    // For the upside down rotation we don't rotate seamlessly as the navigation
-                    // bar moves position. Note most apps (using orientation:sensor or user as
-                    // opposed to fullSensor) will not enter the reverse portrait orientation, so
-                    // actually the orientation won't change at all.
-                    int upsideDownRotation = displayLayout.getUpsideDownRotation();
-                    if (change.getStartRotation() == upsideDownRotation
-                            || change.getEndRotation() == upsideDownRotation) {
-                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
-                                "  rotation involves upside-down portrait, so not seamless.");
-                        return false;
-                    }
-
-                    // If the navigation bar can't change sides, then it will jump when we change
-                    // orientations and we don't rotate seamlessly - unless that is allowed, eg.
-                    // with gesture navigation where the navbar is low-profile enough that this
-                    // isn't very noticeable.
-                    if (!displayLayout.allowSeamlessRotationDespiteNavBarMoving()
-                            && (!(displayLayout.navigationBarCanMove()
-                                    && (change.getStartAbsBounds().width()
-                                            != change.getStartAbsBounds().height())))) {
-                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
-                                "  nav bar changes sides, so not seamless.");
-                        return false;
-                    }
+                            taskInfo.taskId);
+                    allTasksSeamless = false;
+                } else if (isTopTask) {
+                    allTasksSeamless = true;
                 }
             }
         }
 
-        // ROTATION_ANIMATION_SEAMLESS can only be requested by task or display.
-        if (hasTask || displayExplicitSeamless) {
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  Rotation IS seamless.");
-            return true;
+        if (!allTasksSeamless || rejectSeamless) {
+            return animationHint;
         }
-        return false;
-    }
 
-    /**
-     * Gets the rotation animation for the topmost task. Assumes that seamless is checked
-     * elsewhere, so it will default SEAMLESS to ROTATE.
-     */
-    private int getRotationAnimation(@NonNull TransitionInfo info) {
-        // Traverse in top-to-bottom order so that the first task is top-most
-        for (int i = 0; i < info.getChanges().size(); ++i) {
-            final TransitionInfo.Change change = info.getChanges().get(i);
-
-            // Only look at changing things. showing/hiding don't need to rotate.
-            if (change.getMode() != TRANSIT_CHANGE) continue;
-
-            // This container isn't rotating, so we can ignore it.
-            if (change.getEndRotation() == change.getStartRotation()) continue;
-
-            if (change.getTaskInfo() != null) {
-                final int anim = change.getRotationAnimation();
-                if (anim == ROTATION_ANIMATION_UNSPECIFIED
-                        // Fallback animation for seamless should also be default.
-                        || anim == ROTATION_ANIMATION_SEAMLESS) {
-                    return ROTATION_ANIMATION_ROTATE;
-                }
-                return anim;
-            }
+        // This is the only way to get display-id currently, so check display capabilities here.
+        final DisplayLayout displayLayout = displayController.getDisplayLayout(
+                topTaskInfo.displayId);
+        // For the upside down rotation we don't rotate seamlessly as the navigation bar moves
+        // position. Note most apps (using orientation:sensor or user as opposed to fullSensor)
+        // will not enter the reverse portrait orientation, so actually the orientation won't
+        // change at all.
+        final int upsideDownRotation = displayLayout.getUpsideDownRotation();
+        if (displayChange.getStartRotation() == upsideDownRotation
+                || displayChange.getEndRotation() == upsideDownRotation) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                    "  rotation involves upside-down portrait, so not seamless.");
+            return animationHint;
         }
-        return ROTATION_ANIMATION_ROTATE;
+
+        // If the navigation bar can't change sides, then it will jump when we change orientations
+        // and we don't rotate seamlessly - unless that is allowed, e.g. with gesture navigation
+        // where the navbar is low-profile enough that this isn't very noticeable.
+        if (!displayLayout.allowSeamlessRotationDespiteNavBarMoving()
+                && (!(displayLayout.navigationBarCanMove()
+                        && (displayChange.getStartAbsBounds().width()
+                                != displayChange.getStartAbsBounds().height())))) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                    "  nav bar changes sides, so not seamless.");
+            return animationHint;
+        }
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  Rotation IS seamless.");
+        return ROTATION_ANIMATION_SEAMLESS;
     }
 
     @Override
@@ -354,8 +341,8 @@
 
             if (change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
                 if (info.getType() == TRANSIT_CHANGE) {
-                    isSeamlessDisplayChange = isRotationSeamless(info, mDisplayController);
-                    final int anim = getRotationAnimation(info);
+                    final int anim = getRotationAnimationHint(change, info, mDisplayController);
+                    isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS;
                     if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
                         startRotationAnimation(startTransaction, change, info, anim, animations,
                                 onAnimFinish);
@@ -918,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) {
@@ -937,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/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index 45b69f1..6388ca1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -86,8 +86,6 @@
     private final float[] mTmpFloats = new float[9];
     /** The leash of the changing window container. */
     private final SurfaceControl mSurfaceControl;
-    private final Rect mStartBounds = new Rect();
-    private final Rect mEndBounds = new Rect();
 
     private final int mAnimHint;
     private final int mStartWidth;
@@ -105,8 +103,7 @@
      */
     private SurfaceControl mBackColorSurface;
     /** The leash using to animate screenshot layer. */
-    private SurfaceControl mAnimLeash;
-    private Transaction mTransaction;
+    private final SurfaceControl mAnimLeash;
 
     // The current active animation to move from the old to the new rotated
     // state.  Which animation is run here will depend on the old and new
@@ -134,9 +131,6 @@
         mStartRotation = change.getStartRotation();
         mEndRotation = change.getEndRotation();
 
-        mStartBounds.set(change.getStartAbsBounds());
-        mEndBounds.set(change.getEndAbsBounds());
-
         mAnimLeash = new SurfaceControl.Builder(session)
                 .setParent(rootLeash)
                 .setEffectLayer()
@@ -169,6 +163,8 @@
 
             t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
             t.show(mAnimLeash);
+            // Crop the real content in case it contains a larger child layer, e.g. wallpaper.
+            t.setCrop(mSurfaceControl, new Rect(0, 0, mEndWidth, mEndHeight));
 
             final ColorSpace colorSpace = screenshotBuffer.getColorSpace();
             final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
@@ -306,7 +302,6 @@
         mRotateEnterAnimation.restrictDuration(MAX_ANIMATION_DURATION);
         mRotateEnterAnimation.scaleCurrentDuration(animationScale);
 
-        mTransaction = mTransactionPool.acquire();
         if (customRotate) {
             mRotateAlphaAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
             mRotateAlphaAnimation.restrictDuration(MAX_ANIMATION_DURATION);
@@ -386,22 +381,16 @@
     }
 
     public void kill() {
-        Transaction t = mTransaction != null ? mTransaction : mTransactionPool.acquire();
+        final Transaction t = mTransactionPool.acquire();
         if (mAnimLeash.isValid()) {
             t.remove(mAnimLeash);
         }
 
-        if (mScreenshotLayer != null) {
-            if (mScreenshotLayer.isValid()) {
-                t.remove(mScreenshotLayer);
-            }
-            mScreenshotLayer = null;
+        if (mScreenshotLayer != null && mScreenshotLayer.isValid()) {
+            t.remove(mScreenshotLayer);
         }
-        if (mBackColorSurface != null) {
-            if (mBackColorSurface.isValid()) {
-                t.remove(mBackColorSurface);
-            }
-            mBackColorSurface = null;
+        if (mBackColorSurface != null && mBackColorSurface.isValid()) {
+            t.remove(mBackColorSurface);
         }
         t.apply();
         mTransactionPool.release(t);
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 279d57a..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
@@ -23,6 +23,7 @@
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.fixScale;
 import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -119,6 +120,8 @@
     /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
     private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
 
+    private final ArrayList<TransitionObserver> mObservers = new ArrayList<>();
+
     /** List of {@link Runnable} instances to run when the last active transition has finished.  */
     private final ArrayList<Runnable> mRunWhenIdleQueue = new ArrayList<>();
 
@@ -165,10 +168,7 @@
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
 
         ContentResolver resolver = mContext.getContentResolver();
-        mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
-                Settings.Global.TRANSITION_ANIMATION_SCALE,
-                mContext.getResources().getFloat(
-                        R.dimen.config_appTransitionAnimationDurationScaleDefault));
+        mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
         dispatchAnimScaleSetting(mTransitionAnimationScaleSetting);
 
         resolver.registerContentObserver(
@@ -183,6 +183,12 @@
         }
     }
 
+    private float getTransitionAnimationScaleSetting() {
+        return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+                Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
+                                R.dimen.config_appTransitionAnimationDurationScaleDefault)));
+    }
+
     public ShellTransitions asRemoteTransitions() {
         return mImpl;
     }
@@ -213,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());
     }
@@ -242,6 +250,16 @@
         mRemoteTransitionHandler.removeFiltered(remoteTransition);
     }
 
+    /** Registers an observer on the lifecycle of transitions. */
+    public void registerObserver(@NonNull TransitionObserver observer) {
+        mObservers.add(observer);
+    }
+
+    /** Unregisters the observer. */
+    public void unregisterObserver(@NonNull TransitionObserver observer) {
+        mObservers.remove(observer);
+    }
+
     /** Boosts the process priority of remote animation player. */
     public static void setRunningRemoteTransitionDelegate(IApplicationThread appThread) {
         if (appThread == null) return;
@@ -407,6 +425,11 @@
                     + Arrays.toString(mActiveTransitions.stream().map(
                             activeTransition -> activeTransition.mToken).toArray()));
         }
+
+        for (int i = 0; i < mObservers.size(); ++i) {
+            mObservers.get(i).onTransitionReady(transitionToken, info, t, finishT);
+        }
+
         if (!info.getRootLeash().isValid()) {
             // Invalid root-leash implies that the transition is empty/no-op, so just do
             // housekeeping and return.
@@ -474,6 +497,10 @@
     }
 
     private void playTransition(@NonNull ActiveTransition active) {
+        for (int i = 0; i < mObservers.size(); ++i) {
+            mObservers.get(i).onTransitionStarting(active.mToken);
+        }
+
         setupAnimHierarchy(active.mInfo, active.mStartT, active.mFinishT);
 
         // If a handler already chose to run this animation, try delegating to it first.
@@ -546,6 +573,10 @@
                 active.mHandler.onTransitionConsumed(
                         active.mToken, abort, abort ? null : active.mFinishT);
             }
+            for (int i = 0; i < mObservers.size(); ++i) {
+                mObservers.get(i).onTransitionMerged(
+                        active.mToken, mActiveTransitions.get(0).mToken);
+            }
             return;
         }
         final ActiveTransition active = mActiveTransitions.get(activeIdx);
@@ -555,6 +586,9 @@
             active.mHandler.onTransitionConsumed(
                     transition, true /* aborted */, null /* finishTransaction */);
         }
+        for (int i = 0; i < mObservers.size(); ++i) {
+            mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
+        }
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                 "Transition animation finished (abort=%b), notifying core %s", abort, transition);
         // Merge all relevant transactions together
@@ -593,6 +627,9 @@
                         transition, true /* aborted */, null /* finishTransaction */);
             }
             mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
+            for (int i = 0; i < mObservers.size(); ++i) {
+                mObservers.get(i).onTransitionFinished(active.mToken, true);
+            }
         }
         if (mActiveTransitions.size() <= activeIdx) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
@@ -792,6 +829,52 @@
         default void setAnimScaleSetting(float scale) {}
     }
 
+    /**
+     * Interface for something that needs to know the lifecycle of some transitions, but never
+     * handles any transition by itself.
+     */
+    public interface TransitionObserver {
+        /**
+         * Called when the transition is ready to play. It may later be merged into other
+         * transitions. Note this doesn't mean this transition will be played anytime soon.
+         *
+         * @param transition the unique token of this transition
+         * @param startTransaction the transaction given to the handler to be applied before the
+         *                         transition animation. This will be applied when the transition
+         *                         handler that handles this transition starts the transition.
+         * @param finishTransaction the transaction given to the handler to be applied after the
+         *                          transition animation. The Transition system will apply it when
+         *                          finishCallback is called by the transition handler.
+         */
+        void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
+                @NonNull SurfaceControl.Transaction startTransaction,
+                @NonNull SurfaceControl.Transaction finishTransaction);
+
+        /**
+         * Called when the transition is starting to play. It isn't called for merged transitions.
+         *
+         * @param transition the unique token of this transition
+         */
+        void onTransitionStarting(@NonNull IBinder transition);
+
+        /**
+         * Called when a transition is merged into another transition. There won't be any following
+         * lifecycle calls for the merged transition.
+         *
+         * @param merged the unique token of the transition that's merged to another one
+         * @param playing the unique token of the transition that accepts the merge
+         */
+        void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing);
+
+        /**
+         * Called when the transition is finished. This isn't called for merged transitions.
+         *
+         * @param transition the unique token of this transition
+         * @param aborted {@code true} if this transition is aborted; {@code false} otherwise.
+         */
+        void onTransitionFinished(@NonNull IBinder transition, boolean aborted);
+    }
+
     @BinderThread
     private class TransitionPlayerImpl extends ITransitionPlayer.Stub {
         @Override
@@ -875,6 +958,11 @@
                         transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition);
                     });
         }
+
+        @Override
+        public IBinder getShellApplyToken() {
+            return SurfaceControl.Transaction.getDefaultApplyToken();
+        }
     }
 
     private class SettingsObserver extends ContentObserver {
@@ -886,9 +974,7 @@
         @Override
         public void onChange(boolean selfChange) {
             super.onChange(selfChange);
-            mTransitionAnimationScaleSetting = Settings.Global.getFloat(
-                    mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE,
-                    mTransitionAnimationScaleSetting);
+            mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
 
             mMainExecutor.execute(() -> dispatchAnimScaleSetting(mTransitionAnimationScaleSetting));
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 08d6c50..e7695926 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -51,7 +51,6 @@
     private final Choreographer mMainChoreographer;
     private final DisplayController mDisplayController;
     private final SyncTransactionQueue mSyncQueue;
-
     private FreeformTaskTransitionStarter mTransitionStarter;
 
     public CaptionWindowDecorViewModel(
@@ -168,6 +167,14 @@
                 } else {
                     mSyncQueue.queue(wct);
                 }
+            } else if (id == R.id.minimize_window) {
+                WindowContainerTransaction wct = new WindowContainerTransaction();
+                wct.reorder(mTaskToken, false);
+                if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+                    mTransitionStarter.startMinimizedModeTransition(wct);
+                } else {
+                    mSyncQueue.queue(wct);
+                }
             }
         }
 
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 dc212fc..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
@@ -34,6 +34,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.desktopmode.DesktopModeConstants;
 
 /**
  * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
@@ -141,7 +142,7 @@
             return;
         }
 
-        if (oldDecorationSurface != mDecorationContainerSurface) {
+        if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) {
             closeDragResizeListener();
             mDragResizeListener = new DragResizeInputListener(
                     mContext,
@@ -161,12 +162,19 @@
      */
     private void setupRootView() {
         View caption = mResult.mRootView.findViewById(R.id.caption);
-
         caption.setOnTouchListener(mOnCaptionTouchListener);
         View maximize = caption.findViewById(R.id.maximize_window);
-        maximize.setOnClickListener(mOnCaptionButtonClickListener);
+        if (DesktopModeConstants.IS_FEATURE_ENABLED) {
+            // Hide maximize button when desktop mode is available
+            maximize.setVisibility(View.GONE);
+        } else {
+            maximize.setVisibility(View.VISIBLE);
+            maximize.setOnClickListener(mOnCaptionButtonClickListener);
+        }
         View close = caption.findViewById(R.id.close_window);
         close.setOnClickListener(mOnCaptionButtonClickListener);
+        View minimize = caption.findViewById(R.id.minimize_window);
+        minimize.setOnClickListener(mOnCaptionButtonClickListener);
     }
 
     void setCaptionColor(int captionColor) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 506a4c0..5e64a06 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -248,7 +248,7 @@
         lp.setTrustedOverlay();
         if (mViewHost == null) {
             mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay,
-                    mCaptionWindowManager, true);
+                    mCaptionWindowManager);
             mViewHost.setView(outResult.mRootView, lp);
         } else {
             mViewHost.relayout(lp);
@@ -345,9 +345,8 @@
     }
 
     interface SurfaceControlViewHostFactory {
-        default SurfaceControlViewHost create(
-                Context c, Display d, WindowlessWindowManager wmm, boolean useSfChoreographer) {
-            return new SurfaceControlViewHost(c, d, wmm, useSfChoreographer);
+        default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
+            return new SurfaceControlViewHost(c, d, wmm);
         }
     }
 }
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 1c587a2..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
@@ -49,7 +48,10 @@
 ) {
     init {
         testSpec.setIsTablet(
-            WindowManagerStateHelper(instrumentation).currentState.wmState.isTablet
+            WindowManagerStateHelper(
+                instrumentation,
+                clearCacheAfterParsing = false
+            ).currentState.wmState.isTablet
         )
     }
 
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 1e4d23c..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,8 @@
 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
 
@@ -83,41 +85,37 @@
     }
 }
 
-fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible(
-    component: IComponentMatcher,
-    splitLeftTop: Boolean
+fun FlickerTestParameter.layerKeepVisible(
+    component: IComponentMatcher
 ) {
     assertLayers {
-        val dividerRegion = this.last().layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
-        this.isInvisible(component)
+        this.isVisible(component)
+    }
+}
+
+fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible(
+    component: IComponentMatcher,
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean
+) {
+    assertLayers {
+        this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
             .then()
-            .invoke("splitAppLayerBoundsBecomesVisible") {
-                it.visibleRegion(component).coversAtMost(
-                    if (splitLeftTop) {
-                        getSplitLeftTopRegion(dividerRegion, endRotation)
-                    } else {
-                        getSplitRightBottomRegion(dividerRegion, endRotation)
-                    }
-                )
-            }
+            .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
+            .then()
+            .splitAppLayerBoundsSnapToDivider(
+                component, landscapePosLeft, portraitPosTop, endRotation)
     }
 }
 
 fun FlickerTestParameter.splitAppLayerBoundsBecomesInvisible(
     component: IComponentMatcher,
-    splitLeftTop: Boolean
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean
 ) {
     assertLayers {
-        val dividerRegion = this.first().layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
-        this.invoke("splitAppLayerBoundsBecomesVisible") {
-                it.visibleRegion(component).coversAtMost(
-                    if (splitLeftTop) {
-                        getSplitLeftTopRegion(dividerRegion, endRotation)
-                    } else {
-                        getSplitRightBottomRegion(dividerRegion, endRotation)
-                    }
-                )
-            }
+        this.splitAppLayerBoundsSnapToDivider(
+                component, landscapePosLeft, portraitPosTop, endRotation)
             .then()
             .isVisible(component, true)
             .then()
@@ -127,15 +125,96 @@
 
 fun FlickerTestParameter.splitAppLayerBoundsIsVisibleAtEnd(
     component: IComponentMatcher,
-    splitLeftTop: Boolean
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean
 ) {
     assertLayersEnd {
+        splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, endRotation)
+    }
+}
+
+fun FlickerTestParameter.splitAppLayerBoundsKeepVisible(
+    component: IComponentMatcher,
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean
+) {
+    assertLayers {
+        splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, endRotation)
+    }
+}
+
+fun FlickerTestParameter.splitAppLayerBoundsChanges(
+    component: IComponentMatcher,
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean
+) {
+    assertLayers {
+        if (landscapePosLeft) {
+            this.splitAppLayerBoundsSnapToDivider(
+                    component, landscapePosLeft, portraitPosTop, endRotation)
+                .then()
+                .isInvisible(component)
+                .then()
+                .splitAppLayerBoundsSnapToDivider(
+                    component, landscapePosLeft, portraitPosTop, endRotation)
+        } else {
+            this.splitAppLayerBoundsSnapToDivider(
+                component, landscapePosLeft, portraitPosTop, endRotation)
+        }
+    }
+}
+
+fun LayersTraceSubject.splitAppLayerBoundsSnapToDivider(
+    component: IComponentMatcher,
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean,
+    rotation: Int
+): LayersTraceSubject {
+    return invoke("splitAppLayerBoundsSnapToDivider") {
+        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 (splitLeftTop) {
-                getSplitLeftTopRegion(dividerRegion, endRotation)
+            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, endRotation)
+                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
+                    )
+                }
             }
         )
     }
@@ -147,6 +226,10 @@
     assertWm {
         this.isAppWindowInvisible(component)
             .then()
+            .notContains(component, isOptional = true)
+            .then()
+            .isAppWindowInvisible(component, isOptional = true)
+            .then()
             .isAppWindowVisible(component)
     }
 }
@@ -169,6 +252,14 @@
     }
 }
 
+fun FlickerTestParameter.appWindowKeepVisible(
+    component: IComponentMatcher
+) {
+    assertWm {
+        this.isAppWindowVisible(component)
+    }
+}
+
 fun FlickerTestParameter.dockedStackDividerIsVisibleAtEnd() {
     assertLayersEnd {
         this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
@@ -270,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 3708e5f..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,9 +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 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 4877442..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,16 +28,18 @@
 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
 import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
 import com.android.wm.shell.flicker.testapp.Components
 
 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
@@ -46,6 +48,7 @@
         const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
         const val DIVIDER_BAR = "docked_divider_handle"
         const val GESTURE_STEP_MS = 16L
+        const val LONG_PRESS_TIME_MS = 100L
 
         private val notificationScrollerSelector: BySelector
             get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
@@ -82,6 +85,13 @@
                 Components.SendNotificationActivity.COMPONENT.toFlickerComponent()
             )
 
+        fun getIme(instrumentation: Instrumentation): SplitScreenHelper =
+            SplitScreenHelper(
+                instrumentation,
+                Components.ImeActivity.LABEL,
+                Components.ImeActivity.COMPONENT.toFlickerComponent()
+            )
+
         fun waitForSplitComplete(
             wmHelper: WindowManagerStateHelper,
             primaryApp: IComponentMatcher,
@@ -95,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,
@@ -206,6 +235,16 @@
             }
         }
 
+        fun longPress(
+            instrumentation: Instrumentation,
+            point: Point
+        ) {
+            val downTime = SystemClock.uptimeMillis()
+            touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, point)
+            SystemClock.sleep(LONG_PRESS_TIME_MS)
+            touch(instrumentation, MotionEvent.ACTION_UP, downTime, downTime, TIMEOUT_MS, point)
+        }
+
         fun createShortcutOnHotseatIfNotExist(
             tapl: LauncherInstrumentation,
             appName: String
@@ -220,6 +259,23 @@
             }
         }
 
+        fun dragDividerToResizeAndWait(
+            device: UiDevice,
+            wmHelper: WindowManagerStateHelper
+        ) {
+            val displayBounds = wmHelper.currentState.layerState
+                .displays.firstOrNull { !it.isVirtual }
+                ?.layerStackSpace
+                ?: error("Display not found")
+            val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+            dividerBar.drag(Point(displayBounds.width * 2 / 3, displayBounds.height * 2 / 3))
+
+            wmHelper.StateSyncBuilder()
+                .withAppTransitionIdle()
+                .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
+                .waitForAndVerify()
+        }
+
         fun dragDividerToDismissSplit(
             device: UiDevice,
             wmHelper: WindowManagerStateHelper
@@ -240,5 +296,33 @@
             SystemClock.sleep(interval.toLong())
             dividerBar.click()
         }
+
+        fun copyContentFromLeftToRight(
+            instrumentation: Instrumentation,
+            device: UiDevice,
+            sourceApp: IComponentNameMatcher,
+            destinationApp: IComponentNameMatcher,
+        ) {
+            // Copy text from sourceApp
+            val textView = device.wait(Until.findObject(
+                By.res(sourceApp.packageName, "SplitScreenTest")), TIMEOUT_MS)
+            longPress(instrumentation, textView.getVisibleCenter())
+
+            val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
+            copyBtn.click()
+
+            // Paste text to destinationApp
+            val editText = device.wait(Until.findObject(
+                By.res(destinationApp.packageName, "plain_text_input")), TIMEOUT_MS)
+            longPress(instrumentation, editText.getVisibleCenter())
+
+            val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
+            pasteBtn.click()
+
+            // Verify text
+            if (!textView.getText().contentEquals(editText.getText())) {
+                error("Fail to copy content in split")
+            }
+        }
     }
 }
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
new file mode 100644
index 0000000..d238814
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 android.view.WindowManagerPolicyConstants
+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.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.appWindowKeepVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerKeepVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test copy content from the left to the right side of the split-screen.
+ *
+ * To run this test: `atest WMShellFlickerTests:CopyContentInSplit`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+    protected val textEditApp = SplitScreenHelper.getIme(instrumentation)
+
+    // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+    @Before
+    open fun before() {
+        Assume.assumeTrue(tapl.isTablet)
+    }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                eachRun {
+                    textEditApp.launchViaIntent(wmHelper)
+                    // TODO(b/231399940): Use recent shortcut to enter split.
+                    tapl.launchedAppState.taskbar
+                        .openAllApps()
+                        .getAppIcon(primaryApp.appName)
+                        .dragToSplitscreen(primaryApp.`package`, textEditApp.`package`)
+                    SplitScreenHelper.waitForSplitComplete(wmHelper, textEditApp, primaryApp)
+                }
+            }
+            transitions {
+                SplitScreenHelper.copyContentFromLeftToRight(
+                    instrumentation, device, primaryApp, textEditApp)
+            }
+        }
+
+    @Presubmit
+    @Test
+    fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+
+    @Presubmit
+    @Test
+    fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
+
+    @Presubmit
+    @Test
+    fun textEditAppLayerKeepVisible() = testSpec.layerKeepVisible(textEditApp)
+
+    @Presubmit
+    @Test
+    fun primaryAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
+        primaryApp, landscapePosLeft = true, portraitPosTop = true)
+
+    @Presubmit
+    @Test
+    fun textEditAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
+        textEditApp, landscapePosLeft = false, portraitPosTop = false)
+
+    @Presubmit
+    @Test
+    fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
+
+    @Presubmit
+    @Test
+    fun textEditAppWindowKeepVisible() = testSpec.appWindowKeepVisible(textEditApp)
+
+    /** {@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,
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes =
+                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+        }
+    }
+}
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
new file mode 100644
index 0000000..9ac7c23
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 android.view.Surface
+import android.view.WindowManagerPolicyConstants
+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.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.appWindowKeepVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerKeepVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test resize split by dragging the divider bar.
+ *
+ * To run this test: `atest WMShellFlickerTests:DragDividerToResize`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class DragDividerToResize (testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+
+    // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+    @Before
+    open fun before() {
+        Assume.assumeTrue(tapl.isTablet)
+    }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                eachRun {
+                    tapl.goHome()
+                    primaryApp.launchViaIntent(wmHelper)
+                    // TODO(b/231399940): Use recent shortcut to enter split.
+                    tapl.launchedAppState.taskbar
+                        .openAllApps()
+                        .getAppIcon(secondaryApp.appName)
+                        .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+                    SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+                }
+            }
+            transitions {
+                SplitScreenHelper.dragDividerToResizeAndWait(device, wmHelper)
+            }
+        }
+
+    @Presubmit
+    @Test
+    fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+
+    @Presubmit
+    @Test
+    fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
+
+    @Presubmit
+    @Test
+    fun secondaryAppLayerVisibilityChanges() {
+        testSpec.assertLayers {
+            this.isVisible(secondaryApp)
+                .then()
+                .isInvisible(secondaryApp)
+                .then()
+                .isVisible(secondaryApp)
+        }
+    }
+
+    @Presubmit
+    @Test
+    fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
+
+    @Presubmit
+    @Test
+    fun secondaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(secondaryApp)
+
+    @Presubmit
+    @Test
+    fun primaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
+        primaryApp, landscapePosLeft = false, portraitPosTop = false)
+
+    @Presubmit
+    @Test
+    fun secondaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
+        secondaryApp, landscapePosLeft = true, portraitPosTop = true)
+
+    /** {@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,
+                supportedRotations = listOf(Surface.ROTATION_0),
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes =
+                listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+        }
+    }
+}
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 38279a3..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
@@ -29,6 +29,7 @@
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.layerKeepVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import org.junit.Assume
 import org.junit.Before
@@ -80,11 +81,7 @@
 
     @Presubmit
     @Test
-    fun splitScreenDividerKeepVisible() {
-        testSpec.assertLayers {
-            this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
-        }
-    }
+    fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 
     @Presubmit
     @Test
@@ -97,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
new file mode 100644
index 0000000..20544bd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -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 com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+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.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switch to split pair from another app.
+ *
+ * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromAnotherApp`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+    val thirdApp = SplitScreenHelper.getNonResizeable(instrumentation)
+
+    // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+    @Before
+    open fun before() {
+        Assume.assumeTrue(tapl.isTablet)
+    }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                eachRun {
+                    primaryApp.launchViaIntent(wmHelper)
+                    // TODO(b/231399940): Use recent shortcut to enter split.
+                    tapl.launchedAppState.taskbar
+                        .openAllApps()
+                        .getAppIcon(secondaryApp.appName)
+                        .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+                    SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+
+                    thirdApp.launchViaIntent(wmHelper)
+                    wmHelper.StateSyncBuilder()
+                        .withAppTransitionIdle()
+                        .withWindowSurfaceAppeared(thirdApp)
+                        .waitForAndVerify()
+                }
+            }
+            transitions {
+                tapl.launchedAppState.quickSwitchToPreviousApp()
+                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+        }
+
+    @Presubmit
+    @Test
+    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+    @Presubmit
+    @Test
+    fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+
+    @Presubmit
+    @Test
+    fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+
+    @Presubmit
+    @Test
+    fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        primaryApp, landscapePosLeft = false, portraitPosTop = false)
+
+    @Presubmit
+    @Test
+    fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        secondaryApp, landscapePosLeft = true, 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,
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes =
+                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+        }
+    }
+}
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 c48f3f7..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
@@ -26,11 +26,8 @@
 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.appWindowIsVisibleAtEnd
 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.Assume
@@ -42,7 +39,7 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test switch back to split pair after go home
+ * Test quick switch to split pair from home.
  *
  * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromHome`
  */
@@ -60,30 +57,30 @@
     }
 
     override val transition: FlickerBuilder.() -> Unit
-    get() = {
-        super.transition(this)
-        setup {
-            eachRun {
-                primaryApp.launchViaIntent(wmHelper)
-                // TODO(b/231399940): Use recent shortcut to enter split.
-                tapl.launchedAppState.taskbar
-                    .openAllApps()
-                    .getAppIcon(secondaryApp.appName)
-                    .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
-                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+        get() = {
+            super.transition(this)
+            setup {
+                eachRun {
+                    primaryApp.launchViaIntent(wmHelper)
+                    // TODO(b/231399940): Use recent shortcut to enter split.
+                    tapl.launchedAppState.taskbar
+                        .openAllApps()
+                        .getAppIcon(secondaryApp.appName)
+                        .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+                    SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
 
-                tapl.goHome()
-                wmHelper.StateSyncBuilder()
-                    .withAppTransitionIdle()
-                    .withHomeActivityVisible()
-                    .waitForAndVerify()
+                    tapl.goHome()
+                    wmHelper.StateSyncBuilder()
+                        .withAppTransitionIdle()
+                        .withHomeActivityVisible()
+                        .waitForAndVerify()
+                }
+            }
+            transitions {
+                tapl.workspace.quickSwitchToPreviousApp()
+                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
             }
         }
-        transitions {
-            tapl.workspace.quickSwitchToPreviousApp()
-            SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-        }
-    }
 
     @Presubmit
     @Test
@@ -91,7 +88,7 @@
 
     @Presubmit
     @Test
-    fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+    fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
 
     @Presubmit
     @Test
@@ -100,16 +97,16 @@
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
-        primaryApp, splitLeftTop = false)
+        primaryApp, landscapePosLeft = false, portraitPosTop = false)
 
     @Presubmit
     @Test
-    fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
-        secondaryApp, splitLeftTop = true)
+    fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        secondaryApp, landscapePosLeft = true, portraitPosTop = true)
 
     @Presubmit
     @Test
-    fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+    fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
 
     @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
new file mode 100644
index 0000000..adea66a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 android.view.WindowManagerPolicyConstants
+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.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test switch back to split pair from recent.
+ *
+ * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromRecent`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+
+    // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+    @Before
+    open fun before() {
+        Assume.assumeTrue(tapl.isTablet)
+    }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                eachRun {
+                    primaryApp.launchViaIntent(wmHelper)
+                    // TODO(b/231399940): Use recent shortcut to enter split.
+                    tapl.launchedAppState.taskbar
+                        .openAllApps()
+                        .getAppIcon(secondaryApp.appName)
+                        .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+                    SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+
+                    tapl.goHome()
+                    wmHelper.StateSyncBuilder()
+                        .withAppTransitionIdle()
+                        .withHomeActivityVisible()
+                        .waitForAndVerify()
+                }
+            }
+            transitions {
+                tapl.workspace.switchToOverview()
+                    .currentTask
+                    .open()
+                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+        }
+
+    @Presubmit
+    @Test
+    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+    @Presubmit
+    @Test
+    fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+
+    @Presubmit
+    @Test
+    fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+
+    @Presubmit
+    @Test
+    fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        primaryApp, landscapePosLeft = false, portraitPosTop = false)
+
+    @Presubmit
+    @Test
+    fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        secondaryApp, landscapePosLeft = true, 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,
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes =
+                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml
index 84789f5..642a08b5 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml
@@ -26,6 +26,7 @@
         android:layout_width="fill_parent"
         android:layout_height="fill_parent"
         android:gravity="center_vertical|center_horizontal"
+        android:textIsSelectable="true"
         android:text="PrimaryActivity"
         android:textAppearance="?android:attr/textAppearanceLarge"/>
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index f865649..b29c436 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -16,9 +16,11 @@
 
 package com.android.wm.shell;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -30,6 +32,8 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
@@ -38,9 +42,11 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.TaskInfo;
+import android.app.WindowConfiguration;
 import android.content.LocusId;
 import android.content.pm.ParceledListSlice;
 import android.os.Binder;
@@ -53,6 +59,8 @@
 import android.window.ITaskOrganizerController;
 import android.window.TaskAppearedInfo;
 import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+import android.window.WindowContainerTransaction.Change;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -628,6 +636,71 @@
         verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token);
     }
 
+    @Test
+    public void testPrepareClearBoundsForTasks() {
+        RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED);
+        task1.displayId = 1;
+        MockToken token1 = new MockToken();
+        task1.token = token1.token();
+        mOrganizer.onTaskAppeared(task1, null);
+
+        RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED);
+        task2.displayId = 1;
+        MockToken token2 = new MockToken();
+        task2.token = token2.token();
+        mOrganizer.onTaskAppeared(task2, null);
+
+        RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_UNDEFINED);
+        otherDisplayTask.displayId = 2;
+        MockToken otherDisplayToken = new MockToken();
+        otherDisplayTask.token = otherDisplayToken.token();
+        mOrganizer.onTaskAppeared(otherDisplayTask, null);
+
+        WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForTasks(1);
+
+        assertEquals(wct.getChanges().size(), 2);
+        Change boundsChange1 = wct.getChanges().get(token1.binder());
+        assertNotNull(boundsChange1);
+        assertNotEquals(
+                (boundsChange1.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
+        assertTrue(boundsChange1.getConfiguration().windowConfiguration.getBounds().isEmpty());
+
+        Change boundsChange2 = wct.getChanges().get(token2.binder());
+        assertNotNull(boundsChange2);
+        assertNotEquals(
+                (boundsChange2.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
+        assertTrue(boundsChange2.getConfiguration().windowConfiguration.getBounds().isEmpty());
+    }
+
+    @Test
+    public void testPrepareClearFreeformForTasks() {
+        RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM);
+        task1.displayId = 1;
+        MockToken token1 = new MockToken();
+        task1.token = token1.token();
+        mOrganizer.onTaskAppeared(task1, null);
+
+        RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW);
+        task2.displayId = 1;
+        MockToken token2 = new MockToken();
+        task2.token = token2.token();
+        mOrganizer.onTaskAppeared(task2, null);
+
+        RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_FREEFORM);
+        otherDisplayTask.displayId = 2;
+        MockToken otherDisplayToken = new MockToken();
+        otherDisplayTask.token = otherDisplayToken.token();
+        mOrganizer.onTaskAppeared(otherDisplayTask, null);
+
+        WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForTasks(1);
+
+        // Only task with freeform windowing mode and the right display should be updated
+        assertEquals(wct.getChanges().size(), 1);
+        Change wmModeChange1 = wct.getChanges().get(token1.binder());
+        assertNotNull(wmModeChange1);
+        assertEquals(wmModeChange1.getWindowingMode(), WINDOWING_MODE_UNDEFINED);
+    }
+
     private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
         RunningTaskInfo taskInfo = new RunningTaskInfo();
         taskInfo.taskId = taskId;
@@ -635,4 +708,22 @@
         return taskInfo;
     }
 
+    private static class MockToken {
+        private final WindowContainerToken mToken;
+        private final IBinder mBinder;
+
+        MockToken() {
+            mToken = mock(WindowContainerToken.class);
+            mBinder = mock(IBinder.class);
+            when(mToken.asBinder()).thenReturn(mBinder);
+        }
+
+        WindowContainerToken token() {
+            return mToken;
+        }
+
+        IBinder binder() {
+            return mBinder;
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
index 32f1587..ff1d2990 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
@@ -169,6 +169,7 @@
         mTaskView.onTaskAppeared(mTaskInfo, mLeash);
 
         verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
+        assertThat(mTaskView.isInitialized()).isTrue();
         verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
     }
 
@@ -178,6 +179,7 @@
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
 
         verify(mViewListener).onInitialized();
+        assertThat(mTaskView.isInitialized()).isTrue();
         // No task, no visibility change
         verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
     }
@@ -189,6 +191,7 @@
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
 
         verify(mViewListener).onInitialized();
+        assertThat(mTaskView.isInitialized()).isTrue();
         verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(true));
     }
 
@@ -223,6 +226,7 @@
 
         verify(mOrganizer).removeListener(eq(mTaskView));
         verify(mViewListener).onReleased();
+        assertThat(mTaskView.isInitialized()).isFalse();
     }
 
     @Test
@@ -270,6 +274,7 @@
 
         verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
         verify(mViewListener, never()).onInitialized();
+        assertThat(mTaskView.isInitialized()).isFalse();
         // If there's no surface the task should be made invisible
         verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(false));
     }
@@ -281,6 +286,7 @@
         verify(mTaskViewTransitions, never()).setTaskViewVisible(any(), anyBoolean());
 
         verify(mViewListener).onInitialized();
+        assertThat(mTaskView.isInitialized()).isTrue();
         // No task, no visibility change
         verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
     }
@@ -353,6 +359,7 @@
 
         verify(mOrganizer).removeListener(eq(mTaskView));
         verify(mViewListener).onReleased();
+        assertThat(mTaskView.isInitialized()).isFalse();
         verify(mTaskViewTransitions).removeTaskView(eq(mTaskView));
     }
 
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/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
new file mode 100644
index 0000000..58f20da
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.desktopmode;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.WindowConfiguration;
+import android.os.Handler;
+import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+import android.window.WindowContainerTransaction.Change;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.RootDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DesktopModeControllerTest extends ShellTestCase {
+
+    @Mock
+    private ShellTaskOrganizer mShellTaskOrganizer;
+    @Mock
+    private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
+    @Mock
+    private ShellExecutor mTestExecutor;
+    @Mock
+    private Handler mMockHandler;
+
+    private DesktopModeController mController;
+    private ShellInit mShellInit;
+
+    @Before
+    public void setUp() {
+        mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
+
+        mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer,
+                mRootDisplayAreaOrganizer, mMockHandler);
+
+        mShellInit.init();
+    }
+
+    @Test
+    public void instantiate_addInitCallback() {
+        verify(mShellInit, times(1)).addInitCallback(any(), any());
+    }
+
+    @Test
+    public void testDesktopModeEnabled_taskWmClearedDisplaySetToFreeform() {
+        // Create a fake WCT to simulate setting task windowing mode to undefined
+        WindowContainerTransaction taskWct = new WindowContainerTransaction();
+        MockToken taskMockToken = new MockToken();
+        taskWct.setWindowingMode(taskMockToken.token(), WINDOWING_MODE_UNDEFINED);
+        when(mShellTaskOrganizer.prepareClearFreeformForTasks(mContext.getDisplayId())).thenReturn(
+                taskWct);
+
+        // Create a fake WCT to simulate setting display windowing mode to freeform
+        WindowContainerTransaction displayWct = new WindowContainerTransaction();
+        MockToken displayMockToken = new MockToken();
+        displayWct.setWindowingMode(displayMockToken.token(), WINDOWING_MODE_FREEFORM);
+        when(mRootDisplayAreaOrganizer.prepareWindowingModeChange(mContext.getDisplayId(),
+                WINDOWING_MODE_FREEFORM)).thenReturn(displayWct);
+
+        // The test
+        mController.updateDesktopModeEnabled(true);
+
+        ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+                WindowContainerTransaction.class);
+        verify(mRootDisplayAreaOrganizer).applyTransaction(arg.capture());
+
+        // WCT should have 2 changes - clear task wm mode and set display wm mode
+        WindowContainerTransaction wct = arg.getValue();
+        assertThat(wct.getChanges()).hasSize(2);
+
+        // Verify executed WCT has a change for setting task windowing mode to undefined
+        Change taskWmModeChange = wct.getChanges().get(taskMockToken.binder());
+        assertThat(taskWmModeChange).isNotNull();
+        assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+
+        // Verify executed WCT has a change for setting display windowing mode to freeform
+        Change displayWmModeChange = wct.getChanges().get(displayMockToken.binder());
+        assertThat(displayWmModeChange).isNotNull();
+        assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
+    }
+
+    @Test
+    public void testDesktopModeDisabled_taskWmAndBoundsClearedDisplaySetToFullscreen() {
+        // Create a fake WCT to simulate setting task windowing mode to undefined
+        WindowContainerTransaction taskWmWct = new WindowContainerTransaction();
+        MockToken taskWmMockToken = new MockToken();
+        taskWmWct.setWindowingMode(taskWmMockToken.token(), WINDOWING_MODE_UNDEFINED);
+        when(mShellTaskOrganizer.prepareClearFreeformForTasks(mContext.getDisplayId())).thenReturn(
+                taskWmWct);
+
+        // Create a fake WCT to simulate clearing task bounds
+        WindowContainerTransaction taskBoundsWct = new WindowContainerTransaction();
+        MockToken taskBoundsMockToken = new MockToken();
+        taskBoundsWct.setBounds(taskBoundsMockToken.token(), null);
+        when(mShellTaskOrganizer.prepareClearBoundsForTasks(mContext.getDisplayId())).thenReturn(
+                taskBoundsWct);
+
+        // Create a fake WCT to simulate setting display windowing mode to fullscreen
+        WindowContainerTransaction displayWct = new WindowContainerTransaction();
+        MockToken displayMockToken = new MockToken();
+        displayWct.setWindowingMode(displayMockToken.token(), WINDOWING_MODE_FULLSCREEN);
+        when(mRootDisplayAreaOrganizer.prepareWindowingModeChange(mContext.getDisplayId(),
+                WINDOWING_MODE_FULLSCREEN)).thenReturn(displayWct);
+
+        // The test
+        mController.updateDesktopModeEnabled(false);
+
+        ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+                WindowContainerTransaction.class);
+        verify(mRootDisplayAreaOrganizer).applyTransaction(arg.capture());
+
+        // WCT should have 3 changes - clear task wm mode and bounds and set display wm mode
+        WindowContainerTransaction wct = arg.getValue();
+        assertThat(wct.getChanges()).hasSize(3);
+
+        // Verify executed WCT has a change for setting task windowing mode to undefined
+        Change taskWmModeChange = wct.getChanges().get(taskWmMockToken.binder());
+        assertThat(taskWmModeChange).isNotNull();
+        assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+
+        // Verify executed WCT has a change for clearing task bounds
+        Change taskBoundsChange = wct.getChanges().get(taskBoundsMockToken.binder());
+        assertThat(taskBoundsChange).isNotNull();
+        assertThat(taskBoundsChange.getWindowSetMask()
+                & WindowConfiguration.WINDOW_CONFIG_BOUNDS).isNotEqualTo(0);
+        assertThat(taskBoundsChange.getConfiguration().windowConfiguration.getBounds().isEmpty())
+                .isTrue();
+
+        // Verify executed WCT has a change for setting display windowing mode to fullscreen
+        Change displayWmModeChange = wct.getChanges().get(displayMockToken.binder());
+        assertThat(displayWmModeChange).isNotNull();
+        assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
+    }
+
+    private static class MockToken {
+        private final WindowContainerToken mToken;
+        private final IBinder mBinder;
+
+        MockToken() {
+            mToken = mock(WindowContainerToken.class);
+            mBinder = mock(IBinder.class);
+            when(mToken.asBinder()).thenReturn(mBinder);
+        }
+
+        WindowContainerToken token() {
+            return mToken;
+        }
+
+        IBinder binder() {
+            return mBinder;
+        }
+    }
+}
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/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 388792b..c6492be 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -22,6 +22,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
@@ -43,11 +44,13 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -59,8 +62,6 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
-import android.view.IDisplayWindowListener;
-import android.view.IWindowManager;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
@@ -82,6 +83,7 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.sysui.ShellInit;
@@ -89,6 +91,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
 
 import java.util.ArrayList;
 
@@ -551,64 +554,77 @@
         final @Surface.Rotation int upsideDown = displays
                 .getDisplayLayout(DEFAULT_DISPLAY).getUpsideDownRotation();
 
+        TransitionInfo.Change displayChange = new ChangeBuilder(TRANSIT_CHANGE)
+                .setFlags(FLAG_IS_DISPLAY).setRotate().build();
+        // Set non-square display so nav bar won't be allowed to move.
+        displayChange.getStartAbsBounds().set(0, 0, 1000, 2000);
         final TransitionInfo normalDispRotate = new TransitionInfoBuilder(TRANSIT_CHANGE)
-                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
-                        .build())
+                .addChange(displayChange)
                 .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo).setRotate().build())
                 .build();
-        assertFalse(DefaultTransitionHandler.isRotationSeamless(normalDispRotate, displays));
+        assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint(
+                displayChange, normalDispRotate, displays));
 
         // Seamless if all tasks are seamless
         final TransitionInfo rotateSeamless = new TransitionInfoBuilder(TRANSIT_CHANGE)
-                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
-                        .build())
+                .addChange(displayChange)
                 .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
                         .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
                 .build();
-        assertTrue(DefaultTransitionHandler.isRotationSeamless(rotateSeamless, displays));
+        assertEquals(ROTATION_ANIMATION_SEAMLESS, DefaultTransitionHandler.getRotationAnimationHint(
+                displayChange, rotateSeamless, displays));
 
         // Not seamless if there is PiP (or any other non-seamless task)
         final TransitionInfo pipDispRotate = new TransitionInfoBuilder(TRANSIT_CHANGE)
-                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
-                        .build())
+                .addChange(displayChange)
                 .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
                         .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
                 .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfoPip)
                         .setRotate().build())
                 .build();
-        assertFalse(DefaultTransitionHandler.isRotationSeamless(pipDispRotate, displays));
-
-        // Not seamless if one of rotations is upside-down
-        final TransitionInfo seamlessUpsideDown = new TransitionInfoBuilder(TRANSIT_CHANGE)
-                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
-                        .setRotate(upsideDown, ROTATION_ANIMATION_UNSPECIFIED).build())
-                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
-                        .setRotate(upsideDown, ROTATION_ANIMATION_SEAMLESS).build())
-                .build();
-        assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessUpsideDown, displays));
-
-        // Not seamless if system alert windows
-        final TransitionInfo seamlessButAlert = new TransitionInfoBuilder(TRANSIT_CHANGE)
-                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(
-                        FLAG_IS_DISPLAY | FLAG_DISPLAY_HAS_ALERT_WINDOWS).setRotate().build())
-                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
-                        .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
-                .build();
-        assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessButAlert, displays));
+        assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint(
+                displayChange, pipDispRotate, displays));
 
         // Not seamless if there is no changed task.
         final TransitionInfo noTask = new TransitionInfoBuilder(TRANSIT_CHANGE)
-                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
-                        .setRotate().build())
+                .addChange(displayChange)
                 .build();
-        assertFalse(DefaultTransitionHandler.isRotationSeamless(noTask, displays));
+        assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint(
+                displayChange, noTask, displays));
 
-        // Seamless if display is explicitly seamless.
-        final TransitionInfo seamlessDisplay = new TransitionInfoBuilder(TRANSIT_CHANGE)
-                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
+        // Not seamless if one of rotations is upside-down
+        displayChange = new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
+                .setRotate(upsideDown, ROTATION_ANIMATION_UNSPECIFIED).build();
+        final TransitionInfo seamlessUpsideDown = new TransitionInfoBuilder(TRANSIT_CHANGE)
+                .addChange(displayChange)
+                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
+                        .setRotate(upsideDown, ROTATION_ANIMATION_SEAMLESS).build())
+                .build();
+        assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint(
+                displayChange, seamlessUpsideDown, displays));
+
+        // Not seamless if system alert windows
+        displayChange = new ChangeBuilder(TRANSIT_CHANGE)
+                .setFlags(FLAG_IS_DISPLAY | FLAG_DISPLAY_HAS_ALERT_WINDOWS).setRotate().build();
+        final TransitionInfo seamlessButAlert = new TransitionInfoBuilder(TRANSIT_CHANGE)
+                .addChange(displayChange)
+                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
                         .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
                 .build();
-        assertTrue(DefaultTransitionHandler.isRotationSeamless(seamlessDisplay, displays));
+        assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint(
+                displayChange, seamlessButAlert, displays));
+
+        // Seamless if display is explicitly seamless.
+        displayChange = new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
+                .setRotate(ROTATION_ANIMATION_SEAMLESS).build();
+        final TransitionInfo seamlessDisplay = new TransitionInfoBuilder(TRANSIT_CHANGE)
+                .addChange(displayChange)
+                // The animation hint of task will be ignored.
+                .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
+                        .setRotate(ROTATION_ANIMATION_ROTATE).build())
+                .build();
+        assertEquals(ROTATION_ANIMATION_SEAMLESS, DefaultTransitionHandler.getRotationAnimationHint(
+                displayChange, seamlessDisplay, displays));
     }
 
     @Test
@@ -688,6 +704,204 @@
         verify(runnable4, times(1)).run();
     }
 
+    @Test
+    public void testObserverLifecycle_basicTransitionFlow() {
+        Transitions transitions = createTestTransitions();
+        Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
+        transitions.registerObserver(observer);
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        IBinder transitToken = new Binder();
+        transitions.requestStartTransition(transitToken,
+                new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+        TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+        SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+        transitions.onTransitionReady(transitToken, info, startT, finishT);
+
+        InOrder observerOrder = inOrder(observer);
+        observerOrder.verify(observer).onTransitionReady(transitToken, info, startT, finishT);
+        observerOrder.verify(observer).onTransitionStarting(transitToken);
+        verify(observer, times(0)).onTransitionFinished(eq(transitToken), anyBoolean());
+        mDefaultHandler.finishAll();
+        mMainExecutor.flushAll();
+        verify(observer).onTransitionFinished(transitToken, false);
+    }
+
+    @Test
+    public void testObserverLifecycle_queueing() {
+        Transitions transitions = createTestTransitions();
+        Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
+        transitions.registerObserver(observer);
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        IBinder transitToken1 = new Binder();
+        transitions.requestStartTransition(transitToken1,
+                new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+        TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+        SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
+        transitions.onTransitionReady(transitToken1, info1, startT1, finishT1);
+        verify(observer).onTransitionReady(transitToken1, info1, startT1, finishT1);
+
+        IBinder transitToken2 = new Binder();
+        transitions.requestStartTransition(transitToken2,
+                new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
+        TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
+                .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+        SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
+        transitions.onTransitionReady(transitToken2, info2, startT2, finishT2);
+        verify(observer, times(1)).onTransitionReady(transitToken2, info2, startT2, finishT2);
+        verify(observer, times(0)).onTransitionStarting(transitToken2);
+        verify(observer, times(0)).onTransitionFinished(eq(transitToken1), anyBoolean());
+        verify(observer, times(0)).onTransitionFinished(eq(transitToken2), anyBoolean());
+
+        mDefaultHandler.finishAll();
+        mMainExecutor.flushAll();
+        // first transition finished
+        verify(observer, times(1)).onTransitionFinished(transitToken1, false);
+        verify(observer, times(1)).onTransitionStarting(transitToken2);
+        verify(observer, times(0)).onTransitionFinished(eq(transitToken2), anyBoolean());
+
+        mDefaultHandler.finishAll();
+        mMainExecutor.flushAll();
+        verify(observer, times(1)).onTransitionFinished(transitToken2, false);
+    }
+
+
+    @Test
+    public void testObserverLifecycle_merging() {
+        Transitions transitions = createTestTransitions();
+        Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
+        transitions.registerObserver(observer);
+        mDefaultHandler.setSimulateMerge(true);
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        IBinder transitToken1 = new Binder();
+        transitions.requestStartTransition(transitToken1,
+                new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+        TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+        SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
+        transitions.onTransitionReady(transitToken1, info1, startT1, finishT1);
+
+        IBinder transitToken2 = new Binder();
+        transitions.requestStartTransition(transitToken2,
+                new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
+        TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
+                .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+        SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
+        transitions.onTransitionReady(transitToken2, info2, startT2, finishT2);
+
+        InOrder observerOrder = inOrder(observer);
+        observerOrder.verify(observer).onTransitionReady(transitToken2, info2, startT2, finishT2);
+        observerOrder.verify(observer).onTransitionMerged(transitToken2, transitToken1);
+        verify(observer, times(0)).onTransitionFinished(eq(transitToken1), anyBoolean());
+
+        mDefaultHandler.finishAll();
+        mMainExecutor.flushAll();
+        // transition + merged all finished.
+        verify(observer, times(1)).onTransitionFinished(transitToken1, false);
+        // Merged transition won't receive any lifecycle calls beyond ready
+        verify(observer, times(0)).onTransitionStarting(transitToken2);
+        verify(observer, times(0)).onTransitionFinished(eq(transitToken2), anyBoolean());
+    }
+
+    @Test
+    public void testObserverLifecycle_mergingAfterQueueing() {
+        Transitions transitions = createTestTransitions();
+        Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
+        transitions.registerObserver(observer);
+        mDefaultHandler.setSimulateMerge(true);
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        // Make a test handler that only responds to multi-window triggers AND only animates
+        // Change transitions.
+        final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
+        TestTransitionHandler testHandler = new TestTransitionHandler() {
+            @Override
+            public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+                    @NonNull SurfaceControl.Transaction startTransaction,
+                    @NonNull SurfaceControl.Transaction finishTransaction,
+                    @NonNull Transitions.TransitionFinishCallback finishCallback) {
+                for (TransitionInfo.Change chg : info.getChanges()) {
+                    if (chg.getMode() == TRANSIT_CHANGE) {
+                        return super.startAnimation(transition, info, startTransaction,
+                                finishTransaction, finishCallback);
+                    }
+                }
+                return false;
+            }
+
+            @Nullable
+            @Override
+            public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+                    @NonNull TransitionRequestInfo request) {
+                final RunningTaskInfo task = request.getTriggerTask();
+                return (task != null && task.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW)
+                        ? handlerWCT : null;
+            }
+        };
+        transitions.addHandler(testHandler);
+
+        // Use test handler to play an animation
+        IBinder transitToken1 = new Binder();
+        RunningTaskInfo mwTaskInfo =
+                createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+        transitions.requestStartTransition(transitToken1,
+                new TransitionRequestInfo(TRANSIT_CHANGE, mwTaskInfo, null /* remote */));
+        TransitionInfo change = new TransitionInfoBuilder(TRANSIT_CHANGE)
+                .addChange(TRANSIT_CHANGE).build();
+        SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
+        transitions.onTransitionReady(transitToken1, change, startT1, finishT1);
+
+        // Request the second transition that should be handled by the default handler
+        IBinder transitToken2 = new Binder();
+        TransitionInfo open = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+        transitions.requestStartTransition(transitToken2,
+                new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+        SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
+        transitions.onTransitionReady(transitToken2, open, startT2, finishT2);
+        verify(observer).onTransitionReady(transitToken2, open, startT2, finishT2);
+        verify(observer, times(0)).onTransitionStarting(transitToken2);
+
+        // Request the third transition that should be merged into the second one
+        IBinder transitToken3 = new Binder();
+        transitions.requestStartTransition(transitToken3,
+                new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+        SurfaceControl.Transaction startT3 = mock(SurfaceControl.Transaction.class);
+        SurfaceControl.Transaction finishT3 = mock(SurfaceControl.Transaction.class);
+        transitions.onTransitionReady(transitToken3, open, startT3, finishT3);
+        verify(observer, times(0)).onTransitionStarting(transitToken2);
+        verify(observer).onTransitionReady(transitToken3, open, startT3, finishT3);
+        verify(observer, times(0)).onTransitionStarting(transitToken3);
+
+        testHandler.finishAll();
+        mMainExecutor.flushAll();
+
+        verify(observer).onTransitionFinished(transitToken1, false);
+
+        mDefaultHandler.finishAll();
+        mMainExecutor.flushAll();
+
+        InOrder observerOrder = inOrder(observer);
+        observerOrder.verify(observer).onTransitionStarting(transitToken2);
+        observerOrder.verify(observer).onTransitionMerged(transitToken3, transitToken2);
+        observerOrder.verify(observer).onTransitionFinished(transitToken2, false);
+
+        // Merged transition won't receive any lifecycle calls beyond ready
+        verify(observer, times(0)).onTransitionStarting(transitToken3);
+        verify(observer, times(0)).onTransitionFinished(eq(transitToken3), anyBoolean());
+    }
+
     class TransitionInfoBuilder {
         final TransitionInfo mInfo;
 
@@ -834,16 +1048,13 @@
     }
 
     private DisplayController createTestDisplayController() {
-        IWindowManager mockWM = mock(IWindowManager.class);
-        final IDisplayWindowListener[] displayListener = new IDisplayWindowListener[1];
-        try {
-            doReturn(new int[]{DEFAULT_DISPLAY}).when(mockWM).registerDisplayWindowListener(any());
-        } catch (RemoteException e) {
-            // No remote stuff happening, so this can't be hit
-        }
-        ShellInit shellInit = new ShellInit(mMainExecutor);
-        DisplayController out = new DisplayController(mContext, mockWM, shellInit, mMainExecutor);
-        shellInit.init();
+        DisplayLayout displayLayout = mock(DisplayLayout.class);
+        doReturn(Surface.ROTATION_180).when(displayLayout).getUpsideDownRotation();
+        // By default we ignore nav bar in deciding if a seamless rotation is allowed.
+        doReturn(true).when(displayLayout).allowSeamlessRotationDespiteNavBarMoving();
+
+        DisplayController out = mock(DisplayController.class);
+        doReturn(displayLayout).when(out).getDisplayLayout(DEFAULT_DISPLAY);
         return out;
     }
 
@@ -854,17 +1065,4 @@
         shellInit.init();
         return t;
     }
-//
-//    private class TestDisplayController extends DisplayController {
-//        private final DisplayLayout mTestDisplayLayout;
-//        TestDisplayController() {
-//            super(mContext, mock(IWindowManager.class), mMainExecutor);
-//            mTestDisplayLayout = new DisplayLayout();
-//            mTestDisplayLayout.
-//        }
-//
-//        @Override
-//        DisplayLayout
-//    }
-
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 226843e..e11be31 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -24,7 +24,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.argThat;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
@@ -107,7 +106,7 @@
         mMockSurfaceControlFinishT = createMockSurfaceControlTransaction();
 
         doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
-                .create(any(), any(), any(), anyBoolean());
+                .create(any(), any(), any());
     }
 
     @Test
@@ -148,8 +147,7 @@
 
         verify(decorContainerSurfaceBuilder, never()).build();
         verify(taskBackgroundSurfaceBuilder, never()).build();
-        verify(mMockSurfaceControlViewHostFactory, never())
-                .create(any(), any(), any(), anyBoolean());
+        verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any());
 
         verify(mMockSurfaceControlFinishT).hide(taskSurface);
 
@@ -207,8 +205,7 @@
         verify(mMockSurfaceControlStartT).setLayer(taskBackgroundSurface, -1);
         verify(mMockSurfaceControlStartT).show(taskBackgroundSurface);
 
-        verify(mMockSurfaceControlViewHostFactory)
-                .create(any(), eq(defaultDisplay), any(), anyBoolean());
+        verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
         verify(mMockSurfaceControlViewHost)
                 .setView(same(mMockView),
                         argThat(lp -> lp.height == 64
@@ -326,8 +323,7 @@
         verify(mMockDisplayController).removeDisplayWindowListener(same(listener));
 
         assertThat(mRelayoutResult.mRootView).isSameInstanceAs(mMockView);
-        verify(mMockSurfaceControlViewHostFactory)
-                .create(any(), eq(mockDisplay), any(), anyBoolean());
+        verify(mMockSurfaceControlViewHostFactory).create(any(), eq(mockDisplay), any());
         verify(mMockSurfaceControlViewHost).setView(same(mMockView), any());
     }
 
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 2beb33a..9aa3787 100755
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -141,6 +141,9 @@
       return {};
     }
     loaded_arsc = LoadedArsc::Load(data, length, loaded_idmap.get(), property_flags);
+  } else if (loaded_idmap != nullptr &&
+      IsFabricatedOverlay(std::string(loaded_idmap->OverlayApkPath()))) {
+    loaded_arsc = LoadedArsc::Load(loaded_idmap.get());
   } else {
     loaded_arsc = LoadedArsc::CreateEmpty();
   }
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 35b6170..5b69cca 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -820,6 +820,13 @@
   return true;
 }
 
+bool LoadedArsc::LoadStringPool(const LoadedIdmap* loaded_idmap) {
+  if (loaded_idmap != nullptr) {
+    global_string_pool_ = util::make_unique<OverlayStringPool>(loaded_idmap);
+  }
+  return true;
+}
+
 std::unique_ptr<LoadedArsc> LoadedArsc::Load(incfs::map_ptr<void> data,
                                              const size_t length,
                                              const LoadedIdmap* loaded_idmap,
@@ -855,6 +862,16 @@
   return loaded_arsc;
 }
 
+std::unique_ptr<LoadedArsc> LoadedArsc::Load(const LoadedIdmap* loaded_idmap) {
+  ATRACE_NAME("LoadedArsc::Load");
+
+  // Not using make_unique because the constructor is private.
+  std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc());
+  loaded_arsc->LoadStringPool(loaded_idmap);
+  return loaded_arsc;
+}
+
+
 std::unique_ptr<LoadedArsc> LoadedArsc::CreateEmpty() {
   return std::unique_ptr<LoadedArsc>(new LoadedArsc());
 }
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index b3d6a4d..e459639 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -314,6 +314,8 @@
                                           const LoadedIdmap* loaded_idmap = nullptr,
                                           package_property_t property_flags = 0U);
 
+  static std::unique_ptr<LoadedArsc> Load(const LoadedIdmap* loaded_idmap = nullptr);
+
   // Create an empty LoadedArsc. This is used when an APK has no resources.arsc.
   static std::unique_ptr<LoadedArsc> CreateEmpty();
 
@@ -338,6 +340,7 @@
   LoadedArsc() = default;
   bool LoadTable(
       const Chunk& chunk, const LoadedIdmap* loaded_idmap, package_property_t property_flags);
+  bool LoadStringPool(const LoadedIdmap* loaded_idmap);
 
   std::unique_ptr<ResStringPool> global_string_pool_ = util::make_unique<ResStringPool>();
   std::vector<std::unique_ptr<const LoadedPackage>> packages_;
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 3d66244..8c614bc 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -53,7 +53,7 @@
 // The version should only be changed when a backwards-incompatible change must be made to the
 // fabricated overlay file format. Old fabricated overlays must be migrated to the new file format
 // to prevent losing fabricated overlay data.
-constexpr const uint32_t kFabricatedOverlayCurrentVersion = 1;
+constexpr const uint32_t kFabricatedOverlayCurrentVersion = 2;
 
 // Returns whether or not the path represents a fabricated overlay.
 bool IsFabricatedOverlay(const std::string& path);
diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp
index 3a8e559..687e4dd 100644
--- a/libs/hwui/FrameInfoVisualizer.cpp
+++ b/libs/hwui/FrameInfoVisualizer.cpp
@@ -179,7 +179,7 @@
 void FrameInfoVisualizer::nextBarSegment(FrameInfoIndex start, FrameInfoIndex end) {
     int fast_i = (mNumFastRects - 1) * 4;
     int janky_i = (mNumJankyRects - 1) * 4;
-    ;
+
     for (size_t fi = 0; fi < mFrameSource.size(); fi++) {
         if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) {
             continue;
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 5a9d250..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.reset(new minikin::FontCollection(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;
@@ -191,8 +196,8 @@
     std::vector<std::shared_ptr<minikin::Font>> fonts;
     fonts.push_back(minikin::Font::Builder(font).build());
 
-    std::shared_ptr<minikin::FontCollection> collection = std::make_shared<minikin::FontCollection>(
-            std::make_shared<minikin::FontFamily>(std::move(fonts)));
+    std::shared_ptr<minikin::FontCollection> collection =
+            minikin::FontCollection::create(minikin::FontFamily::create(std::move(fonts)));
 
     Typeface* hwTypeface = new Typeface();
     hwTypeface->fFontCollection = collection;
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/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index acc1b04..c146ada 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -85,9 +85,9 @@
     if (builder->fonts.empty()) {
         return 0;
     }
-    std::shared_ptr<minikin::FontFamily> family = std::make_shared<minikin::FontFamily>(
-            builder->langId, builder->variant, std::move(builder->fonts),
-            true /* isCustomFallback */);
+    std::shared_ptr<minikin::FontFamily> family =
+            minikin::FontFamily::create(builder->langId, builder->variant,
+                                        std::move(builder->fonts), true /* isCustomFallback */);
     if (family->getCoverage().length() == 0) {
         return 0;
     }
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/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index b682135..fbfc07e 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -66,9 +66,9 @@
         ScopedUtfChars str(env, langTags);
         localeId = minikin::registerLocaleList(str.c_str());
     }
-    std::shared_ptr<minikin::FontFamily> family = std::make_shared<minikin::FontFamily>(
-            localeId, static_cast<minikin::FamilyVariant>(variant), std::move(builder->fonts),
-            isCustomFallback);
+    std::shared_ptr<minikin::FontFamily> family =
+            minikin::FontFamily::create(localeId, static_cast<minikin::FamilyVariant>(variant),
+                                        std::move(builder->fonts), isCustomFallback);
     if (family->getCoverage().length() == 0) {
         // No coverage means minikin rejected given font for some reasons.
         jniThrowException(env, "java/lang/IllegalArgumentException",
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 2aca41e..62e42b8 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -112,7 +112,7 @@
     if (CC_UNLIKELY(Properties::showDirtyRegions ||
                     ProfileType::None != Properties::getProfileType())) {
         SkCanvas* profileCanvas = surface->getCanvas();
-        SkiaProfileRenderer profileRenderer(profileCanvas);
+        SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
         profiler->draw(profileRenderer);
     }
 
diff --git a/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp b/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp
index 492c39f..81cfc5d 100644
--- a/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp
+++ b/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp
@@ -33,13 +33,5 @@
     }
 }
 
-uint32_t SkiaProfileRenderer::getViewportWidth() {
-    return mCanvas->imageInfo().width();
-}
-
-uint32_t SkiaProfileRenderer::getViewportHeight() {
-    return mCanvas->imageInfo().height();
-}
-
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaProfileRenderer.h b/libs/hwui/pipeline/skia/SkiaProfileRenderer.h
index dc8420f..96d2a5e 100644
--- a/libs/hwui/pipeline/skia/SkiaProfileRenderer.h
+++ b/libs/hwui/pipeline/skia/SkiaProfileRenderer.h
@@ -23,18 +23,21 @@
 
 class SkiaProfileRenderer : public IProfileRenderer {
 public:
-    explicit SkiaProfileRenderer(SkCanvas* canvas) : mCanvas(canvas) {}
+    explicit SkiaProfileRenderer(SkCanvas* canvas, uint32_t width, uint32_t height)
+            : mCanvas(canvas), mWidth(width), mHeight(height) {}
 
     void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) override;
     void drawRects(const float* rects, int count, const SkPaint& paint) override;
-    uint32_t getViewportWidth() override;
-    uint32_t getViewportHeight() override;
+    uint32_t getViewportWidth() override { return mWidth; }
+    uint32_t getViewportHeight() override { return mHeight; }
 
     virtual ~SkiaProfileRenderer() {}
 
 private:
     // Does not have ownership.
     SkCanvas* mCanvas;
+    uint32_t mWidth;
+    uint32_t mHeight;
 };
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 905d46e..53a4c60 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -88,7 +88,9 @@
     if (CC_UNLIKELY(Properties::showDirtyRegions ||
                     ProfileType::None != Properties::getProfileType())) {
         SkCanvas* profileCanvas = backBuffer->getCanvas();
-        SkiaProfileRenderer profileRenderer(profileCanvas);
+        SkAutoCanvasRestore saver(profileCanvas, true);
+        profileCanvas->concat(mVkSurface->getCurrentPreTransform());
+        SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
         profiler->draw(profileRenderer);
     }
 
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 976117b..75d3ff7 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -512,9 +512,19 @@
 
     ATRACE_FORMAT("Drawing " RECT_STRING, SK_RECT_ARGS(dirty));
 
-    const auto drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
-                                                  &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
-                                                  mLightInfo, mRenderNodes, &(profiler()));
+    IRenderPipeline::DrawResult drawResult;
+    {
+        // FrameInfoVisualizer accesses the frame events, which cannot be mutated mid-draw
+        // or it can lead to memory corruption.
+        // This lock is overly broad, but it's the quickest fix since this mutex is otherwise
+        // not visible to IRenderPipeline much less FrameInfoVisualizer. And since this is
+        // the thread we're primarily concerned about being responsive, this being too broad
+        // shouldn't pose a performance issue.
+        std::scoped_lock lock(mFrameMetricsReporterMutex);
+        drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
+                                           &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
+                                           mLightInfo, mRenderNodes, &(profiler()));
+    }
 
     uint64_t frameCompleteNr = getFrameNumber();
 
@@ -754,11 +764,11 @@
     FrameInfo* frameInfo = instance->getFrameInfoFromLast4(frameNumber, surfaceControlId);
 
     if (frameInfo != nullptr) {
+        std::scoped_lock lock(instance->mFrameMetricsReporterMutex);
         frameInfo->set(FrameInfoIndex::FrameCompleted) = std::max(gpuCompleteTime,
                 frameInfo->get(FrameInfoIndex::SwapBuffersCompleted));
         frameInfo->set(FrameInfoIndex::GpuCompleted) = std::max(
                 gpuCompleteTime, frameInfo->get(FrameInfoIndex::CommandSubmissionCompleted));
-        std::scoped_lock lock(instance->mFrameMetricsReporterMutex);
         instance->mJankTracker.finishFrame(*frameInfo, instance->mFrameMetricsReporter, frameNumber,
                                            surfaceControlId);
     }
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 9295a93..499afa0 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -64,7 +64,7 @@
                                               std::vector<minikin::FontVariation>());
     std::vector<std::shared_ptr<minikin::Font>> fonts;
     fonts.push_back(minikin::Font::Builder(font).build());
-    return std::make_shared<minikin::FontFamily>(std::move(fonts));
+    return minikin::FontFamily::create(std::move(fonts));
 }
 
 std::vector<std::shared_ptr<minikin::FontFamily>> makeSingleFamlyVector(const char* fileName) {
@@ -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/location/java/android/location/Location.java b/location/java/android/location/Location.java
index 55ee3aa..f5a9850 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -47,6 +47,15 @@
  * <p>All locations generated through {@link LocationManager} are guaranteed to have a valid
  * latitude, longitude, timestamp (both Unix epoch time and elapsed realtime since boot), and
  * accuracy. All other parameters are optional.
+ *
+ * <p class="note">Note that Android provides the ability for applications to submit "mock" or faked
+ * locations through {@link LocationManager}, and that these locations can then be received by
+ * applications using LocationManager to obtain location information. These locations can be
+ * identified via the {@link #isMock()} API. Applications that wish to determine if a given location
+ * represents the best estimate of the real position of the device as opposed to a fake location
+ * coming from another application or the user should use this API. Keep in mind that the user may
+ * have a good reason for mocking their location, and thus apps should generally reject mock
+ * locations only when it is essential to their use case that only real locations are accepted.
  */
 public class Location implements Parcelable {
 
diff --git a/location/java/android/location/util/identity/CallerIdentity.java b/location/java/android/location/util/identity/CallerIdentity.java
index ade0ea4..2f8d92b 100644
--- a/location/java/android/location/util/identity/CallerIdentity.java
+++ b/location/java/android/location/util/identity/CallerIdentity.java
@@ -124,15 +124,22 @@
                 packageName, attributionTag, listenerId);
     }
 
+    // in some tests these constants are loaded too early leading to an "incorrect" view of the
+    // current pid and uid. load lazily to prevent this problem in tests.
+    private static class Loader {
+        private static final int MY_UID = Process.myUid();
+        private static final int MY_PID = Process.myPid();
+    }
+
     private final int mUid;
 
     private final int mPid;
 
     private final String mPackageName;
 
-    private final @Nullable String mAttributionTag;
+    @Nullable private final String mAttributionTag;
 
-    private final @Nullable String mListenerId;
+    @Nullable private final String mListenerId;
 
     private CallerIdentity(int uid, int pid, String packageName,
             @Nullable String attributionTag, @Nullable String listenerId) {
@@ -181,6 +188,24 @@
         return mUid == Process.SYSTEM_UID;
     }
 
+    /** Returns true if this identity represents the same user this code is running in. */
+    public boolean isMyUser() {
+        return UserHandle.getUserId(mUid) == UserHandle.getUserId(Loader.MY_UID);
+    }
+
+    /** Returns true if this identity represents the same uid this code is running in. */
+    public boolean isMyUid() {
+        return mUid == Loader.MY_UID;
+    }
+
+    /**
+     * Returns true if this identity represents the same process this code is running in. Returns
+     * false if the identity process is unknown.
+     */
+    public boolean isMyProcess() {
+        return mPid == Loader.MY_PID;
+    }
+
     /**
      * Adds this identity to the worksource supplied, or if not worksource is supplied, creates a
      * new worksource representing this identity.
diff --git a/lowpan/java/Android.bp b/lowpan/java/Android.bp
deleted file mode 100644
index 58513d7..0000000
--- a/lowpan/java/Android.bp
+++ /dev/null
@@ -1,17 +0,0 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-filegroup {
-    name: "framework-lowpan-sources",
-    srcs: [
-        "**/*.java",
-        "**/*.aidl",
-    ],
-    visibility: ["//frameworks/base"],
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl b/lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl
deleted file mode 100644
index f09dbe3..0000000
--- a/lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/** {@hide} */
-interface ILowpanEnergyScanCallback {
-    oneway void onEnergyScanResult(int channel, int rssi);
-    oneway void onEnergyScanFinished();
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanInterface.aidl b/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
deleted file mode 100644
index 603dc3c..0000000
--- a/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.net.IpPrefix;
-import android.net.lowpan.ILowpanEnergyScanCallback;
-import android.net.lowpan.ILowpanInterfaceListener;
-import android.net.lowpan.ILowpanNetScanCallback;
-import android.net.lowpan.LowpanBeaconInfo;
-import android.net.lowpan.LowpanChannelInfo;
-import android.net.lowpan.LowpanCredential;
-import android.net.lowpan.LowpanIdentity;
-import android.net.lowpan.LowpanProvision;
-
-/** {@hide} */
-interface ILowpanInterface {
-
-    // These are here for the sake of C++ interface implementations.
-
-    const String PERM_ACCESS_LOWPAN_STATE    = "android.permission.ACCESS_LOWPAN_STATE";
-    const String PERM_CHANGE_LOWPAN_STATE    = "android.permission.CHANGE_LOWPAN_STATE";
-    const String PERM_READ_LOWPAN_CREDENTIAL = "android.permission.READ_LOWPAN_CREDENTIAL";
-
-    /**
-     * Channel mask key.
-     * Used for setting a channel mask when starting a scan.
-     * Type: int[]
-     * */
-    const String KEY_CHANNEL_MASK       = "android.net.lowpan.property.CHANNEL_MASK";
-
-    /**
-     * Max Transmit Power Key.
-     * Used for setting the maximum transmit power when starting a network scan.
-     * Type: Integer
-     * */
-    const String KEY_MAX_TX_POWER       = "android.net.lowpan.property.MAX_TX_POWER";
-
-    // Interface States
-
-    const String STATE_OFFLINE = "offline";
-    const String STATE_COMMISSIONING = "commissioning";
-    const String STATE_ATTACHING = "attaching";
-    const String STATE_ATTACHED = "attached";
-    const String STATE_FAULT = "fault";
-
-    // Device Roles
-
-    const String ROLE_END_DEVICE = "end-device";
-    const String ROLE_ROUTER = "router";
-    const String ROLE_SLEEPY_END_DEVICE = "sleepy-end-device";
-    const String ROLE_SLEEPY_ROUTER = "sleepy-router";
-    const String ROLE_LEADER = "leader";
-    const String ROLE_COORDINATOR = "coordinator";
-    const String ROLE_DETACHED = "detached";
-
-    const String NETWORK_TYPE_UNKNOWN = "unknown";
-
-    /**
-     * Network type for Thread 1.x networks.
-     *
-     * @see android.net.lowpan.LowpanIdentity#getType
-     * @see #getLowpanIdentity
-     */
-    const String NETWORK_TYPE_THREAD_V1 = "org.threadgroup.thread.v1";
-
-    // Service-Specific Error Code Constants
-
-    const int ERROR_UNSPECIFIED = 1;
-    const int ERROR_INVALID_ARGUMENT = 2;
-    const int ERROR_DISABLED = 3;
-    const int ERROR_WRONG_STATE = 4;
-    const int ERROR_TIMEOUT = 5;
-    const int ERROR_IO_FAILURE = 6;
-    const int ERROR_NCP_PROBLEM = 7;
-    const int ERROR_BUSY = 8;
-    const int ERROR_ALREADY = 9;
-    const int ERROR_CANCELED = 10;
-    const int ERROR_FEATURE_NOT_SUPPORTED = 11;
-    const int ERROR_JOIN_FAILED_UNKNOWN = 12;
-    const int ERROR_JOIN_FAILED_AT_SCAN = 13;
-    const int ERROR_JOIN_FAILED_AT_AUTH = 14;
-    const int ERROR_FORM_FAILED_AT_SCAN = 15;
-
-    // Methods
-
-    @utf8InCpp String getName();
-
-    @utf8InCpp String getNcpVersion();
-    @utf8InCpp String getDriverVersion();
-    LowpanChannelInfo[] getSupportedChannels();
-    @utf8InCpp String[] getSupportedNetworkTypes();
-    byte[] getMacAddress();
-
-    boolean isEnabled();
-    void setEnabled(boolean enabled);
-
-    boolean isUp();
-    boolean isCommissioned();
-    boolean isConnected();
-    @utf8InCpp String getState();
-
-    @utf8InCpp String getRole();
-    @utf8InCpp String getPartitionId();
-    byte[] getExtendedAddress();
-
-    LowpanIdentity getLowpanIdentity();
-    LowpanCredential getLowpanCredential();
-
-    @utf8InCpp String[] getLinkAddresses();
-    IpPrefix[] getLinkNetworks();
-
-    void join(in LowpanProvision provision);
-    void form(in LowpanProvision provision);
-    void attach(in LowpanProvision provision);
-    void leave();
-    void reset();
-
-    void startCommissioningSession(in LowpanBeaconInfo beaconInfo);
-    void closeCommissioningSession();
-    oneway void sendToCommissioner(in byte[] packet);
-
-    void beginLowPower();
-    oneway void pollForData();
-
-    oneway void onHostWake();
-
-    void addListener(ILowpanInterfaceListener listener);
-    oneway void removeListener(ILowpanInterfaceListener listener);
-
-    void startNetScan(in Map properties, ILowpanNetScanCallback listener);
-    oneway void stopNetScan();
-
-    void startEnergyScan(in Map properties, ILowpanEnergyScanCallback listener);
-    oneway void stopEnergyScan();
-
-    void addOnMeshPrefix(in IpPrefix prefix, int flags);
-    oneway void removeOnMeshPrefix(in IpPrefix prefix);
-
-    void addExternalRoute(in IpPrefix prefix, int flags);
-    oneway void removeExternalRoute(in IpPrefix prefix);
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl b/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
deleted file mode 100644
index 5e4049a..0000000
--- a/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.net.IpPrefix;
-import android.net.lowpan.LowpanIdentity;
-
-/** {@hide} */
-interface ILowpanInterfaceListener {
-    oneway void onEnabledChanged(boolean value);
-
-    oneway void onConnectedChanged(boolean value);
-
-    oneway void onUpChanged(boolean value);
-
-    oneway void onRoleChanged(@utf8InCpp String value);
-
-    oneway void onStateChanged(@utf8InCpp String value);
-
-    oneway void onLowpanIdentityChanged(in LowpanIdentity value);
-
-    oneway void onLinkNetworkAdded(in IpPrefix value);
-
-    oneway void onLinkNetworkRemoved(in IpPrefix value);
-
-    oneway void onLinkAddressAdded(@utf8InCpp String value);
-
-    oneway void onLinkAddressRemoved(@utf8InCpp String value);
-
-    oneway void onReceiveFromCommissioner(in byte[] packet);
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanManager.aidl b/lowpan/java/android/net/lowpan/ILowpanManager.aidl
deleted file mode 100644
index 326aa65..0000000
--- a/lowpan/java/android/net/lowpan/ILowpanManager.aidl
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-import android.net.lowpan.ILowpanInterface;
-import android.net.lowpan.ILowpanManagerListener;
-
-/** {@hide} */
-interface ILowpanManager {
-
-    /* Keep this in sync with Context.LOWPAN_SERVICE */
-    const String LOWPAN_SERVICE_NAME = "lowpan";
-
-    ILowpanInterface getInterface(@utf8InCpp String name);
-
-    @utf8InCpp String[] getInterfaceList();
-
-    void addListener(ILowpanManagerListener listener);
-    void removeListener(ILowpanManagerListener listener);
-
-    void addInterface(ILowpanInterface lowpan_interface);
-    void removeInterface(ILowpanInterface lowpan_interface);
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl b/lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl
deleted file mode 100644
index d4846f6..0000000
--- a/lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.net.lowpan.ILowpanInterface;
-
-/** {@hide} */
-interface ILowpanManagerListener {
-    oneway void onInterfaceAdded(ILowpanInterface lowpanInterface);
-    oneway void onInterfaceRemoved(ILowpanInterface lowpanInterface);
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl b/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl
deleted file mode 100644
index 9743fce..0000000
--- a/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.net.lowpan.LowpanBeaconInfo;
-
-/** {@hide} */
-interface ILowpanNetScanCallback {
-    oneway void onNetScanBeacon(in LowpanBeaconInfo beacon);
-    oneway void onNetScanFinished();
-}
diff --git a/lowpan/java/android/net/lowpan/InterfaceDisabledException.java b/lowpan/java/android/net/lowpan/InterfaceDisabledException.java
deleted file mode 100644
index e917d45..0000000
--- a/lowpan/java/android/net/lowpan/InterfaceDisabledException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating this operation requires the interface to be enabled.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class InterfaceDisabledException extends LowpanException {
-
-    public InterfaceDisabledException() {}
-
-    public InterfaceDisabledException(String message) {
-        super(message);
-    }
-
-    public InterfaceDisabledException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    protected InterfaceDisabledException(Exception cause) {
-        super(cause);
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/JoinFailedAtAuthException.java b/lowpan/java/android/net/lowpan/JoinFailedAtAuthException.java
deleted file mode 100644
index 7aceb71..0000000
--- a/lowpan/java/android/net/lowpan/JoinFailedAtAuthException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the join operation was unable to find the given network.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class JoinFailedAtAuthException extends JoinFailedException {
-
-    public JoinFailedAtAuthException() {}
-
-    public JoinFailedAtAuthException(String message) {
-        super(message);
-    }
-
-    public JoinFailedAtAuthException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public JoinFailedAtAuthException(Exception cause) {
-        super(cause);
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/JoinFailedAtScanException.java b/lowpan/java/android/net/lowpan/JoinFailedAtScanException.java
deleted file mode 100644
index a4346f98..0000000
--- a/lowpan/java/android/net/lowpan/JoinFailedAtScanException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the join operation was unable to find the given network.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class JoinFailedAtScanException extends JoinFailedException {
-
-    public JoinFailedAtScanException() {}
-
-    public JoinFailedAtScanException(String message) {
-        super(message);
-    }
-
-    public JoinFailedAtScanException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public JoinFailedAtScanException(Exception cause) {
-        super(cause);
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/JoinFailedException.java b/lowpan/java/android/net/lowpan/JoinFailedException.java
deleted file mode 100644
index e51d382..0000000
--- a/lowpan/java/android/net/lowpan/JoinFailedException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the join operation has failed.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class JoinFailedException extends LowpanException {
-
-    public JoinFailedException() {}
-
-    public JoinFailedException(String message) {
-        super(message);
-    }
-
-    public JoinFailedException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    protected JoinFailedException(Exception cause) {
-        super(cause);
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.aidl b/lowpan/java/android/net/lowpan/LowpanBeaconInfo.aidl
deleted file mode 100644
index 9464fea..0000000
--- a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-parcelable LowpanBeaconInfo cpp_header "android/net/lowpan/LowpanBeaconInfo.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java b/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java
deleted file mode 100644
index 5d4a3a0..0000000
--- a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import com.android.internal.util.HexDump;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Objects;
-import java.util.TreeSet;
-
-/**
- * Describes a LoWPAN Beacon
- *
- * @hide
- */
-// @SystemApi
-public class LowpanBeaconInfo implements Parcelable {
-    public static final int UNKNOWN_RSSI = Integer.MAX_VALUE;
-    public static final int UNKNOWN_LQI = 0;
-
-    private LowpanIdentity mIdentity;
-    private int mRssi = UNKNOWN_RSSI;
-    private int mLqi = UNKNOWN_LQI;
-    private byte[] mBeaconAddress = null;
-    private final TreeSet<Integer> mFlags = new TreeSet<>();
-
-    public static final int FLAG_CAN_ASSIST = 1;
-
-    /** @hide */
-    public static class Builder {
-        final LowpanIdentity.Builder mIdentityBuilder = new LowpanIdentity.Builder();
-        final LowpanBeaconInfo mBeaconInfo = new LowpanBeaconInfo();
-
-        public Builder setLowpanIdentity(LowpanIdentity x) {
-            mIdentityBuilder.setLowpanIdentity(x);
-            return this;
-        }
-
-        public Builder setName(String x) {
-            mIdentityBuilder.setName(x);
-            return this;
-        }
-
-        public Builder setXpanid(byte x[]) {
-            mIdentityBuilder.setXpanid(x);
-            return this;
-        }
-
-        public Builder setPanid(int x) {
-            mIdentityBuilder.setPanid(x);
-            return this;
-        }
-
-        public Builder setChannel(int x) {
-            mIdentityBuilder.setChannel(x);
-            return this;
-        }
-
-        public Builder setType(String x) {
-            mIdentityBuilder.setType(x);
-            return this;
-        }
-
-        public Builder setRssi(int x) {
-            mBeaconInfo.mRssi = x;
-            return this;
-        }
-
-        public Builder setLqi(int x) {
-            mBeaconInfo.mLqi = x;
-            return this;
-        }
-
-        public Builder setBeaconAddress(byte x[]) {
-            mBeaconInfo.mBeaconAddress = (x != null ? x.clone() : null);
-            return this;
-        }
-
-        public Builder setFlag(int x) {
-            mBeaconInfo.mFlags.add(x);
-            return this;
-        }
-
-        public Builder setFlags(Collection<Integer> x) {
-            mBeaconInfo.mFlags.addAll(x);
-            return this;
-        }
-
-        public LowpanBeaconInfo build() {
-            mBeaconInfo.mIdentity = mIdentityBuilder.build();
-            if (mBeaconInfo.mBeaconAddress == null) {
-                mBeaconInfo.mBeaconAddress = new byte[0];
-            }
-            return mBeaconInfo;
-        }
-    }
-
-    private LowpanBeaconInfo() {}
-
-    public LowpanIdentity getLowpanIdentity() {
-        return mIdentity;
-    }
-
-    public int getRssi() {
-        return mRssi;
-    }
-
-    public int getLqi() {
-        return mLqi;
-    }
-
-    public byte[] getBeaconAddress() {
-        return mBeaconAddress.clone();
-    }
-
-    public Collection<Integer> getFlags() {
-        return (Collection<Integer>) mFlags.clone();
-    }
-
-    public boolean isFlagSet(int flag) {
-        return mFlags.contains(flag);
-    }
-
-    @Override
-    public String toString() {
-        StringBuffer sb = new StringBuffer();
-
-        sb.append(mIdentity.toString());
-
-        if (mRssi != UNKNOWN_RSSI) {
-            sb.append(", RSSI:").append(mRssi).append("dBm");
-        }
-
-        if (mLqi != UNKNOWN_LQI) {
-            sb.append(", LQI:").append(mLqi);
-        }
-
-        if (mBeaconAddress.length > 0) {
-            sb.append(", BeaconAddress:").append(HexDump.toHexString(mBeaconAddress));
-        }
-
-        for (Integer flag : mFlags) {
-            switch (flag.intValue()) {
-                case FLAG_CAN_ASSIST:
-                    sb.append(", CAN_ASSIST");
-                    break;
-                default:
-                    sb.append(", FLAG_").append(Integer.toHexString(flag));
-                    break;
-            }
-        }
-
-        return sb.toString();
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mIdentity, mRssi, mLqi, Arrays.hashCode(mBeaconAddress), mFlags);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof LowpanBeaconInfo)) {
-            return false;
-        }
-        LowpanBeaconInfo rhs = (LowpanBeaconInfo) obj;
-        return mIdentity.equals(rhs.mIdentity)
-                && Arrays.equals(mBeaconAddress, rhs.mBeaconAddress)
-                && mRssi == rhs.mRssi
-                && mLqi == rhs.mLqi
-                && mFlags.equals(rhs.mFlags);
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        mIdentity.writeToParcel(dest, flags);
-        dest.writeInt(mRssi);
-        dest.writeInt(mLqi);
-        dest.writeByteArray(mBeaconAddress);
-
-        dest.writeInt(mFlags.size());
-        for (Integer val : mFlags) {
-            dest.writeInt(val);
-        }
-    }
-
-    /** Implement the Parcelable interface. */
-    public static final @android.annotation.NonNull Creator<LowpanBeaconInfo> CREATOR =
-            new Creator<LowpanBeaconInfo>() {
-                public LowpanBeaconInfo createFromParcel(Parcel in) {
-                    Builder builder = new Builder();
-
-                    builder.setLowpanIdentity(LowpanIdentity.CREATOR.createFromParcel(in));
-
-                    builder.setRssi(in.readInt());
-                    builder.setLqi(in.readInt());
-
-                    builder.setBeaconAddress(in.createByteArray());
-
-                    for (int i = in.readInt(); i > 0; i--) {
-                        builder.setFlag(in.readInt());
-                    }
-
-                    return builder.build();
-                }
-
-                public LowpanBeaconInfo[] newArray(int size) {
-                    return new LowpanBeaconInfo[size];
-                }
-            };
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanChannelInfo.aidl b/lowpan/java/android/net/lowpan/LowpanChannelInfo.aidl
deleted file mode 100644
index 0676deb..0000000
--- a/lowpan/java/android/net/lowpan/LowpanChannelInfo.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-parcelable LowpanChannelInfo cpp_header "android/net/lowpan/LowpanChannelInfo.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanChannelInfo.java b/lowpan/java/android/net/lowpan/LowpanChannelInfo.java
deleted file mode 100644
index 12c98b6..0000000
--- a/lowpan/java/android/net/lowpan/LowpanChannelInfo.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import java.util.Objects;
-
-/**
- * Provides detailed information about a given channel.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanChannelInfo implements Parcelable {
-
-    public static final int UNKNOWN_POWER = Integer.MAX_VALUE;
-    public static final float UNKNOWN_FREQUENCY = 0.0f;
-    public static final float UNKNOWN_BANDWIDTH = 0.0f;
-
-    private int mIndex = 0;
-    private String mName = null;
-    private float mSpectrumCenterFrequency = UNKNOWN_FREQUENCY;
-    private float mSpectrumBandwidth = UNKNOWN_BANDWIDTH;
-    private int mMaxTransmitPower = UNKNOWN_POWER;
-    private boolean mIsMaskedByRegulatoryDomain = false;
-
-    /** @hide */
-    public static LowpanChannelInfo getChannelInfoForIeee802154Page0(int index) {
-        LowpanChannelInfo info = new LowpanChannelInfo();
-
-        if (index < 0) {
-            info = null;
-
-        } else if (index == 0) {
-            info.mSpectrumCenterFrequency = 868300000.0f;
-            info.mSpectrumBandwidth = 600000.0f;
-
-        } else if (index < 11) {
-            info.mSpectrumCenterFrequency = 906000000.0f - (2000000.0f * 1) + 2000000.0f * (index);
-            info.mSpectrumBandwidth = 0; // Unknown
-
-        } else if (index < 26) {
-            info.mSpectrumCenterFrequency =
-                    2405000000.0f - (5000000.0f * 11) + 5000000.0f * (index);
-            info.mSpectrumBandwidth = 2000000.0f;
-
-        } else {
-            info = null;
-        }
-
-        info.mName = Integer.toString(index);
-
-        return info;
-    }
-
-    private LowpanChannelInfo() {}
-
-    private LowpanChannelInfo(int index, String name, float cf, float bw) {
-        mIndex = index;
-        mName = name;
-        mSpectrumCenterFrequency = cf;
-        mSpectrumBandwidth = bw;
-    }
-
-    public String getName() {
-        return mName;
-    }
-
-    public int getIndex() {
-        return mIndex;
-    }
-
-    public int getMaxTransmitPower() {
-        return mMaxTransmitPower;
-    }
-
-    public boolean isMaskedByRegulatoryDomain() {
-        return mIsMaskedByRegulatoryDomain;
-    }
-
-    public float getSpectrumCenterFrequency() {
-        return mSpectrumCenterFrequency;
-    }
-
-    public float getSpectrumBandwidth() {
-        return mSpectrumBandwidth;
-    }
-
-    @Override
-    public String toString() {
-        StringBuffer sb = new StringBuffer();
-
-        sb.append("Channel ").append(mIndex);
-
-        if (mName != null && !mName.equals(Integer.toString(mIndex))) {
-            sb.append(" (").append(mName).append(")");
-        }
-
-        if (mSpectrumCenterFrequency > 0.0f) {
-            if (mSpectrumCenterFrequency > 1000000000.0f) {
-                sb.append(", SpectrumCenterFrequency: ")
-                        .append(mSpectrumCenterFrequency / 1000000000.0f)
-                        .append("GHz");
-            } else if (mSpectrumCenterFrequency > 1000000.0f) {
-                sb.append(", SpectrumCenterFrequency: ")
-                        .append(mSpectrumCenterFrequency / 1000000.0f)
-                        .append("MHz");
-            } else {
-                sb.append(", SpectrumCenterFrequency: ")
-                        .append(mSpectrumCenterFrequency / 1000.0f)
-                        .append("kHz");
-            }
-        }
-
-        if (mSpectrumBandwidth > 0.0f) {
-            if (mSpectrumBandwidth > 1000000000.0f) {
-                sb.append(", SpectrumBandwidth: ")
-                        .append(mSpectrumBandwidth / 1000000000.0f)
-                        .append("GHz");
-            } else if (mSpectrumBandwidth > 1000000.0f) {
-                sb.append(", SpectrumBandwidth: ")
-                        .append(mSpectrumBandwidth / 1000000.0f)
-                        .append("MHz");
-            } else {
-                sb.append(", SpectrumBandwidth: ")
-                        .append(mSpectrumBandwidth / 1000.0f)
-                        .append("kHz");
-            }
-        }
-
-        if (mMaxTransmitPower != UNKNOWN_POWER) {
-            sb.append(", MaxTransmitPower: ").append(mMaxTransmitPower).append("dBm");
-        }
-
-        return sb.toString();
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof LowpanChannelInfo)) {
-            return false;
-        }
-        LowpanChannelInfo rhs = (LowpanChannelInfo) obj;
-        return Objects.equals(mName, rhs.mName)
-                && mIndex == rhs.mIndex
-                && mIsMaskedByRegulatoryDomain == rhs.mIsMaskedByRegulatoryDomain
-                && mSpectrumCenterFrequency == rhs.mSpectrumCenterFrequency
-                && mSpectrumBandwidth == rhs.mSpectrumBandwidth
-                && mMaxTransmitPower == rhs.mMaxTransmitPower;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(
-                mName,
-                mIndex,
-                mIsMaskedByRegulatoryDomain,
-                mSpectrumCenterFrequency,
-                mSpectrumBandwidth,
-                mMaxTransmitPower);
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mIndex);
-        dest.writeString(mName);
-        dest.writeFloat(mSpectrumCenterFrequency);
-        dest.writeFloat(mSpectrumBandwidth);
-        dest.writeInt(mMaxTransmitPower);
-        dest.writeBoolean(mIsMaskedByRegulatoryDomain);
-    }
-
-    /** Implement the Parcelable interface. */
-    public static final @android.annotation.NonNull Creator<LowpanChannelInfo> CREATOR =
-            new Creator<LowpanChannelInfo>() {
-
-                public LowpanChannelInfo createFromParcel(Parcel in) {
-                    LowpanChannelInfo info = new LowpanChannelInfo();
-
-                    info.mIndex = in.readInt();
-                    info.mName = in.readString();
-                    info.mSpectrumCenterFrequency = in.readFloat();
-                    info.mSpectrumBandwidth = in.readFloat();
-                    info.mMaxTransmitPower = in.readInt();
-                    info.mIsMaskedByRegulatoryDomain = in.readBoolean();
-
-                    return info;
-                }
-
-                public LowpanChannelInfo[] newArray(int size) {
-                    return new LowpanChannelInfo[size];
-                }
-            };
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java b/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java
deleted file mode 100644
index 8f75e8d..0000000
--- a/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.IpPrefix;
-import android.os.DeadObjectException;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
-
-/**
- * Commissioning Session.
- *
- * <p>This class enables a device to learn the credential needed to join a network using a technique
- * called "in-band commissioning".
- *
- * @hide
- */
-// @SystemApi
-public class LowpanCommissioningSession {
-
-    private final ILowpanInterface mBinder;
-    private final LowpanBeaconInfo mBeaconInfo;
-    private final ILowpanInterfaceListener mInternalCallback = new InternalCallback();
-    private final Looper mLooper;
-    private Handler mHandler;
-    private Callback mCallback = null;
-    private volatile boolean mIsClosed = false;
-
-    /**
-     * Callback base class for {@link LowpanCommissioningSession}
-     *
-     * @hide
-     */
-    // @SystemApi
-    public abstract static class Callback {
-        public void onReceiveFromCommissioner(@NonNull byte[] packet) {};
-
-        public void onClosed() {};
-    }
-
-    private class InternalCallback extends ILowpanInterfaceListener.Stub {
-        @Override
-        public void onStateChanged(String value) {
-            if (!mIsClosed) {
-                switch (value) {
-                    case ILowpanInterface.STATE_OFFLINE:
-                    case ILowpanInterface.STATE_FAULT:
-                        synchronized (LowpanCommissioningSession.this) {
-                            lockedCleanup();
-                        }
-                }
-            }
-        }
-
-        @Override
-        public void onReceiveFromCommissioner(byte[] packet) {
-            mHandler.post(
-                    () -> {
-                        synchronized (LowpanCommissioningSession.this) {
-                            if (!mIsClosed && (mCallback != null)) {
-                                mCallback.onReceiveFromCommissioner(packet);
-                            }
-                        }
-                    });
-        }
-
-        // We ignore all other callbacks.
-        @Override
-        public void onEnabledChanged(boolean value) {}
-
-        @Override
-        public void onConnectedChanged(boolean value) {}
-
-        @Override
-        public void onUpChanged(boolean value) {}
-
-        @Override
-        public void onRoleChanged(String value) {}
-
-        @Override
-        public void onLowpanIdentityChanged(LowpanIdentity value) {}
-
-        @Override
-        public void onLinkNetworkAdded(IpPrefix value) {}
-
-        @Override
-        public void onLinkNetworkRemoved(IpPrefix value) {}
-
-        @Override
-        public void onLinkAddressAdded(String value) {}
-
-        @Override
-        public void onLinkAddressRemoved(String value) {}
-    }
-
-    LowpanCommissioningSession(
-            ILowpanInterface binder, LowpanBeaconInfo beaconInfo, Looper looper) {
-        mBinder = binder;
-        mBeaconInfo = beaconInfo;
-        mLooper = looper;
-
-        if (mLooper != null) {
-            mHandler = new Handler(mLooper);
-        } else {
-            mHandler = new Handler();
-        }
-
-        try {
-            mBinder.addListener(mInternalCallback);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    private void lockedCleanup() {
-        // Note: this method is only called from synchronized contexts.
-
-        if (!mIsClosed) {
-            try {
-                mBinder.removeListener(mInternalCallback);
-
-            } catch (DeadObjectException x) {
-                /* We don't care if we receive a DOE at this point.
-                 * DOE is as good as success as far as we are concerned.
-                 */
-
-            } catch (RemoteException x) {
-                throw x.rethrowAsRuntimeException();
-            }
-
-            if (mCallback != null) {
-                mHandler.post(() -> mCallback.onClosed());
-            }
-        }
-
-        mCallback = null;
-        mIsClosed = true;
-    }
-
-    /** TODO: doc */
-    @NonNull
-    public LowpanBeaconInfo getBeaconInfo() {
-        return mBeaconInfo;
-    }
-
-    /** TODO: doc */
-    public void sendToCommissioner(@NonNull byte[] packet) {
-        if (!mIsClosed) {
-            try {
-                mBinder.sendToCommissioner(packet);
-
-            } catch (DeadObjectException x) {
-                /* This method is a best-effort delivery.
-                 * We don't care if we receive a DOE at this point.
-                 */
-
-            } catch (RemoteException x) {
-                throw x.rethrowAsRuntimeException();
-            }
-        }
-    }
-
-    /** TODO: doc */
-    public synchronized void setCallback(@Nullable Callback cb, @Nullable Handler handler) {
-        if (!mIsClosed) {
-            /* This class can be created with or without a default looper.
-             * Also, this method can be called with or without a specific
-             * handler. If a handler is specified, it is to always be used.
-             * Otherwise, if there was a Looper specified when this object
-             * was created, we create a new handle based on that looper.
-             * Otherwise we just create a default handler object. Since we
-             * don't really know how the previous handler was created, we
-             * end up always replacing it here. This isn't a huge problem
-             * because this method should be called infrequently.
-             */
-            if (handler != null) {
-                mHandler = handler;
-            } else if (mLooper != null) {
-                mHandler = new Handler(mLooper);
-            } else {
-                mHandler = new Handler();
-            }
-            mCallback = cb;
-        }
-    }
-
-    /** TODO: doc */
-    public synchronized void close() {
-        if (!mIsClosed) {
-            try {
-                mBinder.closeCommissioningSession();
-
-                lockedCleanup();
-
-            } catch (DeadObjectException x) {
-                /* We don't care if we receive a DOE at this point.
-                 * DOE is as good as success as far as we are concerned.
-                 */
-
-            } catch (RemoteException x) {
-                throw x.rethrowAsRuntimeException();
-            }
-        }
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanCredential.aidl b/lowpan/java/android/net/lowpan/LowpanCredential.aidl
deleted file mode 100644
index af0c2d6..0000000
--- a/lowpan/java/android/net/lowpan/LowpanCredential.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-parcelable LowpanCredential cpp_header "android/net/lowpan/LowpanCredential.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanCredential.java b/lowpan/java/android/net/lowpan/LowpanCredential.java
deleted file mode 100644
index dcbb831..0000000
--- a/lowpan/java/android/net/lowpan/LowpanCredential.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import com.android.internal.util.HexDump;
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * Describes a credential for a LoWPAN network.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanCredential implements Parcelable {
-
-    public static final int UNSPECIFIED_KEY_INDEX = 0;
-
-    private byte[] mMasterKey = null;
-    private int mMasterKeyIndex = UNSPECIFIED_KEY_INDEX;
-
-    LowpanCredential() {}
-
-    private LowpanCredential(byte[] masterKey, int keyIndex) {
-        setMasterKey(masterKey, keyIndex);
-    }
-
-    private LowpanCredential(byte[] masterKey) {
-        setMasterKey(masterKey);
-    }
-
-    public static LowpanCredential createMasterKey(byte[] masterKey) {
-        return new LowpanCredential(masterKey);
-    }
-
-    public static LowpanCredential createMasterKey(byte[] masterKey, int keyIndex) {
-        return new LowpanCredential(masterKey, keyIndex);
-    }
-
-    void setMasterKey(byte[] masterKey) {
-        if (masterKey != null) {
-            masterKey = masterKey.clone();
-        }
-        mMasterKey = masterKey;
-    }
-
-    void setMasterKeyIndex(int keyIndex) {
-        mMasterKeyIndex = keyIndex;
-    }
-
-    void setMasterKey(byte[] masterKey, int keyIndex) {
-        setMasterKey(masterKey);
-        setMasterKeyIndex(keyIndex);
-    }
-
-    public byte[] getMasterKey() {
-        if (mMasterKey != null) {
-            return mMasterKey.clone();
-        }
-        return null;
-    }
-
-    public int getMasterKeyIndex() {
-        return mMasterKeyIndex;
-    }
-
-    public boolean isMasterKey() {
-        return mMasterKey != null;
-    }
-
-    public String toSensitiveString() {
-        StringBuffer sb = new StringBuffer();
-
-        sb.append("<LowpanCredential");
-
-        if (isMasterKey()) {
-            sb.append(" MasterKey:").append(HexDump.toHexString(mMasterKey));
-            if (mMasterKeyIndex != UNSPECIFIED_KEY_INDEX) {
-                sb.append(", Index:").append(mMasterKeyIndex);
-            }
-        } else {
-            sb.append(" empty");
-        }
-
-        sb.append(">");
-
-        return sb.toString();
-    }
-
-    @Override
-    public String toString() {
-        StringBuffer sb = new StringBuffer();
-
-        sb.append("<LowpanCredential");
-
-        if (isMasterKey()) {
-            // We don't print out the contents of the key here,
-            // we only do that in toSensitiveString.
-            sb.append(" MasterKey");
-            if (mMasterKeyIndex != UNSPECIFIED_KEY_INDEX) {
-                sb.append(", Index:").append(mMasterKeyIndex);
-            }
-        } else {
-            sb.append(" empty");
-        }
-
-        sb.append(">");
-
-        return sb.toString();
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof LowpanCredential)) {
-            return false;
-        }
-        LowpanCredential rhs = (LowpanCredential) obj;
-        return Arrays.equals(mMasterKey, rhs.mMasterKey) && mMasterKeyIndex == rhs.mMasterKeyIndex;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(Arrays.hashCode(mMasterKey), mMasterKeyIndex);
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeByteArray(mMasterKey);
-        dest.writeInt(mMasterKeyIndex);
-    }
-
-    /** Implement the Parcelable interface. */
-    public static final @android.annotation.NonNull Creator<LowpanCredential> CREATOR =
-            new Creator<LowpanCredential>() {
-
-                public LowpanCredential createFromParcel(Parcel in) {
-                    LowpanCredential credential = new LowpanCredential();
-
-                    credential.mMasterKey = in.createByteArray();
-                    credential.mMasterKeyIndex = in.readInt();
-
-                    return credential;
-                }
-
-                public LowpanCredential[] newArray(int size) {
-                    return new LowpanCredential[size];
-                }
-            };
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java b/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java
deleted file mode 100644
index da87752..0000000
--- a/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Describes the result from one channel of an energy scan.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanEnergyScanResult {
-    public static final int UNKNOWN = Integer.MAX_VALUE;
-
-    private int mChannel = UNKNOWN;
-    private int mMaxRssi = UNKNOWN;
-
-    LowpanEnergyScanResult() {}
-
-    public int getChannel() {
-        return mChannel;
-    }
-
-    public int getMaxRssi() {
-        return mMaxRssi;
-    }
-
-    void setChannel(int x) {
-        mChannel = x;
-    }
-
-    void setMaxRssi(int x) {
-        mMaxRssi = x;
-    }
-
-    @Override
-    public String toString() {
-        return "LowpanEnergyScanResult(channel: " + mChannel + ", maxRssi:" + mMaxRssi + ")";
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanException.java b/lowpan/java/android/net/lowpan/LowpanException.java
deleted file mode 100644
index 5dfce48..0000000
--- a/lowpan/java/android/net/lowpan/LowpanException.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.os.ServiceSpecificException;
-import android.util.AndroidException;
-
-/**
- * <code>LowpanException</code> is thrown if an action to a LoWPAN interface could not be performed
- * or a LoWPAN interface property could not be fetched or changed.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class LowpanException extends AndroidException {
-    public LowpanException() {}
-
-    public LowpanException(String message) {
-        super(message);
-    }
-
-    public LowpanException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public LowpanException(Exception cause) {
-        super(cause);
-    }
-
-    /* This method returns LowpanException so that the caller
-     * can add "throw" before the invocation of this method.
-     * This might seem superfluous, but it is actually to
-     * help provide a hint to the java compiler that this
-     * function will not return.
-     */
-    static LowpanException rethrowFromServiceSpecificException(ServiceSpecificException e)
-            throws LowpanException {
-        switch (e.errorCode) {
-            case ILowpanInterface.ERROR_DISABLED:
-                throw new InterfaceDisabledException(e);
-
-            case ILowpanInterface.ERROR_WRONG_STATE:
-                throw new WrongStateException(e);
-
-            case ILowpanInterface.ERROR_CANCELED:
-                throw new OperationCanceledException(e);
-
-            case ILowpanInterface.ERROR_JOIN_FAILED_UNKNOWN:
-                throw new JoinFailedException(e);
-
-            case ILowpanInterface.ERROR_JOIN_FAILED_AT_SCAN:
-                throw new JoinFailedAtScanException(e);
-
-            case ILowpanInterface.ERROR_JOIN_FAILED_AT_AUTH:
-                throw new JoinFailedAtAuthException(e);
-
-            case ILowpanInterface.ERROR_FORM_FAILED_AT_SCAN:
-                throw new NetworkAlreadyExistsException(e);
-
-            case ILowpanInterface.ERROR_FEATURE_NOT_SUPPORTED:
-                throw new LowpanException(
-                        e.getMessage() != null ? e.getMessage() : "Feature not supported", e);
-
-            case ILowpanInterface.ERROR_NCP_PROBLEM:
-                throw new LowpanRuntimeException(
-                        e.getMessage() != null ? e.getMessage() : "NCP problem", e);
-
-            case ILowpanInterface.ERROR_INVALID_ARGUMENT:
-                throw new LowpanRuntimeException(
-                        e.getMessage() != null ? e.getMessage() : "Invalid argument", e);
-
-            case ILowpanInterface.ERROR_UNSPECIFIED:
-            default:
-                throw new LowpanRuntimeException(e);
-        }
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanIdentity.java b/lowpan/java/android/net/lowpan/LowpanIdentity.java
deleted file mode 100644
index 1997bc4..0000000
--- a/lowpan/java/android/net/lowpan/LowpanIdentity.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.icu.text.StringPrep;
-import android.icu.text.StringPrepParseException;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.Log;
-import com.android.internal.util.HexDump;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * Describes an instance of a LoWPAN network.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanIdentity implements Parcelable {
-    private static final String TAG = LowpanIdentity.class.getSimpleName();
-
-    // Constants
-    public static final int UNSPECIFIED_CHANNEL = -1;
-    public static final int UNSPECIFIED_PANID = 0xFFFFFFFF;
-    // Builder
-
-    /** @hide */
-    // @SystemApi
-    public static class Builder {
-        private static final StringPrep stringPrep =
-                StringPrep.getInstance(StringPrep.RFC3920_RESOURCEPREP);
-
-        final LowpanIdentity mIdentity = new LowpanIdentity();
-
-        private static String escape(@NonNull byte[] bytes) {
-            StringBuffer sb = new StringBuffer();
-            for (byte b : bytes) {
-                if (b >= 32 && b <= 126) {
-                    sb.append((char) b);
-                } else {
-                    sb.append(String.format("\\0x%02x", b & 0xFF));
-                }
-            }
-            return sb.toString();
-        }
-
-        public Builder setLowpanIdentity(@NonNull LowpanIdentity x) {
-            Objects.requireNonNull(x);
-            setRawName(x.getRawName());
-            setXpanid(x.getXpanid());
-            setPanid(x.getPanid());
-            setChannel(x.getChannel());
-            setType(x.getType());
-            return this;
-        }
-
-        public Builder setName(@NonNull String name) {
-            Objects.requireNonNull(name);
-            try {
-                mIdentity.mName = stringPrep.prepare(name, StringPrep.DEFAULT);
-                mIdentity.mRawName = mIdentity.mName.getBytes(StandardCharsets.UTF_8);
-                mIdentity.mIsNameValid = true;
-            } catch (StringPrepParseException x) {
-                Log.w(TAG, x.toString());
-                setRawName(name.getBytes(StandardCharsets.UTF_8));
-            }
-            return this;
-        }
-
-        public Builder setRawName(@NonNull byte[] name) {
-            Objects.requireNonNull(name);
-            mIdentity.mRawName = name.clone();
-            mIdentity.mName = new String(name, StandardCharsets.UTF_8);
-            try {
-                String nameCheck = stringPrep.prepare(mIdentity.mName, StringPrep.DEFAULT);
-                mIdentity.mIsNameValid =
-                        Arrays.equals(nameCheck.getBytes(StandardCharsets.UTF_8), name);
-            } catch (StringPrepParseException x) {
-                Log.w(TAG, x.toString());
-                mIdentity.mIsNameValid = false;
-            }
-
-            // Non-normal names must be rendered differently to avoid confusion.
-            if (!mIdentity.mIsNameValid) {
-                mIdentity.mName = "«" + escape(name) + "»";
-            }
-
-            return this;
-        }
-
-        public Builder setXpanid(byte x[]) {
-            mIdentity.mXpanid = (x != null ? x.clone() : null);
-            return this;
-        }
-
-        public Builder setPanid(int x) {
-            mIdentity.mPanid = x;
-            return this;
-        }
-
-        public Builder setType(@NonNull String x) {
-            mIdentity.mType = x;
-            return this;
-        }
-
-        public Builder setChannel(int x) {
-            mIdentity.mChannel = x;
-            return this;
-        }
-
-        public LowpanIdentity build() {
-            return mIdentity;
-        }
-    }
-
-    LowpanIdentity() {}
-
-    // Instance Variables
-
-    private String mName = "";
-    private boolean mIsNameValid = true;
-    private byte[] mRawName = new byte[0];
-    private String mType = "";
-    private byte[] mXpanid = new byte[0];
-    private int mPanid = UNSPECIFIED_PANID;
-    private int mChannel = UNSPECIFIED_CHANNEL;
-
-    // Public Getters
-
-    public String getName() {
-        return mName;
-    }
-
-    public boolean isNameValid() {
-        return mIsNameValid;
-    }
-
-    public byte[] getRawName() {
-        return mRawName.clone();
-    }
-
-    public byte[] getXpanid() {
-        return mXpanid.clone();
-    }
-
-    public int getPanid() {
-        return mPanid;
-    }
-
-    public String getType() {
-        return mType;
-    }
-
-    public int getChannel() {
-        return mChannel;
-    }
-
-    @Override
-    public String toString() {
-        StringBuffer sb = new StringBuffer();
-
-        sb.append("Name:").append(getName());
-
-        if (mType.length() > 0) {
-            sb.append(", Type:").append(mType);
-        }
-
-        if (mXpanid.length > 0) {
-            sb.append(", XPANID:").append(HexDump.toHexString(mXpanid));
-        }
-
-        if (mPanid != UNSPECIFIED_PANID) {
-            sb.append(", PANID:").append(String.format("0x%04X", mPanid));
-        }
-
-        if (mChannel != UNSPECIFIED_CHANNEL) {
-            sb.append(", Channel:").append(mChannel);
-        }
-
-        return sb.toString();
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof LowpanIdentity)) {
-            return false;
-        }
-        LowpanIdentity rhs = (LowpanIdentity) obj;
-        return Arrays.equals(mRawName, rhs.mRawName)
-                && Arrays.equals(mXpanid, rhs.mXpanid)
-                && mType.equals(rhs.mType)
-                && mPanid == rhs.mPanid
-                && mChannel == rhs.mChannel;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(
-                Arrays.hashCode(mRawName), mType, Arrays.hashCode(mXpanid), mPanid, mChannel);
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeByteArray(mRawName);
-        dest.writeString(mType);
-        dest.writeByteArray(mXpanid);
-        dest.writeInt(mPanid);
-        dest.writeInt(mChannel);
-    }
-
-    /** Implement the Parcelable interface. */
-    public static final @android.annotation.NonNull Creator<LowpanIdentity> CREATOR =
-            new Creator<LowpanIdentity>() {
-
-                public LowpanIdentity createFromParcel(Parcel in) {
-                    Builder builder = new Builder();
-
-                    builder.setRawName(in.createByteArray());
-                    builder.setType(in.readString());
-                    builder.setXpanid(in.createByteArray());
-                    builder.setPanid(in.readInt());
-                    builder.setChannel(in.readInt());
-
-                    return builder.build();
-                }
-
-                public LowpanIdentity[] newArray(int size) {
-                    return new LowpanIdentity[size];
-                }
-            };
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanInterface.java b/lowpan/java/android/net/lowpan/LowpanInterface.java
deleted file mode 100644
index 57e9135..0000000
--- a/lowpan/java/android/net/lowpan/LowpanInterface.java
+++ /dev/null
@@ -1,824 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.os.DeadObjectException;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-import android.util.Log;
-import java.util.HashMap;
-
-/**
- * Class for managing a specific Low-power Wireless Personal Area Network (LoWPAN) interface.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanInterface {
-    private static final String TAG = LowpanInterface.class.getSimpleName();
-
-    /** Detached role. The interface is not currently attached to a network. */
-    public static final String ROLE_DETACHED = ILowpanInterface.ROLE_DETACHED;
-
-    /** End-device role. End devices do not route traffic for other nodes. */
-    public static final String ROLE_END_DEVICE = ILowpanInterface.ROLE_END_DEVICE;
-
-    /** Router role. Routers help route traffic around the mesh network. */
-    public static final String ROLE_ROUTER = ILowpanInterface.ROLE_ROUTER;
-
-    /**
-     * Sleepy End-Device role.
-     *
-     * <p>End devices with this role are nominally asleep, waking up periodically to check in with
-     * their parent to see if there are packets destined for them. Such devices are capable of
-     * extraordinarilly low power consumption, but packet latency can be on the order of dozens of
-     * seconds(depending on how the node is configured).
-     */
-    public static final String ROLE_SLEEPY_END_DEVICE = ILowpanInterface.ROLE_SLEEPY_END_DEVICE;
-
-    /**
-     * Sleepy-router role.
-     *
-     * <p>Routers with this role are nominally asleep, waking up periodically to check in with other
-     * routers and their children.
-     */
-    public static final String ROLE_SLEEPY_ROUTER = ILowpanInterface.ROLE_SLEEPY_ROUTER;
-
-    /** TODO: doc */
-    public static final String ROLE_LEADER = ILowpanInterface.ROLE_LEADER;
-
-    /** TODO: doc */
-    public static final String ROLE_COORDINATOR = ILowpanInterface.ROLE_COORDINATOR;
-
-    /**
-     * Offline state.
-     *
-     * <p>This is the initial state of the LoWPAN interface when the underlying driver starts. In
-     * this state the NCP is idle and not connected to any network.
-     *
-     * <p>This state can be explicitly entered by calling {@link #reset()}, {@link #leave()}, or
-     * <code>setUp(false)</code>, with the later two only working if we were not previously in the
-     * {@link #STATE_FAULT} state.
-     *
-     * @see #getState()
-     * @see #STATE_FAULT
-     */
-    public static final String STATE_OFFLINE = ILowpanInterface.STATE_OFFLINE;
-
-    /**
-     * Commissioning state.
-     *
-     * <p>The interface enters this state after a call to {@link #startCommissioningSession()}. This
-     * state may only be entered directly from the {@link #STATE_OFFLINE} state.
-     *
-     * @see #startCommissioningSession()
-     * @see #getState()
-     * @hide
-     */
-    public static final String STATE_COMMISSIONING = ILowpanInterface.STATE_COMMISSIONING;
-
-    /**
-     * Attaching state.
-     *
-     * <p>The interface enters this state when it starts the process of trying to find other nodes
-     * so that it can attach to any pre-existing network fragment, or when it is in the process of
-     * calculating the optimal values for unspecified parameters when forming a new network.
-     *
-     * <p>The interface may stay in this state for a prolonged period of time (or may spontaneously
-     * enter this state from {@link #STATE_ATTACHED}) if the underlying network technology is
-     * heirarchical (like ZigBeeIP) or if the device role is that of an "end-device" ({@link
-     * #ROLE_END_DEVICE} or {@link #ROLE_SLEEPY_END_DEVICE}). This is because such roles cannot
-     * create their own network fragments.
-     *
-     * @see #STATE_ATTACHED
-     * @see #getState()
-     */
-    public static final String STATE_ATTACHING = ILowpanInterface.STATE_ATTACHING;
-
-    /**
-     * Attached state.
-     *
-     * <p>The interface enters this state from {@link #STATE_ATTACHING} once it is actively
-     * participating on a network fragment.
-     *
-     * @see #STATE_ATTACHING
-     * @see #getState()
-     */
-    public static final String STATE_ATTACHED = ILowpanInterface.STATE_ATTACHED;
-
-    /**
-     * Fault state.
-     *
-     * <p>The interface will enter this state when the driver has detected some sort of problem from
-     * which it was not immediately able to recover.
-     *
-     * <p>This state can be entered spontaneously from any other state. Calling {@link #reset} will
-     * cause the device to return to the {@link #STATE_OFFLINE} state.
-     *
-     * @see #getState
-     * @see #STATE_OFFLINE
-     */
-    public static final String STATE_FAULT = ILowpanInterface.STATE_FAULT;
-
-    /**
-     * Network type for Thread 1.x networks.
-     *
-     * @see android.net.lowpan.LowpanIdentity#getType
-     * @see #getLowpanIdentity
-     * @hide
-     */
-    public static final String NETWORK_TYPE_THREAD_V1 = ILowpanInterface.NETWORK_TYPE_THREAD_V1;
-
-    public static final String EMPTY_PARTITION_ID = "";
-
-    /**
-     * Callback base class for LowpanInterface
-     *
-     * @hide
-     */
-    // @SystemApi
-    public abstract static class Callback {
-        public void onConnectedChanged(boolean value) {}
-
-        public void onEnabledChanged(boolean value) {}
-
-        public void onUpChanged(boolean value) {}
-
-        public void onRoleChanged(@NonNull String value) {}
-
-        public void onStateChanged(@NonNull String state) {}
-
-        public void onLowpanIdentityChanged(@NonNull LowpanIdentity value) {}
-
-        public void onLinkNetworkAdded(IpPrefix prefix) {}
-
-        public void onLinkNetworkRemoved(IpPrefix prefix) {}
-
-        public void onLinkAddressAdded(LinkAddress address) {}
-
-        public void onLinkAddressRemoved(LinkAddress address) {}
-    }
-
-    private final ILowpanInterface mBinder;
-    private final Looper mLooper;
-    private final HashMap<Integer, ILowpanInterfaceListener> mListenerMap = new HashMap<>();
-
-    /**
-     * Create a new LowpanInterface instance. Applications will almost always want to use {@link
-     * LowpanManager#getInterface LowpanManager.getInterface()} instead of this.
-     *
-     * @param context the application context
-     * @param service the Binder interface
-     * @param looper the Binder interface
-     * @hide
-     */
-    public LowpanInterface(Context context, ILowpanInterface service, Looper looper) {
-        /* We aren't currently using the context, but if we need
-         * it later on we can easily add it to the class.
-         */
-
-        mBinder = service;
-        mLooper = looper;
-    }
-
-    /**
-     * Returns the ILowpanInterface object associated with this interface.
-     *
-     * @hide
-     */
-    public ILowpanInterface getService() {
-        return mBinder;
-    }
-
-    // Public Actions
-
-    /**
-     * Form a new network with the given network information optional credential. Unspecified fields
-     * in the network information will be filled in with reasonable values. If the network
-     * credential is unspecified, one will be generated automatically.
-     *
-     * <p>This method will block until either the network was successfully formed or an error
-     * prevents the network form being formed.
-     *
-     * <p>Upon success, the interface will be up and attached to the newly formed network.
-     *
-     * @see #join(LowpanProvision)
-     */
-    public void form(@NonNull LowpanProvision provision) throws LowpanException {
-        try {
-            mBinder.form(provision);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Attempts to join a new network with the given network information. This method will block
-     * until either the network was successfully joined or an error prevented the network from being
-     * formed. Upon success, the interface will be up and attached to the newly joined network.
-     *
-     * <p>Note that “joining” is distinct from “attaching”: Joining requires at least one other peer
-     * device to be present in order for the operation to complete successfully.
-     */
-    public void join(@NonNull LowpanProvision provision) throws LowpanException {
-        try {
-            mBinder.join(provision);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Attaches to the network described by identity and credential. This is similar to {@link
-     * #join}, except that (assuming the identity and credential are valid) it will always succeed
-     * and provision the interface, even if there are no peers nearby.
-     *
-     * <p>This method will block execution until the operation has completed.
-     */
-    public void attach(@NonNull LowpanProvision provision) throws LowpanException {
-        try {
-            mBinder.attach(provision);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Bring down the network interface and forget all non-volatile details about the current
-     * network.
-     *
-     * <p>This method will block execution until the operation has completed.
-     */
-    public void leave() throws LowpanException {
-        try {
-            mBinder.leave();
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Start a new commissioning session. Will fail if the interface is attached to a network or if
-     * the interface is disabled.
-     */
-    public @NonNull LowpanCommissioningSession startCommissioningSession(
-            @NonNull LowpanBeaconInfo beaconInfo) throws LowpanException {
-        try {
-            mBinder.startCommissioningSession(beaconInfo);
-
-            return new LowpanCommissioningSession(mBinder, beaconInfo, mLooper);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Reset this network interface as if it has been power cycled. Will bring the network interface
-     * down if it was previously up. Will not erase any non-volatile settings.
-     *
-     * <p>This method will block execution until the operation has completed.
-     *
-     * @hide
-     */
-    public void reset() throws LowpanException {
-        try {
-            mBinder.reset();
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    // Public Getters and Setters
-
-    /** Returns the name of this network interface. */
-    @NonNull
-    public String getName() {
-        try {
-            return mBinder.getName();
-
-        } catch (DeadObjectException x) {
-            return "";
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /**
-     * Indicates if the interface is enabled or disabled.
-     *
-     * @see #setEnabled
-     * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED
-     */
-    public boolean isEnabled() {
-        try {
-            return mBinder.isEnabled();
-
-        } catch (DeadObjectException x) {
-            return false;
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /**
-     * Enables or disables the LoWPAN interface. When disabled, the interface is put into a
-     * low-power state and all commands that require the NCP to be queried will fail with {@link
-     * android.net.lowpan.LowpanException#LOWPAN_DISABLED}.
-     *
-     * @see #isEnabled
-     * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED
-     * @hide
-     */
-    public void setEnabled(boolean enabled) throws LowpanException {
-        try {
-            mBinder.setEnabled(enabled);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Indicates if the network interface is up or down.
-     *
-     * @hide
-     */
-    public boolean isUp() {
-        try {
-            return mBinder.isUp();
-
-        } catch (DeadObjectException x) {
-            return false;
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /**
-     * Indicates if there is at least one peer in range.
-     *
-     * @return <code>true</code> if we have at least one other peer in range, <code>false</code>
-     *     otherwise.
-     */
-    public boolean isConnected() {
-        try {
-            return mBinder.isConnected();
-
-        } catch (DeadObjectException x) {
-            return false;
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /**
-     * Indicates if this interface is currently commissioned onto an existing network. If the
-     * interface is commissioned, the interface may be brought up using setUp().
-     */
-    public boolean isCommissioned() {
-        try {
-            return mBinder.isCommissioned();
-
-        } catch (DeadObjectException x) {
-            return false;
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /**
-     * Get interface state
-     *
-     * <h3>State Diagram</h3>
-     *
-     * <img src="LowpanInterface-1.png" />
-     *
-     * @return The current state of the interface.
-     * @see #STATE_OFFLINE
-     * @see #STATE_COMMISSIONING
-     * @see #STATE_ATTACHING
-     * @see #STATE_ATTACHED
-     * @see #STATE_FAULT
-     */
-    public String getState() {
-        try {
-            return mBinder.getState();
-
-        } catch (DeadObjectException x) {
-            return STATE_FAULT;
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /** Get network partition/fragment identifier. */
-    public String getPartitionId() {
-        try {
-            return mBinder.getPartitionId();
-
-        } catch (DeadObjectException x) {
-            return EMPTY_PARTITION_ID;
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /** TODO: doc */
-    public LowpanIdentity getLowpanIdentity() {
-        try {
-            return mBinder.getLowpanIdentity();
-
-        } catch (DeadObjectException x) {
-            return new LowpanIdentity();
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /** TODO: doc */
-    @NonNull
-    public String getRole() {
-        try {
-            return mBinder.getRole();
-
-        } catch (DeadObjectException x) {
-            return ROLE_DETACHED;
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /** TODO: doc */
-    @Nullable
-    public LowpanCredential getLowpanCredential() {
-        try {
-            return mBinder.getLowpanCredential();
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    public @NonNull String[] getSupportedNetworkTypes() throws LowpanException {
-        try {
-            return mBinder.getSupportedNetworkTypes();
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    public @NonNull LowpanChannelInfo[] getSupportedChannels() throws LowpanException {
-        try {
-            return mBinder.getSupportedChannels();
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    // Listener Support
-
-    /**
-     * Registers a subclass of {@link LowpanInterface.Callback} to receive events.
-     *
-     * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events.
-     * @param handler If not <code>null</code>, events will be dispatched via the given handler
-     *     object. If <code>null</code>, the thread upon which events will be dispatched is
-     *     unspecified.
-     * @see #registerCallback(Callback)
-     * @see #unregisterCallback(Callback)
-     */
-    public void registerCallback(@NonNull Callback cb, @Nullable Handler handler) {
-        ILowpanInterfaceListener.Stub listenerBinder =
-                new ILowpanInterfaceListener.Stub() {
-                    private Handler mHandler;
-
-                    {
-                        if (handler != null) {
-                            mHandler = handler;
-                        } else if (mLooper != null) {
-                            mHandler = new Handler(mLooper);
-                        } else {
-                            mHandler = new Handler();
-                        }
-                    }
-
-                    @Override
-                    public void onEnabledChanged(boolean value) {
-                        mHandler.post(() -> cb.onEnabledChanged(value));
-                    }
-
-                    @Override
-                    public void onConnectedChanged(boolean value) {
-                        mHandler.post(() -> cb.onConnectedChanged(value));
-                    }
-
-                    @Override
-                    public void onUpChanged(boolean value) {
-                        mHandler.post(() -> cb.onUpChanged(value));
-                    }
-
-                    @Override
-                    public void onRoleChanged(String value) {
-                        mHandler.post(() -> cb.onRoleChanged(value));
-                    }
-
-                    @Override
-                    public void onStateChanged(String value) {
-                        mHandler.post(() -> cb.onStateChanged(value));
-                    }
-
-                    @Override
-                    public void onLowpanIdentityChanged(LowpanIdentity value) {
-                        mHandler.post(() -> cb.onLowpanIdentityChanged(value));
-                    }
-
-                    @Override
-                    public void onLinkNetworkAdded(IpPrefix value) {
-                        mHandler.post(() -> cb.onLinkNetworkAdded(value));
-                    }
-
-                    @Override
-                    public void onLinkNetworkRemoved(IpPrefix value) {
-                        mHandler.post(() -> cb.onLinkNetworkRemoved(value));
-                    }
-
-                    @Override
-                    public void onLinkAddressAdded(String value) {
-                        LinkAddress la;
-                        try {
-                            la = new LinkAddress(value);
-                        } catch (IllegalArgumentException x) {
-                            Log.e(
-                                    TAG,
-                                    "onLinkAddressAdded: Bad LinkAddress \"" + value + "\", " + x);
-                            return;
-                        }
-                        mHandler.post(() -> cb.onLinkAddressAdded(la));
-                    }
-
-                    @Override
-                    public void onLinkAddressRemoved(String value) {
-                        LinkAddress la;
-                        try {
-                            la = new LinkAddress(value);
-                        } catch (IllegalArgumentException x) {
-                            Log.e(
-                                    TAG,
-                                    "onLinkAddressRemoved: Bad LinkAddress \""
-                                            + value
-                                            + "\", "
-                                            + x);
-                            return;
-                        }
-                        mHandler.post(() -> cb.onLinkAddressRemoved(la));
-                    }
-
-                    @Override
-                    public void onReceiveFromCommissioner(byte[] packet) {
-                        // This is only used by the LowpanCommissioningSession.
-                    }
-                };
-        try {
-            mBinder.addListener(listenerBinder);
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-
-        synchronized (mListenerMap) {
-            mListenerMap.put(System.identityHashCode(cb), listenerBinder);
-        }
-    }
-
-    /**
-     * Registers a subclass of {@link LowpanInterface.Callback} to receive events.
-     *
-     * <p>The thread upon which events will be dispatched is unspecified.
-     *
-     * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events.
-     * @see #registerCallback(Callback, Handler)
-     * @see #unregisterCallback(Callback)
-     */
-    public void registerCallback(Callback cb) {
-        registerCallback(cb, null);
-    }
-
-    /**
-     * Unregisters a previously registered callback class.
-     *
-     * @param cb Subclass of {@link LowpanInterface.Callback} which was previously registered to
-     *     receive events.
-     * @see #registerCallback(Callback, Handler)
-     * @see #registerCallback(Callback)
-     */
-    public void unregisterCallback(Callback cb) {
-        int hashCode = System.identityHashCode(cb);
-        synchronized (mListenerMap) {
-            ILowpanInterfaceListener listenerBinder = mListenerMap.get(hashCode);
-
-            if (listenerBinder != null) {
-                mListenerMap.remove(hashCode);
-
-                try {
-                    mBinder.removeListener(listenerBinder);
-                } catch (DeadObjectException x) {
-                    // We ignore a dead object exception because that
-                    // pretty clearly means our callback isn't registered.
-                } catch (RemoteException x) {
-                    throw x.rethrowAsRuntimeException();
-                }
-            }
-        }
-    }
-
-    // Active and Passive Scanning
-
-    /**
-     * Creates a new {@link android.net.lowpan.LowpanScanner} object for this interface.
-     *
-     * <p>This method allocates a new unique object for each call.
-     *
-     * @see android.net.lowpan.LowpanScanner
-     */
-    public @NonNull LowpanScanner createScanner() {
-        return new LowpanScanner(mBinder);
-    }
-
-    // Route Management
-
-    /**
-     * Makes a copy of the internal list of LinkAddresses.
-     *
-     * @hide
-     */
-    public LinkAddress[] getLinkAddresses() throws LowpanException {
-        try {
-            String[] linkAddressStrings = mBinder.getLinkAddresses();
-            LinkAddress[] ret = new LinkAddress[linkAddressStrings.length];
-            int i = 0;
-            for (String str : linkAddressStrings) {
-                ret[i++] = new LinkAddress(str);
-            }
-            return ret;
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Makes a copy of the internal list of networks reachable on via this link.
-     *
-     * @hide
-     */
-    public IpPrefix[] getLinkNetworks() throws LowpanException {
-        try {
-            return mBinder.getLinkNetworks();
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Advertise the given IP prefix as an on-mesh prefix.
-     *
-     * @hide
-     */
-    public void addOnMeshPrefix(IpPrefix prefix, int flags) throws LowpanException {
-        try {
-            mBinder.addOnMeshPrefix(prefix, flags);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Remove an IP prefix previously advertised by this device from the list of advertised on-mesh
-     * prefixes.
-     *
-     * @hide
-     */
-    public void removeOnMeshPrefix(IpPrefix prefix) {
-        try {
-            mBinder.removeOnMeshPrefix(prefix);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            // Catch and ignore all service exceptions
-            Log.e(TAG, x.toString());
-        }
-    }
-
-    /**
-     * Advertise this device to other devices on the mesh network as having a specific route to the
-     * given network. This device will then receive forwarded traffic for that network.
-     *
-     * @hide
-     */
-    public void addExternalRoute(IpPrefix prefix, int flags) throws LowpanException {
-        try {
-            mBinder.addExternalRoute(prefix, flags);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Revoke a previously advertised specific route to the given network.
-     *
-     * @hide
-     */
-    public void removeExternalRoute(IpPrefix prefix) {
-        try {
-            mBinder.removeExternalRoute(prefix);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            // Catch and ignore all service exceptions
-            Log.e(TAG, x.toString());
-        }
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanManager.java b/lowpan/java/android/net/lowpan/LowpanManager.java
deleted file mode 100644
index 33b35e6..0000000
--- a/lowpan/java/android/net/lowpan/LowpanManager.java
+++ /dev/null
@@ -1,335 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.BackgroundThread;
-
-import java.lang.ref.WeakReference;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.WeakHashMap;
-
-/**
- * Manager object for looking up LoWPAN interfaces.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanManager {
-    private static final String TAG = LowpanManager.class.getSimpleName();
-
-    /** @hide */
-    // @SystemApi
-    public abstract static class Callback {
-        public void onInterfaceAdded(LowpanInterface lowpanInterface) {}
-
-        public void onInterfaceRemoved(LowpanInterface lowpanInterface) {}
-    }
-
-    private final Map<Integer, ILowpanManagerListener> mListenerMap = new HashMap<>();
-    private final Map<String, LowpanInterface> mInterfaceCache = new HashMap<>();
-
-    /* This is a WeakHashMap because we don't want to hold onto
-     * a strong reference to ILowpanInterface, so that it can be
-     * garbage collected if it isn't being used anymore. Since
-     * the value class holds onto this specific ILowpanInterface,
-     * we also need to have a weak reference to the value.
-     * This design pattern allows us to skip removal of items
-     * from this Map without leaking memory.
-     */
-    private final Map<IBinder, WeakReference<LowpanInterface>> mBinderCache =
-            new WeakHashMap<>();
-
-    private final ILowpanManager mService;
-    private final Context mContext;
-    private final Looper mLooper;
-
-    // Static Methods
-
-    public static LowpanManager from(Context context) {
-        return (LowpanManager) context.getSystemService(Context.LOWPAN_SERVICE);
-    }
-
-    /** @hide */
-    public static LowpanManager getManager() {
-        IBinder binder = ServiceManager.getService(Context.LOWPAN_SERVICE);
-
-        if (binder != null) {
-            ILowpanManager service = ILowpanManager.Stub.asInterface(binder);
-            return new LowpanManager(service);
-        }
-
-        return null;
-    }
-
-    // Constructors
-
-    LowpanManager(ILowpanManager service) {
-        mService = service;
-        mContext = null;
-        mLooper = null;
-    }
-
-    /**
-     * Create a new LowpanManager instance. Applications will almost always want to use {@link
-     * android.content.Context#getSystemService Context.getSystemService()} to retrieve the standard
-     * {@link android.content.Context#LOWPAN_SERVICE Context.LOWPAN_SERVICE}.
-     *
-     * @param context the application context
-     * @param service the Binder interface
-     * @hide - hide this because it takes in a parameter of type ILowpanManager, which is a system
-     *     private class.
-     */
-    public LowpanManager(Context context, ILowpanManager service) {
-        this(context, service, BackgroundThread.get().getLooper());
-    }
-
-    @VisibleForTesting
-    public LowpanManager(Context context, ILowpanManager service, Looper looper) {
-        mContext = context;
-        mService = service;
-        mLooper = looper;
-    }
-
-    /** @hide */
-    @Nullable
-    public LowpanInterface getInterfaceNoCreate(@NonNull ILowpanInterface ifaceService) {
-        LowpanInterface iface = null;
-
-        synchronized (mBinderCache) {
-            if (mBinderCache.containsKey(ifaceService.asBinder())) {
-                iface = mBinderCache.get(ifaceService.asBinder()).get();
-            }
-        }
-
-        return iface;
-    }
-
-    /** @hide */
-    @Nullable
-    public LowpanInterface getInterface(@NonNull ILowpanInterface ifaceService) {
-        LowpanInterface iface = null;
-
-        try {
-            synchronized (mBinderCache) {
-                if (mBinderCache.containsKey(ifaceService.asBinder())) {
-                    iface = mBinderCache.get(ifaceService.asBinder()).get();
-                }
-
-                if (iface == null) {
-                    String ifaceName = ifaceService.getName();
-
-                    iface = new LowpanInterface(mContext, ifaceService, mLooper);
-
-                    synchronized (mInterfaceCache) {
-                        mInterfaceCache.put(iface.getName(), iface);
-                    }
-
-                    mBinderCache.put(ifaceService.asBinder(), new WeakReference(iface));
-
-                    /* Make sure we remove the object from the
-                     * interface cache if the associated service
-                     * dies.
-                     */
-                    ifaceService
-                            .asBinder()
-                            .linkToDeath(
-                                    new IBinder.DeathRecipient() {
-                                        @Override
-                                        public void binderDied() {
-                                            synchronized (mInterfaceCache) {
-                                                LowpanInterface iface =
-                                                        mInterfaceCache.get(ifaceName);
-
-                                                if ((iface != null)
-                                                        && (iface.getService() == ifaceService)) {
-                                                    mInterfaceCache.remove(ifaceName);
-                                                }
-                                            }
-                                        }
-                                    },
-                                    0);
-                }
-            }
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-
-        return iface;
-    }
-
-    /**
-     * Returns a reference to the requested LowpanInterface object. If the given interface doesn't
-     * exist, or it is not a LoWPAN interface, returns null.
-     */
-    @Nullable
-    public LowpanInterface getInterface(@NonNull String name) {
-        LowpanInterface iface = null;
-
-        try {
-            /* This synchronized block covers both branches of the enclosed
-             * if() statement in order to avoid a race condition. Two threads
-             * calling getInterface() with the same name would race to create
-             * the associated LowpanInterface object, creating two of them.
-             * Having the whole block be synchronized avoids that race.
-             */
-            synchronized (mInterfaceCache) {
-                if (mInterfaceCache.containsKey(name)) {
-                    iface = mInterfaceCache.get(name);
-
-                } else {
-                    ILowpanInterface ifaceService = mService.getInterface(name);
-
-                    if (ifaceService != null) {
-                        iface = getInterface(ifaceService);
-                    }
-                }
-            }
-        } catch (RemoteException x) {
-            throw x.rethrowFromSystemServer();
-        }
-
-        return iface;
-    }
-
-    /**
-     * Returns a reference to the first registered LowpanInterface object. If there are no LoWPAN
-     * interfaces registered, returns null.
-     */
-    @Nullable
-    public LowpanInterface getInterface() {
-        String[] ifaceList = getInterfaceList();
-        if (ifaceList.length > 0) {
-            return getInterface(ifaceList[0]);
-        }
-        return null;
-    }
-
-    /**
-     * Returns a string array containing the names of LoWPAN interfaces. This list may contain fewer
-     * interfaces if the calling process does not have permissions to see individual interfaces.
-     */
-    @NonNull
-    public String[] getInterfaceList() {
-        try {
-            return mService.getInterfaceList();
-        } catch (RemoteException x) {
-            throw x.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Registers a callback object to receive notifications when LoWPAN interfaces are added or
-     * removed.
-     *
-     * @hide
-     */
-    public void registerCallback(@NonNull Callback cb, @Nullable Handler handler)
-            throws LowpanException {
-        ILowpanManagerListener.Stub listenerBinder =
-                new ILowpanManagerListener.Stub() {
-                    private Handler mHandler;
-
-                    {
-                        if (handler != null) {
-                            mHandler = handler;
-                        } else if (mLooper != null) {
-                            mHandler = new Handler(mLooper);
-                        } else {
-                            mHandler = new Handler();
-                        }
-                    }
-
-                    @Override
-                    public void onInterfaceAdded(ILowpanInterface ifaceService) {
-                        Runnable runnable =
-                                () -> {
-                                    LowpanInterface iface = getInterface(ifaceService);
-
-                                    if (iface != null) {
-                                        cb.onInterfaceAdded(iface);
-                                    }
-                                };
-
-                        mHandler.post(runnable);
-                    }
-
-                    @Override
-                    public void onInterfaceRemoved(ILowpanInterface ifaceService) {
-                        Runnable runnable =
-                                () -> {
-                                    LowpanInterface iface = getInterfaceNoCreate(ifaceService);
-
-                                    if (iface != null) {
-                                        cb.onInterfaceRemoved(iface);
-                                    }
-                                };
-
-                        mHandler.post(runnable);
-                    }
-                };
-        try {
-            mService.addListener(listenerBinder);
-        } catch (RemoteException x) {
-            throw x.rethrowFromSystemServer();
-        }
-
-        synchronized (mListenerMap) {
-            mListenerMap.put(Integer.valueOf(System.identityHashCode(cb)), listenerBinder);
-        }
-    }
-
-    /** @hide */
-    public void registerCallback(@NonNull Callback cb) throws LowpanException {
-        registerCallback(cb, null);
-    }
-
-    /**
-     * Unregisters a previously registered {@link LowpanManager.Callback} object.
-     *
-     * @hide
-     */
-    public void unregisterCallback(@NonNull Callback cb) {
-        Integer hashCode = Integer.valueOf(System.identityHashCode(cb));
-        ILowpanManagerListener listenerBinder = null;
-
-        synchronized (mListenerMap) {
-            listenerBinder = mListenerMap.get(hashCode);
-            mListenerMap.remove(hashCode);
-        }
-
-        if (listenerBinder != null) {
-            try {
-                mService.removeListener(listenerBinder);
-            } catch (RemoteException x) {
-                throw x.rethrowFromSystemServer();
-            }
-        } else {
-            throw new RuntimeException("Attempt to unregister an unknown callback");
-        }
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanProperties.java b/lowpan/java/android/net/lowpan/LowpanProperties.java
deleted file mode 100644
index cc45ff85..0000000
--- a/lowpan/java/android/net/lowpan/LowpanProperties.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/** {@hide} */
-public final class LowpanProperties {
-
-    public static final LowpanProperty<int[]> KEY_CHANNEL_MASK =
-            new LowpanStandardProperty("android.net.lowpan.property.CHANNEL_MASK", int[].class);
-
-    public static final LowpanProperty<Integer> KEY_MAX_TX_POWER =
-            new LowpanStandardProperty("android.net.lowpan.property.MAX_TX_POWER", Integer.class);
-
-    /** @hide */
-    private LowpanProperties() {}
-
-    /** @hide */
-    static final class LowpanStandardProperty<T> extends LowpanProperty<T> {
-        private final String mName;
-        private final Class<T> mType;
-
-        LowpanStandardProperty(String name, Class<T> type) {
-            mName = name;
-            mType = type;
-        }
-
-        @Override
-        public String getName() {
-            return mName;
-        }
-
-        @Override
-        public Class<T> getType() {
-            return mType;
-        }
-
-        @Override
-        public String toString() {
-            return getName();
-        }
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanProperty.java b/lowpan/java/android/net/lowpan/LowpanProperty.java
deleted file mode 100644
index 7f26986..0000000
--- a/lowpan/java/android/net/lowpan/LowpanProperty.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import java.util.Map;
-
-/** {@hide} */
-public abstract class LowpanProperty<T> {
-    public abstract String getName();
-
-    public abstract Class<T> getType();
-
-    public void putInMap(Map map, T value) {
-        map.put(getName(), value);
-    }
-
-    public T getFromMap(Map map) {
-        return (T) map.get(getName());
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanProvision.aidl b/lowpan/java/android/net/lowpan/LowpanProvision.aidl
deleted file mode 100644
index 100e9dc..0000000
--- a/lowpan/java/android/net/lowpan/LowpanProvision.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-parcelable LowpanProvision cpp_header "android/net/lowpan/LowpanProvision.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanProvision.java b/lowpan/java/android/net/lowpan/LowpanProvision.java
deleted file mode 100644
index 68c8709..0000000
--- a/lowpan/java/android/net/lowpan/LowpanProvision.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-import java.util.Objects;
-
-/**
- * Describes the information needed to describe a network
- *
- * @hide
- */
-// @SystemApi
-public class LowpanProvision implements Parcelable {
-
-    // Builder
-
-    /** @hide */
-    // @SystemApi
-    public static class Builder {
-        private final LowpanProvision provision = new LowpanProvision();
-
-        public Builder setLowpanIdentity(@NonNull LowpanIdentity identity) {
-            provision.mIdentity = identity;
-            return this;
-        }
-
-        public Builder setLowpanCredential(@NonNull LowpanCredential credential) {
-            provision.mCredential = credential;
-            return this;
-        }
-
-        public LowpanProvision build() {
-            return provision;
-        }
-    }
-
-    private LowpanProvision() {}
-
-    // Instance Variables
-
-    private LowpanIdentity mIdentity = new LowpanIdentity();
-    private LowpanCredential mCredential = null;
-
-    // Public Getters and Setters
-
-    @NonNull
-    public LowpanIdentity getLowpanIdentity() {
-        return mIdentity;
-    }
-
-    @Nullable
-    public LowpanCredential getLowpanCredential() {
-        return mCredential;
-    }
-
-    @Override
-    public String toString() {
-        StringBuffer sb = new StringBuffer();
-
-        sb.append("LowpanProvision { identity => ").append(mIdentity.toString());
-
-        if (mCredential != null) {
-            sb.append(", credential => ").append(mCredential.toString());
-        }
-
-        sb.append("}");
-
-        return sb.toString();
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mIdentity, mCredential);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof LowpanProvision)) {
-            return false;
-        }
-        LowpanProvision rhs = (LowpanProvision) obj;
-
-        if (!mIdentity.equals(rhs.mIdentity)) {
-            return false;
-        }
-
-        if (!Objects.equals(mCredential, rhs.mCredential)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /** Implement the Parcelable interface. */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        mIdentity.writeToParcel(dest, flags);
-        if (mCredential == null) {
-            dest.writeBoolean(false);
-        } else {
-            dest.writeBoolean(true);
-            mCredential.writeToParcel(dest, flags);
-        }
-    }
-
-    /** Implement the Parcelable interface. */
-    public static final @android.annotation.NonNull Creator<LowpanProvision> CREATOR =
-            new Creator<LowpanProvision>() {
-                public LowpanProvision createFromParcel(Parcel in) {
-                    Builder builder = new Builder();
-
-                    builder.setLowpanIdentity(LowpanIdentity.CREATOR.createFromParcel(in));
-
-                    if (in.readBoolean()) {
-                        builder.setLowpanCredential(LowpanCredential.CREATOR.createFromParcel(in));
-                    }
-
-                    return builder.build();
-                }
-
-                public LowpanProvision[] newArray(int size) {
-                    return new LowpanProvision[size];
-                }
-            };
-};
diff --git a/lowpan/java/android/net/lowpan/LowpanRuntimeException.java b/lowpan/java/android/net/lowpan/LowpanRuntimeException.java
deleted file mode 100644
index 71a5a13..0000000
--- a/lowpan/java/android/net/lowpan/LowpanRuntimeException.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.util.AndroidRuntimeException;
-
-/**
- * Generic runtime exception for LoWPAN operations.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanRuntimeException extends AndroidRuntimeException {
-
-    public LowpanRuntimeException() {}
-
-    public LowpanRuntimeException(String message) {
-        super(message);
-    }
-
-    public LowpanRuntimeException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public LowpanRuntimeException(Exception cause) {
-        super(cause);
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanScanner.java b/lowpan/java/android/net/lowpan/LowpanScanner.java
deleted file mode 100644
index 59156c4..0000000
--- a/lowpan/java/android/net/lowpan/LowpanScanner.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * LoWPAN Scanner
- *
- * <p>This class allows performing network (active) scans and energy (passive) scans.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class LowpanScanner {
-    private static final String TAG = LowpanScanner.class.getSimpleName();
-
-    // Public Classes
-
-    /**
-     * Callback base class for LowpanScanner
-     *
-     * @hide
-     */
-    // @SystemApi
-    public abstract static class Callback {
-        public void onNetScanBeacon(LowpanBeaconInfo beacon) {}
-
-        public void onEnergyScanResult(LowpanEnergyScanResult result) {}
-
-        public void onScanFinished() {}
-    }
-
-    // Instance Variables
-
-    private ILowpanInterface mBinder;
-    private Callback mCallback = null;
-    private Handler mHandler = null;
-    private ArrayList<Integer> mChannelMask = null;
-    private int mTxPower = Integer.MAX_VALUE;
-
-    // Constructors/Accessors and Exception Glue
-
-    LowpanScanner(@NonNull ILowpanInterface binder) {
-        mBinder = binder;
-    }
-
-    /** Sets an instance of {@link LowpanScanner.Callback} to receive events. */
-    public synchronized void setCallback(@Nullable Callback cb, @Nullable Handler handler) {
-        mCallback = cb;
-        mHandler = handler;
-    }
-
-    /** Sets an instance of {@link LowpanScanner.Callback} to receive events. */
-    public void setCallback(@Nullable Callback cb) {
-        setCallback(cb, null);
-    }
-
-    /**
-     * Sets the channel mask to use when scanning.
-     *
-     * @param mask The channel mask to use when scanning. If <code>null</code>, any previously set
-     *     channel mask will be cleared and all channels not masked by the current regulatory zone
-     *     will be scanned.
-     */
-    public void setChannelMask(@Nullable Collection<Integer> mask) {
-        if (mask == null) {
-            mChannelMask = null;
-        } else {
-            if (mChannelMask == null) {
-                mChannelMask = new ArrayList<>();
-            } else {
-                mChannelMask.clear();
-            }
-            mChannelMask.addAll(mask);
-        }
-    }
-
-    /**
-     * Gets the current channel mask.
-     *
-     * @return the current channel mask, or <code>null</code> if no channel mask is currently set.
-     */
-    public @Nullable Collection<Integer> getChannelMask() {
-        return (Collection<Integer>) mChannelMask.clone();
-    }
-
-    /**
-     * Adds a channel to the channel mask used for scanning.
-     *
-     * <p>If a channel mask was previously <code>null</code>, a new one is created containing only
-     * this channel. May be called multiple times to add additional channels ot the channel mask.
-     *
-     * @see #setChannelMask
-     * @see #getChannelMask
-     * @see #getTxPower
-     */
-    public void addChannel(int channel) {
-        if (mChannelMask == null) {
-            mChannelMask = new ArrayList<>();
-        }
-        mChannelMask.add(Integer.valueOf(channel));
-    }
-
-    /**
-     * Sets the maximum transmit power to be used for active scanning.
-     *
-     * <p>The actual transmit power used is the lesser of this value and the currently configured
-     * maximum transmit power for the interface.
-     *
-     * @see #getTxPower
-     */
-    public void setTxPower(int txPower) {
-        mTxPower = txPower;
-    }
-
-    /**
-     * Gets the maximum transmit power used for active scanning.
-     *
-     * @see #setTxPower
-     */
-    public int getTxPower() {
-        return mTxPower;
-    }
-
-    private Map<String, Object> createScanOptionMap() {
-        Map<String, Object> map = new HashMap();
-
-        if (mChannelMask != null) {
-            LowpanProperties.KEY_CHANNEL_MASK.putInMap(
-                    map, mChannelMask.stream().mapToInt(i -> i).toArray());
-        }
-
-        if (mTxPower != Integer.MAX_VALUE) {
-            LowpanProperties.KEY_MAX_TX_POWER.putInMap(map, Integer.valueOf(mTxPower));
-        }
-
-        return map;
-    }
-
-    /**
-     * Start a network scan.
-     *
-     * <p>This method will return once the scan has started.
-     *
-     * @see #stopNetScan
-     */
-    public void startNetScan() throws LowpanException {
-        Map<String, Object> map = createScanOptionMap();
-
-        ILowpanNetScanCallback binderListener =
-                new ILowpanNetScanCallback.Stub() {
-                    public void onNetScanBeacon(LowpanBeaconInfo beaconInfo) {
-                        Callback callback;
-                        Handler handler;
-
-                        synchronized (LowpanScanner.this) {
-                            callback = mCallback;
-                            handler = mHandler;
-                        }
-
-                        if (callback == null) {
-                            return;
-                        }
-
-                        Runnable runnable = () -> callback.onNetScanBeacon(beaconInfo);
-
-                        if (handler != null) {
-                            handler.post(runnable);
-                        } else {
-                            runnable.run();
-                        }
-                    }
-
-                    public void onNetScanFinished() {
-                        Callback callback;
-                        Handler handler;
-
-                        synchronized (LowpanScanner.this) {
-                            callback = mCallback;
-                            handler = mHandler;
-                        }
-
-                        if (callback == null) {
-                            return;
-                        }
-
-                        Runnable runnable = () -> callback.onScanFinished();
-
-                        if (handler != null) {
-                            handler.post(runnable);
-                        } else {
-                            runnable.run();
-                        }
-                    }
-                };
-
-        try {
-            mBinder.startNetScan(map, binderListener);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Stop a network scan currently in progress.
-     *
-     * @see #startNetScan
-     */
-    public void stopNetScan() {
-        try {
-            mBinder.stopNetScan();
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-
-    /**
-     * Start an energy scan.
-     *
-     * <p>This method will return once the scan has started.
-     *
-     * @see #stopEnergyScan
-     */
-    public void startEnergyScan() throws LowpanException {
-        Map<String, Object> map = createScanOptionMap();
-
-        ILowpanEnergyScanCallback binderListener =
-                new ILowpanEnergyScanCallback.Stub() {
-                    public void onEnergyScanResult(int channel, int rssi) {
-                        Callback callback = mCallback;
-                        Handler handler = mHandler;
-
-                        if (callback == null) {
-                            return;
-                        }
-
-                        Runnable runnable =
-                                () -> {
-                                    if (callback != null) {
-                                        LowpanEnergyScanResult result =
-                                                new LowpanEnergyScanResult();
-                                        result.setChannel(channel);
-                                        result.setMaxRssi(rssi);
-                                        callback.onEnergyScanResult(result);
-                                    }
-                                };
-
-                        if (handler != null) {
-                            handler.post(runnable);
-                        } else {
-                            runnable.run();
-                        }
-                    }
-
-                    public void onEnergyScanFinished() {
-                        Callback callback = mCallback;
-                        Handler handler = mHandler;
-
-                        if (callback == null) {
-                            return;
-                        }
-
-                        Runnable runnable = () -> callback.onScanFinished();
-
-                        if (handler != null) {
-                            handler.post(runnable);
-                        } else {
-                            runnable.run();
-                        }
-                    }
-                };
-
-        try {
-            mBinder.startEnergyScan(map, binderListener);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowFromServiceSpecificException(x);
-        }
-    }
-
-    /**
-     * Stop an energy scan currently in progress.
-     *
-     * @see #startEnergyScan
-     */
-    public void stopEnergyScan() {
-        try {
-            mBinder.stopEnergyScan();
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-        }
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/NetworkAlreadyExistsException.java b/lowpan/java/android/net/lowpan/NetworkAlreadyExistsException.java
deleted file mode 100644
index 90ef498..0000000
--- a/lowpan/java/android/net/lowpan/NetworkAlreadyExistsException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the form operation found a network nearby with the same identity.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class NetworkAlreadyExistsException extends LowpanException {
-
-    public NetworkAlreadyExistsException() {}
-
-    public NetworkAlreadyExistsException(String message) {
-        super(message, null);
-    }
-
-    public NetworkAlreadyExistsException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public NetworkAlreadyExistsException(Exception cause) {
-        super(cause);
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/OperationCanceledException.java b/lowpan/java/android/net/lowpan/OperationCanceledException.java
deleted file mode 100644
index fcafe3a..0000000
--- a/lowpan/java/android/net/lowpan/OperationCanceledException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating this operation was canceled by the driver before it could finish.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class OperationCanceledException extends LowpanException {
-
-    public OperationCanceledException() {}
-
-    public OperationCanceledException(String message) {
-        super(message);
-    }
-
-    public OperationCanceledException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    protected OperationCanceledException(Exception cause) {
-        super(cause);
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/WrongStateException.java b/lowpan/java/android/net/lowpan/WrongStateException.java
deleted file mode 100644
index 3565419..0000000
--- a/lowpan/java/android/net/lowpan/WrongStateException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the interface is the wrong state for an operation.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class WrongStateException extends LowpanException {
-
-    public WrongStateException() {}
-
-    public WrongStateException(String message) {
-        super(message);
-    }
-
-    public WrongStateException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    protected WrongStateException(Exception cause) {
-        super(cause);
-    }
-}
diff --git a/lowpan/java/android/net/lowpan/package.html b/lowpan/java/android/net/lowpan/package.html
deleted file mode 100644
index 342e32e..0000000
--- a/lowpan/java/android/net/lowpan/package.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<HTML>
-<BODY>
-<p>@SystemApi</p>
-<!-- @hide -->
-<p>Provides classes to manage Low-power Wireless Personal Area Network (LoWPAN) functionality on the device.
-Examples of such network technologies include <a href="http://threadgroup.org/">Thread</a> and
-<a href="http://www.zigbee.org/zigbee-for-developers/network-specifications/zigbeeip/">ZigBee IP</a>.</p>
-<p>The LoWPAN APIs provide a means by which applications can communicate
-with the lower-level wireless stack that provides LoWPAN network access.</p>
-
-<p>Some APIs may require the following user permissions:</p>
-<ul>
-  <li>{@link android.Manifest.permission#ACCESS_LOWPAN_STATE}</li>
-  <li>{@link android.Manifest.permission#CHANGE_LOWPAN_STATE}</li>
-  <li>TBD</li>
-</ul>
-
-<p class="note"><strong>Note:</strong> Not all Android-powered devices provide LoWPAN functionality.
-If your application uses these APIs, declare so with a <a
-href="{@docRoot}guide/topics/manifest/uses-feature-element.html">{@code <uses-feature>}</a>
-element in the manifest file:</p>
-<pre>
-&lt;manifest ...>
-    &lt;uses-feature android:name="android.hardware.lowpan" />
-    ...
-&lt;/manifest>
-</pre>
-</BODY>
-</HTML>
diff --git a/lowpan/tests/Android.bp b/lowpan/tests/Android.bp
deleted file mode 100644
index 5908929..0000000
--- a/lowpan/tests/Android.bp
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Make test APK
-// ============================================================
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test {
-    name: "FrameworksLowpanApiTests",
-    srcs: ["**/*.java"],
-    // Filter all src files to just java files
-    jacoco: {
-        include_filter: ["android.net.lowpan.*"],
-        exclude_filter: [
-	    "android.net.lowpan.LowpanInterfaceTest*",
-	    "android.net.lowpan.LowpanManagerTest*",
-	],
-    },
-    static_libs: [
-        "androidx.test.rules",
-        "guava",
-        "mockito-target-minus-junit4",
-        "frameworks-base-testutils",
-    ],
-    libs: [
-        "android.test.runner",
-        "android.test.base",
-    ],
-    platform_apis: true,
-    test_suites: ["device-tests"],
-    certificate: "platform",
-}
diff --git a/lowpan/tests/AndroidManifest.xml b/lowpan/tests/AndroidManifest.xml
deleted file mode 100644
index 8e68fc7..0000000
--- a/lowpan/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.net.lowpan.test">
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:label="LowpanTestDummyLabel"
-                  android:name="LowpanTestDummyName"
-                  android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.net.lowpan.test"
-        android:label="Frameworks LoWPAN API Tests">
-    </instrumentation>
-
-</manifest>
diff --git a/lowpan/tests/AndroidTest.xml b/lowpan/tests/AndroidTest.xml
deleted file mode 100644
index 978cc02..0000000
--- a/lowpan/tests/AndroidTest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Runs Frameworks LoWPAN API Tests.">
-    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
-        <option name="test-file-name" value="FrameworksLowpanApiTests.apk" />
-    </target_preparer>
-
-    <option name="test-suite-tag" value="apct" />
-    <option name="test-tag" value="FrameworksLowpanApiTests" />
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.net.lowpan.test" />
-        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
-    </test>
-</configuration>
diff --git a/lowpan/tests/README.md b/lowpan/tests/README.md
deleted file mode 100644
index cb5772e..0000000
--- a/lowpan/tests/README.md
+++ /dev/null
@@ -1,50 +0,0 @@
-# LoWPAN Unit Tests
-This package contains unit tests for the android LoWPAN framework System APIs based on the
-[Android Testing Support Library](http://developer.android.com/tools/testing-support-library/index.html).
-The test cases are built using the [JUnit](http://junit.org/) and [Mockito](http://mockito.org/)
-libraries.
-
-## Running Tests
-The easiest way to run tests is simply run
-
-```
-frameworks/base/lowpan/tests/runtests.sh
-```
-
-`runtests.sh` will build the test project and all of its dependencies and push the APK to the
-connected device. It will then run the tests on the device.
-
-To pick up changes in framework/base, you will need to:
-1. rebuild the framework library 'make -j32'
-2. sync over the updated library to the device 'adb sync'
-3. restart framework on the device 'adb shell stop' then 'adb shell start'
-
-To enable syncing data to the device for first time after clean reflash:
-1. adb disable-verity
-2. adb reboot
-3. adb remount
-
-See below for a few example of options to limit which tests are run.
-See the
-[AndroidJUnitRunner Documentation](https://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html)
-for more details on the supported options.
-
-```
-runtests.sh -e package android.net.lowpan
-runtests.sh -e class android.net.lowpan.LowpanManagerTest
-```
-
-If you manually build and push the test APK to the device you can run tests using
-
-```
-adb shell am instrument -w 'android.net.wifi.test/androidx.test.runner.AndroidJUnitRunner'
-```
-
-## Adding Tests
-Tests can be added by adding classes to the src directory. JUnit4 style test cases can
-be written by simply annotating test methods with `org.junit.Test`.
-
-## Debugging Tests
-If you are trying to debug why tests are not doing what you expected, you can add android log
-statements and use logcat to view them. The beginning and end of every tests is automatically logged
-with the tag `TestRunner`.
diff --git a/lowpan/tests/runtests.sh b/lowpan/tests/runtests.sh
deleted file mode 100755
index 8267a79..0000000
--- a/lowpan/tests/runtests.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/usr/bin/env bash
-
-if [ -z $ANDROID_BUILD_TOP ]; then
-  echo "You need to source and lunch before you can use this script"
-  exit 1
-fi
-
-echo "Running tests"
-
-set -e # fail early
-
-echo "+ mmma -j32 $ANDROID_BUILD_TOP/frameworks/base/lowpan/tests"
-# NOTE Don't actually run the command above since this shell doesn't inherit functions from the
-#      caller.
-make -j32 -C $ANDROID_BUILD_TOP -f build/core/main.mk MODULES-IN-frameworks-base-lowpan-tests
-
-set -x # print commands
-
-adb root
-adb wait-for-device
-
-adb install -r -g "$OUT/data/app/FrameworksLowpanApiTests/FrameworksLowpanApiTests.apk"
-
-adb shell am instrument -w "$@" 'android.net.lowpan.test/androidx.test.runner.AndroidJUnitRunner'
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java b/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
deleted file mode 100644
index 86f9d0e..0000000
--- a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import static org.mockito.Mockito.*;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.test.TestLooper;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Map;
-
-/** Unit tests for android.net.lowpan.LowpanInterface. */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class LowpanInterfaceTest {
-    private static final String TEST_PACKAGE_NAME = "TestPackage";
-
-    @Mock Context mContext;
-    @Mock ILowpanInterface mLowpanInterfaceService;
-    @Mock IBinder mLowpanInterfaceBinder;
-    @Mock ApplicationInfo mApplicationInfo;
-    @Mock IBinder mAppBinder;
-    @Mock LowpanInterface.Callback mLowpanInterfaceCallback;
-
-    private Handler mHandler;
-    private final TestLooper mTestLooper = new TestLooper();
-    private ILowpanInterfaceListener mInterfaceListener;
-    private LowpanInterface mLowpanInterface;
-    private Map<String, Object> mPropertyMap;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
-        when(mContext.getOpPackageName()).thenReturn(TEST_PACKAGE_NAME);
-        when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
-        when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
-
-        mLowpanInterface =
-                new LowpanInterface(mContext, mLowpanInterfaceService, mTestLooper.getLooper());
-    }
-
-    @Test
-    public void testStateChangedCallback() throws Exception {
-        // Register our callback
-        mLowpanInterface.registerCallback(mLowpanInterfaceCallback);
-
-        // Verify a listener was added
-        verify(mLowpanInterfaceService)
-                .addListener(
-                        argThat(
-                                listener -> {
-                                    mInterfaceListener = listener;
-                                    return listener instanceof ILowpanInterfaceListener;
-                                }));
-
-        // Change some properties
-        mInterfaceListener.onStateChanged(LowpanInterface.STATE_OFFLINE);
-        mTestLooper.dispatchAll();
-
-        // Verify that the property was changed
-        verify(mLowpanInterfaceCallback)
-                .onStateChanged(
-                        argThat(stateString -> stateString.equals(LowpanInterface.STATE_OFFLINE)));
-    }
-}
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java b/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
deleted file mode 100644
index 998e8a5..0000000
--- a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.*;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.test.TestLooper;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/** Unit tests for android.net.lowpan.LowpanManager. */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class LowpanManagerTest {
-    private static final String TEST_PACKAGE_NAME = "TestPackage";
-
-    @Mock Context mContext;
-    @Mock ILowpanManager mLowpanService;
-    @Mock ILowpanInterface mLowpanInterfaceService;
-    @Mock IBinder mLowpanInterfaceBinder;
-    @Mock ApplicationInfo mApplicationInfo;
-    @Mock IBinder mAppBinder;
-    @Mock LowpanManager.Callback mLowpanManagerCallback;
-
-    private Handler mHandler;
-    private final TestLooper mTestLooper = new TestLooper();
-    private LowpanManager mLowpanManager;
-
-    private ILowpanManagerListener mManagerListener;
-    private LowpanInterface mLowpanInterface;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
-        when(mContext.getOpPackageName()).thenReturn(TEST_PACKAGE_NAME);
-
-        mLowpanManager = new LowpanManager(mContext, mLowpanService, mTestLooper.getLooper());
-    }
-
-    @Test
-    public void testGetEmptyInterfaceList() throws Exception {
-        when(mLowpanService.getInterfaceList()).thenReturn(new String[0]);
-        assertTrue(mLowpanManager.getInterfaceList().length == 0);
-        assertTrue(mLowpanManager.getInterface() == null);
-    }
-
-    @Test
-    public void testGetInterfaceList() throws Exception {
-        when(mLowpanService.getInterfaceList()).thenReturn(new String[] {"wpan0"});
-        when(mLowpanService.getInterface("wpan0")).thenReturn(mLowpanInterfaceService);
-        when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
-        when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
-        assertEquals(mLowpanManager.getInterfaceList().length, 1);
-
-        LowpanInterface iface = mLowpanManager.getInterface();
-        assertNotNull(iface);
-        assertEquals(iface.getName(), "wpan0");
-    }
-
-    @Test
-    public void testRegisterCallback() throws Exception {
-        when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
-        when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
-
-        // Register our callback
-        mLowpanManager.registerCallback(mLowpanManagerCallback);
-
-        // Verify a listener was added
-        verify(mLowpanService)
-                .addListener(
-                        argThat(
-                                listener -> {
-                                    mManagerListener = listener;
-                                    return listener instanceof ILowpanManagerListener;
-                                }));
-
-        // Add an interface
-        mManagerListener.onInterfaceAdded(mLowpanInterfaceService);
-        mTestLooper.dispatchAll();
-
-        // Verify that the interface was added
-        verify(mLowpanManagerCallback)
-                .onInterfaceAdded(
-                        argThat(
-                                iface -> {
-                                    mLowpanInterface = iface;
-                                    return iface instanceof LowpanInterface;
-                                }));
-        verifyNoMoreInteractions(mLowpanManagerCallback);
-
-        // This check causes the test to fail with a weird error, but I'm not sure why.
-        assertEquals(mLowpanInterface.getService(), mLowpanInterfaceService);
-
-        // Verify that calling getInterface on the LowpanManager object will yield the same
-        // LowpanInterface object.
-        when(mLowpanService.getInterfaceList()).thenReturn(new String[] {"wpan0"});
-        when(mLowpanService.getInterface("wpan0")).thenReturn(mLowpanInterfaceService);
-        assertEquals(mLowpanManager.getInterface(), mLowpanInterface);
-
-        // Remove the service
-        mManagerListener.onInterfaceRemoved(mLowpanInterfaceService);
-        mTestLooper.dispatchAll();
-
-        // Verify that the interface was removed
-        verify(mLowpanManagerCallback).onInterfaceRemoved(mLowpanInterface);
-    }
-
-    @Test
-    public void testUnregisterCallback() throws Exception {
-        when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
-        when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
-
-        // Register our callback
-        mLowpanManager.registerCallback(mLowpanManagerCallback);
-
-        // Verify a listener was added
-        verify(mLowpanService)
-                .addListener(
-                        argThat(
-                                listener -> {
-                                    mManagerListener = listener;
-                                    return listener instanceof ILowpanManagerListener;
-                                }));
-
-        // Add an interface
-        mManagerListener.onInterfaceAdded(mLowpanInterfaceService);
-        mTestLooper.dispatchAll();
-
-        // Verify that the interface was added
-        verify(mLowpanManagerCallback)
-                .onInterfaceAdded(
-                        argThat(
-                                iface -> {
-                                    mLowpanInterface = iface;
-                                    return iface instanceof LowpanInterface;
-                                }));
-        verifyNoMoreInteractions(mLowpanManagerCallback);
-
-        // Unregister our callback
-        mLowpanManager.unregisterCallback(mLowpanManagerCallback);
-
-        // Verify the listener was removed
-        verify(mLowpanService).removeListener(mManagerListener);
-
-        // Verify that the callback wasn't invoked.
-        verifyNoMoreInteractions(mLowpanManagerCallback);
-    }
-}
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/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 444366a..650f360 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1299,8 +1299,8 @@
     /** @hide */ public static final String DEVICE_OUT_REMOTE_SUBMIX_NAME = "remote_submix";
     /** @hide */ public static final String DEVICE_OUT_TELEPHONY_TX_NAME = "telephony_tx";
     /** @hide */ public static final String DEVICE_OUT_LINE_NAME = "line";
-    /** @hide */ public static final String DEVICE_OUT_HDMI_ARC_NAME = "hmdi_arc";
-    /** @hide */ public static final String DEVICE_OUT_HDMI_EARC_NAME = "hmdi_earc";
+    /** @hide */ public static final String DEVICE_OUT_HDMI_ARC_NAME = "hdmi_arc";
+    /** @hide */ public static final String DEVICE_OUT_HDMI_EARC_NAME = "hdmi_earc";
     /** @hide */ public static final String DEVICE_OUT_SPDIF_NAME = "spdif";
     /** @hide */ public static final String DEVICE_OUT_FM_NAME = "fm_transmitter";
     /** @hide */ public static final String DEVICE_OUT_AUX_LINE_NAME = "aux_line";
@@ -2294,10 +2294,10 @@
     public static int[] DEFAULT_STREAM_VOLUME = new int[] {
         4,  // STREAM_VOICE_CALL
         7,  // STREAM_SYSTEM
-        5,  // STREAM_RING
+        5,  // STREAM_RING           // configured in AudioService by config_audio_notif_vol_default
         5, // STREAM_MUSIC
         6,  // STREAM_ALARM
-        5,  // STREAM_NOTIFICATION
+        5,  // STREAM_NOTIFICATION   // configured in AudioService by config_audio_ring_vol_default
         7,  // STREAM_BLUETOOTH_SCO
         7,  // STREAM_SYSTEM_ENFORCED
         5, // STREAM_DTMF
diff --git a/media/java/android/media/BluetoothProfileConnectionInfo.java b/media/java/android/media/BluetoothProfileConnectionInfo.java
index c148846..a316c21 100644
--- a/media/java/android/media/BluetoothProfileConnectionInfo.java
+++ b/media/java/android/media/BluetoothProfileConnectionInfo.java
@@ -126,6 +126,20 @@
     }
 
     /**
+     * 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.
+     * @param volume the volume index of the device, -1 if unknown or to be ignored
+     * @return an instance of BluetoothProfileConnectionInfo for the BLE output device that reflects
+     *     the given parameters
+     */
+    public static @NonNull BluetoothProfileConnectionInfo createLeAudioOutputInfo(
+            boolean suppressNoisyIntent, int volume) {
+        return new BluetoothProfileConnectionInfo(BluetoothProfile.LE_AUDIO, suppressNoisyIntent,
+                volume, /*isLeOutput*/ true);
+    }
+
+    /**
      * @return The profile connection
      */
     public int getProfile() {
diff --git a/media/java/android/media/IMediaRouter2.aidl b/media/java/android/media/IMediaRouter2.aidl
index fe15f0e..29bfd1a 100644
--- a/media/java/android/media/IMediaRouter2.aidl
+++ b/media/java/android/media/IMediaRouter2.aidl
@@ -26,9 +26,7 @@
 oneway interface IMediaRouter2 {
     void notifyRouterRegistered(in List<MediaRoute2Info> currentRoutes,
             in RoutingSessionInfo currentSystemSessionInfo);
-    void notifyRoutesAdded(in List<MediaRoute2Info> routes);
-    void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
-    void notifyRoutesChanged(in List<MediaRoute2Info> routes);
+    void notifyRoutesUpdated(in List<MediaRoute2Info> routes);
     void notifySessionCreated(int requestId, in @nullable RoutingSessionInfo sessionInfo);
     void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo);
     void notifySessionReleased(in RoutingSessionInfo sessionInfo);
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index 71dc2a7..9f3c3ff 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -30,8 +30,6 @@
     void notifySessionReleased(in RoutingSessionInfo session);
     void notifyDiscoveryPreferenceChanged(String packageName,
             in RouteDiscoveryPreference discoveryPreference);
-    void notifyRoutesAdded(in List<MediaRoute2Info> routes);
-    void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
-    void notifyRoutesChanged(in List<MediaRoute2Info> routes);
+    void notifyRoutesUpdated(in List<MediaRoute2Info> routes);
     void notifyRequestFailed(int requestId, int reason);
 }
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index a7a21e7..26cb9f8 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -132,7 +132,7 @@
     /**
      * Stores an auxiliary copy of {@link #mFilteredRoutes} at the time of the last route callback
      * dispatch. This is only used to determine what callback a route should be assigned to (added,
-     * removed, changed) in {@link #dispatchFilteredRoutesChangedLocked(List)}.
+     * removed, changed) in {@link #dispatchFilteredRoutesUpdatedOnHandler(List)}.
      */
     private volatile ArrayMap<String, MediaRoute2Info> mPreviousRoutes = new ArrayMap<>();
 
@@ -820,7 +820,7 @@
         }
     }
 
-    void dispatchFilteredRoutesChangedLocked(List<MediaRoute2Info> newRoutes) {
+    void dispatchFilteredRoutesUpdatedOnHandler(List<MediaRoute2Info> newRoutes) {
         List<MediaRoute2Info> addedRoutes = new ArrayList<>();
         List<MediaRoute2Info> removedRoutes = new ArrayList<>();
         List<MediaRoute2Info> changedRoutes = new ArrayList<>();
@@ -863,29 +863,16 @@
         if (!changedRoutes.isEmpty()) {
             notifyRoutesChanged(changedRoutes);
         }
-    }
 
-    void addRoutesOnHandler(List<MediaRoute2Info> routes) {
-        synchronized (mLock) {
-            for (MediaRoute2Info route : routes) {
-                mRoutes.put(route.getId(), route);
-            }
-            updateFilteredRoutesLocked();
+        // Note: We don't notify clients of changes in route ordering.
+        if (!addedRoutes.isEmpty() || !removedRoutes.isEmpty() || !changedRoutes.isEmpty()) {
+            notifyRoutesUpdated(newRoutes);
         }
     }
 
-    void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
+    void updateRoutesOnHandler(List<MediaRoute2Info> routes) {
         synchronized (mLock) {
-            for (MediaRoute2Info route : routes) {
-                mRoutes.remove(route.getId());
-            }
-            updateFilteredRoutesLocked();
-        }
-    }
-
-    void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
-        List<MediaRoute2Info> changedRoutes = new ArrayList<>();
-        synchronized (mLock) {
+            mRoutes.clear();
             for (MediaRoute2Info route : routes) {
                 mRoutes.put(route.getId(), route);
             }
@@ -900,8 +887,10 @@
                 Collections.unmodifiableList(
                         filterRoutesWithCompositePreferenceLocked(List.copyOf(mRoutes.values())));
         mHandler.sendMessage(
-                obtainMessage(MediaRouter2::dispatchFilteredRoutesChangedLocked,
-                        this, mFilteredRoutes));
+                obtainMessage(
+                        MediaRouter2::dispatchFilteredRoutesUpdatedOnHandler,
+                        this,
+                        mFilteredRoutes));
     }
 
     /**
@@ -1211,6 +1200,14 @@
         }
     }
 
+    private void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
+        for (RouteCallbackRecord record : mRouteCallbackRecords) {
+            List<MediaRoute2Info> filteredRoutes =
+                    filterRoutesWithIndividualPreference(routes, record.mPreference);
+            record.mExecutor.execute(() -> record.mRouteCallback.onRoutesUpdated(filteredRoutes));
+        }
+    }
+
     private void notifyPreferredFeaturesChanged(List<String> features) {
         for (RouteCallbackRecord record : mRouteCallbackRecords) {
             record.mExecutor.execute(
@@ -1246,29 +1243,44 @@
     /** Callback for receiving events about media route discovery. */
     public abstract static class RouteCallback {
         /**
-         * Called when routes are added. Whenever you registers a callback, this will be invoked
-         * with known routes.
+         * Called when routes are added. Whenever you register a callback, this will be invoked with
+         * known routes.
          *
          * @param routes the list of routes that have been added. It's never empty.
+         * @deprecated Use {@link #onRoutesUpdated(List)} instead.
          */
+        @Deprecated
         public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {}
 
         /**
          * Called when routes are removed.
          *
          * @param routes the list of routes that have been removed. It's never empty.
+         * @deprecated Use {@link #onRoutesUpdated(List)} instead.
          */
+        @Deprecated
         public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {}
 
         /**
-         * Called when routes are changed. For example, it is called when the route's name or volume
-         * have been changed.
+         * Called when the properties of one or more existing routes are changed. For example, it is
+         * called when a route's name or volume have changed.
          *
          * @param routes the list of routes that have been changed. It's never empty.
+         * @deprecated Use {@link #onRoutesUpdated(List)} instead.
          */
+        @Deprecated
         public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
 
         /**
+         * Called when the route list is updated, which can happen when routes are added, removed,
+         * or modified. It will also be called when a route callback is registered.
+         *
+         * @param routes the updated list of routes filtered by the callback's individual discovery
+         *     preferences.
+         */
+        public void onRoutesUpdated(@NonNull List<MediaRoute2Info> routes) {}
+
+        /**
          * Called when the client app's preferred features are changed. When this is called, it is
          * recommended to {@link #getRoutes()} to get the routes that are currently available to the
          * app.
@@ -1985,21 +1997,9 @@
         }
 
         @Override
-        public void notifyRoutesAdded(List<MediaRoute2Info> routes) {
+        public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
             mHandler.sendMessage(
-                    obtainMessage(MediaRouter2::addRoutesOnHandler, MediaRouter2.this, routes));
-        }
-
-        @Override
-        public void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
-            mHandler.sendMessage(
-                    obtainMessage(MediaRouter2::removeRoutesOnHandler, MediaRouter2.this, routes));
-        }
-
-        @Override
-        public void notifyRoutesChanged(List<MediaRoute2Info> routes) {
-            mHandler.sendMessage(
-                    obtainMessage(MediaRouter2::changeRoutesOnHandler, MediaRouter2.this, routes));
+                    obtainMessage(MediaRouter2::updateRoutesOnHandler, MediaRouter2.this, routes));
         }
 
         @Override
@@ -2047,17 +2047,7 @@
     class ManagerCallback implements MediaRouter2Manager.Callback {
 
         @Override
-        public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {
-            updateAllRoutesFromManager();
-        }
-
-        @Override
-        public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {
-            updateAllRoutesFromManager();
-        }
-
-        @Override
-        public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {
+        public void onRoutesUpdated() {
             updateAllRoutesFromManager();
         }
 
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 44c0b54..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,43 +544,21 @@
         try {
             int requestId = mNextRequestId.getAndIncrement();
             mMediaRouterService.setSessionVolumeWithManager(
-                    getOrCreateClient(), requestId, sessionInfo.getId(), volume);
+                    mClient, requestId, sessionInfo.getId(), volume);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
     }
 
-    void addRoutesOnHandler(List<MediaRoute2Info> routes) {
+    void updateRoutesOnHandler(@NonNull List<MediaRoute2Info> routes) {
         synchronized (mRoutesLock) {
+            mRoutes.clear();
             for (MediaRoute2Info route : routes) {
                 mRoutes.put(route.getId(), route);
             }
         }
-        if (routes.size() > 0) {
-            notifyRoutesAdded(routes);
-        }
-    }
 
-    void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
-        synchronized (mRoutesLock) {
-            for (MediaRoute2Info route : routes) {
-                mRoutes.remove(route.getId());
-            }
-        }
-        if (routes.size() > 0) {
-            notifyRoutesRemoved(routes);
-        }
-    }
-
-    void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
-        synchronized (mRoutesLock) {
-            for (MediaRoute2Info route : routes) {
-                mRoutes.put(route.getId(), route);
-            }
-        }
-        if (routes.size() > 0) {
-            notifyRoutesChanged(routes);
-        }
+        notifyRoutesUpdated();
     }
 
     void createSessionOnHandler(int requestId, RoutingSessionInfo sessionInfo) {
@@ -650,24 +632,9 @@
         notifySessionUpdated(sessionInfo);
     }
 
-    private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
+    private void notifyRoutesUpdated() {
         for (CallbackRecord record: mCallbackRecords) {
-            record.mExecutor.execute(
-                    () -> record.mCallback.onRoutesAdded(routes));
-        }
-    }
-
-    private void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
-        for (CallbackRecord record: mCallbackRecords) {
-            record.mExecutor.execute(
-                    () -> record.mCallback.onRoutesRemoved(routes));
-        }
-    }
-
-    private void notifyRoutesChanged(List<MediaRoute2Info> routes) {
-        for (CallbackRecord record: mCallbackRecords) {
-            record.mExecutor.execute(
-                    () -> record.mCallback.onRoutesChanged(routes));
+            record.mExecutor.execute(() -> record.mCallback.onRoutesUpdated());
         }
     }
 
@@ -802,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();
         }
@@ -841,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();
         }
@@ -862,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();
         }
@@ -880,7 +846,7 @@
 
         try {
             mMediaRouterService.transferToRouteWithManager(
-                    getOrCreateClient(), requestId, session.getId(), route);
+                    mClient, requestId, session.getId(), route);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
@@ -897,7 +863,7 @@
 
         try {
             mMediaRouterService.requestCreateSessionWithManager(
-                    getOrCreateClient(), requestId, oldSession, route);
+                    mClient, requestId, oldSession, route);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
@@ -943,43 +909,16 @@
                 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.
      */
     public interface Callback {
-        /**
-         * Called when routes are added.
-         * @param routes the list of routes that have been added. It's never empty.
-         */
-        default void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {}
 
         /**
-         * Called when routes are removed.
-         * @param routes the list of routes that have been removed. It's never empty.
+         * Called when the routes list changes. This includes adding, modifying, or removing
+         * individual routes.
          */
-        default void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {}
-
-        /**
-         * Called when routes are changed.
-         * @param routes the list of routes that have been changed. It's never empty.
-         */
-        default void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
+        default void onRoutesUpdated() {}
 
         /**
          * Called when a session is changed.
@@ -1115,21 +1054,12 @@
         }
 
         @Override
-        public void notifyRoutesAdded(List<MediaRoute2Info> routes) {
-            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::addRoutesOnHandler,
-                    MediaRouter2Manager.this, routes));
-        }
-
-        @Override
-        public void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
-            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::removeRoutesOnHandler,
-                    MediaRouter2Manager.this, routes));
-        }
-
-        @Override
-        public void notifyRoutesChanged(List<MediaRoute2Info> routes) {
-            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::changeRoutesOnHandler,
-                    MediaRouter2Manager.this, routes));
+        public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
+            mHandler.sendMessage(
+                    obtainMessage(
+                            MediaRouter2Manager::updateRoutesOnHandler,
+                            MediaRouter2Manager.this,
+                            routes));
         }
     }
 }
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/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java
new file mode 100644
index 0000000..ae162b5
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.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.mediaframeworktest.unit;
+
+import static org.junit.Assert.assertEquals;
+
+import android.bluetooth.BluetoothProfile;
+import android.media.BluetoothProfileConnectionInfo;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class BluetoothProfileConnectionInfoTest {
+
+    @Test
+    public void testCoverageLeAudioOutputVolume() {
+        final boolean supprNoisy = false;
+        final int volume = 1;
+        final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
+                .createLeAudioOutputInfo(supprNoisy, volume);
+        assertEquals(info.getProfile(), BluetoothProfile.LE_AUDIO);
+        assertEquals(info.isSuppressNoisyIntent(), supprNoisy);
+        assertEquals(info.isLeOutput(), true);
+        assertEquals(info.getVolume(), volume);
+    }
+
+}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 4086dec..37c8367 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -32,7 +32,6 @@
 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_FIXED_VOLUME;
 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_SPECIAL_FEATURE;
 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_VARIABLE_VOLUME;
-import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_NAME2;
 import static com.android.mediaroutertest.StubMediaRoute2ProviderService.VOLUME_MAX;
 
 import static org.junit.Assert.assertEquals;
@@ -56,10 +55,10 @@
 import android.os.Bundle;
 import android.text.TextUtils;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.PollingCheck;
 
@@ -69,6 +68,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -115,7 +115,7 @@
 
     @Before
     public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
         mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.MEDIA_CONTENT_CONTROL,
                 Manifest.permission.MODIFY_AUDIO_ROUTING);
@@ -170,51 +170,95 @@
     }
 
     @Test
-    public void testOnRoutesRemovedAndAdded() throws Exception {
-        RouteCallback routeCallback = new RouteCallback() {};
-        mRouteCallbacks.add(routeCallback);
-        mRouter2.registerRouteCallback(mExecutor, routeCallback,
-                new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build());
+    public void testOnRoutesUpdated() throws Exception {
+        final String routeId0 = "routeId0";
+        final String routeName0 = "routeName0";
+        final String routeId1 = "routeId1";
+        final String routeName1 = "routeName1";
+        final List<String> features = Collections.singletonList("customFeature");
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
+        final int newConnectionState = MediaRoute2Info.CONNECTION_STATE_CONNECTED;
 
-        CountDownLatch removedLatch = new CountDownLatch(1);
+        final List<MediaRoute2Info> routes = new ArrayList<>();
+        routes.add(new MediaRoute2Info.Builder(routeId0, routeName0).addFeatures(features).build());
+        routes.add(new MediaRoute2Info.Builder(routeId1, routeName1).addFeatures(features).build());
+
         CountDownLatch addedLatch = new CountDownLatch(1);
+        CountDownLatch changedLatch = new CountDownLatch(1);
+        CountDownLatch removedLatch = new CountDownLatch(1);
 
-        addManagerCallback(new MediaRouter2Manager.Callback() {
-            @Override
-            public void onRoutesRemoved(List<MediaRoute2Info> routes) {
-                assertTrue(routes.size() > 0);
-                for (MediaRoute2Info route : routes) {
-                    if (route.getOriginalId().equals(ROUTE_ID2)
-                            && route.getName().equals(ROUTE_NAME2)) {
-                        removedLatch.countDown();
+        addManagerCallback(
+                new MediaRouter2Manager.Callback() {
+                    @Override
+                    public void onRoutesUpdated() {
+                        if (addedLatch.getCount() == 1
+                                && checkRoutesMatch(mManager.getAllRoutes(), routes)) {
+                            addedLatch.countDown();
+                        } else if (changedLatch.getCount() == 1
+                                && checkRoutesMatch(
+                                        mManager.getAllRoutes(), routes.subList(1, 2))) {
+                            changedLatch.countDown();
+                        } else if (removedLatch.getCount() == 1
+                                && checkRoutesRemoved(mManager.getAllRoutes(), routes)) {
+                            removedLatch.countDown();
+                        }
                     }
-                }
+                });
+
+        mService.addRoutes(routes);
+        assertTrue(
+                "Added routes not found or onRoutesUpdated() never called.",
+                addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        MediaRoute2Info newRoute2 =
+                new MediaRoute2Info.Builder(routes.get(1))
+                        .setConnectionState(newConnectionState)
+                        .build();
+        routes.set(1, newRoute2);
+        mService.addRoute(routes.get(1));
+        assertTrue(
+                "Modified route not found or onRoutesUpdated() never called.",
+                changedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        List<String> routeIds = new ArrayList<>();
+        routeIds.add(routeId0);
+        routeIds.add(routeId1);
+
+        mService.removeRoutes(routeIds);
+        assertTrue(
+                "Removed routes not found or onRoutesUpdated() never called.",
+                removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    private static boolean checkRoutesMatch(
+            List<MediaRoute2Info> routesReceived, List<MediaRoute2Info> expectedRoutes) {
+        for (MediaRoute2Info expectedRoute : expectedRoutes) {
+            MediaRoute2Info matchingRoute =
+                    routesReceived.stream()
+                            .filter(r -> r.getOriginalId().equals(expectedRoute.getOriginalId()))
+                            .findFirst()
+                            .orElse(null);
+
+            if (matchingRoute == null) {
+                return false;
             }
-            @Override
-            public void onRoutesAdded(List<MediaRoute2Info> routes) {
-                assertTrue(routes.size() > 0);
-                if (removedLatch.getCount() > 0) {
-                    return;
-                }
-                for (MediaRoute2Info route : routes) {
-                    if (route.getOriginalId().equals(ROUTE_ID2)
-                            && route.getName().equals(ROUTE_NAME2)) {
-                        addedLatch.countDown();
-                    }
-                }
+            assertTrue(TextUtils.equals(expectedRoute.getName(), matchingRoute.getName()));
+            assertEquals(expectedRoute.getFeatures(), matchingRoute.getFeatures());
+            assertEquals(expectedRoute.getConnectionState(), matchingRoute.getConnectionState());
+        }
+
+        return true;
+    }
+
+    private static boolean checkRoutesRemoved(
+            List<MediaRoute2Info> routesReceived, List<MediaRoute2Info> routesRemoved) {
+        for (MediaRoute2Info removedRoute : routesRemoved) {
+            if (routesReceived.stream()
+                    .anyMatch(r -> r.getOriginalId().equals(removedRoute.getOriginalId()))) {
+                return false;
             }
-        });
-
-        MediaRoute2Info routeToRemove = routes.get(ROUTE_ID2);
-        assertNotNull(routeToRemove);
-
-        mService.removeRoute(ROUTE_ID2);
-        assertTrue(removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        mService.addRoute(routeToRemove);
-        assertTrue(addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        }
+        return true;
     }
 
     @Test
@@ -874,28 +918,31 @@
 
         // A dummy callback is required to send route feature info.
         RouteCallback routeCallback = new RouteCallback() {};
-        MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
-            @Override
-            public void onRoutesAdded(List<MediaRoute2Info> routes) {
-                for (MediaRoute2Info route : routes) {
-                    if (!route.isSystemRoute()
-                            && hasMatchingFeature(route.getFeatures(), preference
-                            .getPreferredFeatures())) {
-                        addedLatch.countDown();
-                        break;
+        MediaRouter2Manager.Callback managerCallback =
+                new MediaRouter2Manager.Callback() {
+                    @Override
+                    public void onRoutesUpdated() {
+                        List<MediaRoute2Info> routes = mManager.getAllRoutes();
+                        for (MediaRoute2Info route : routes) {
+                            if (!route.isSystemRoute()
+                                    && hasMatchingFeature(
+                                            route.getFeatures(),
+                                            preference.getPreferredFeatures())) {
+                                addedLatch.countDown();
+                                break;
+                            }
+                        }
                     }
-                }
-            }
 
-            @Override
-            public void onDiscoveryPreferenceChanged(String packageName,
-                    RouteDiscoveryPreference discoveryPreference) {
-                if (TextUtils.equals(mPackageName, packageName)
-                        && Objects.equals(preference, discoveryPreference)) {
-                    preferenceLatch.countDown();
-                }
-            }
-        };
+                    @Override
+                    public void onDiscoveryPreferenceChanged(
+                            String packageName, RouteDiscoveryPreference discoveryPreference) {
+                        if (TextUtils.equals(mPackageName, packageName)
+                                && Objects.equals(preference, discoveryPreference)) {
+                            preferenceLatch.countDown();
+                        }
+                    }
+                };
         mManager.registerCallback(mExecutor, managerCallback);
         mRouter2.registerRouteCallback(mExecutor, routeCallback, preference);
 
@@ -923,15 +970,17 @@
     void awaitOnRouteChangedManager(Runnable task, String routeId,
             Predicate<MediaRoute2Info> predicate) throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
-        MediaRouter2Manager.Callback callback = new MediaRouter2Manager.Callback() {
-            @Override
-            public void onRoutesChanged(List<MediaRoute2Info> changed) {
-                MediaRoute2Info route = createRouteMap(changed).get(routeId);
-                if (route != null && predicate.test(route)) {
-                    latch.countDown();
-                }
-            }
-        };
+        MediaRouter2Manager.Callback callback =
+                new MediaRouter2Manager.Callback() {
+                    @Override
+                    public void onRoutesUpdated() {
+                        MediaRoute2Info route =
+                                createRouteMap(mManager.getAllRoutes()).get(routeId);
+                        if (route != null && predicate.test(route)) {
+                            latch.countDown();
+                        }
+                    }
+                };
         mManager.registerCallback(mExecutor, callback);
         try {
             task.run();
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
index a51e371..a7ae5f4 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
@@ -30,7 +30,9 @@
 import android.os.IBinder;
 import android.text.TextUtils;
 
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
@@ -146,19 +148,44 @@
      * they have the same route id.
      */
     public void addRoute(@NonNull MediaRoute2Info route) {
-        Objects.requireNonNull(route, "route must not be null");
-        mRoutes.put(route.getOriginalId(), route);
-        publishRoutes();
+        addRoutes(Collections.singletonList(route));
     }
 
     /**
-     * Removes a route and publishes it.
+     * Adds a list of routes and publishes it. It will replace existing routes with matching ids.
+     *
+     * @param routes list of routes to be added.
      */
+    public void addRoutes(@NonNull List<MediaRoute2Info> routes) {
+        Objects.requireNonNull(routes, "Routes must not be null.");
+        for (MediaRoute2Info route : routes) {
+            Objects.requireNonNull(route, "Route must not be null");
+            mRoutes.put(route.getOriginalId(), route);
+        }
+        publishRoutes();
+    }
+
+    /** Removes a route and publishes it. */
     public void removeRoute(@NonNull String routeId) {
-        Objects.requireNonNull(routeId, "routeId must not be null");
-        MediaRoute2Info route = mRoutes.get(routeId);
-        if (route != null) {
-            mRoutes.remove(routeId);
+        removeRoutes(Collections.singletonList(routeId));
+    }
+
+    /**
+     * Removes a list of routes and publishes the changes.
+     *
+     * @param routes list of route ids to be removed.
+     */
+    public void removeRoutes(@NonNull List<String> routes) {
+        Objects.requireNonNull(routes, "Routes must not be null");
+        boolean hasRemovedRoutes = false;
+        for (String routeId : routes) {
+            MediaRoute2Info route = mRoutes.get(routeId);
+            if (route != null) {
+                mRoutes.remove(routeId);
+                hasRemovedRoutes = true;
+            }
+        }
+        if (hasRemovedRoutes) {
             publishRoutes();
         }
     }
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/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
index 98438cd..b6b8837 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
@@ -189,7 +189,8 @@
                         out.write(buffer, 0, bytesRead);
                     }
                 }
-            } catch (IOException | SecurityException | IllegalStateException e) {
+            } catch (IOException | SecurityException | IllegalStateException
+                    | IllegalArgumentException e) {
                 Log.w(LOG_TAG, "Error staging apk from content URI", e);
                 return false;
             }
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
new file mode 100644
index 0000000..9578fcf
--- /dev/null
+++ b/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml
@@ -0,0 +1,138 @@
+<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>
+    <XML>
+      <option name="XML_KEEP_LINE_BREAKS" value="true" />
+    </XML>
+    <codeStyleSettings language="XML">
+      <option name="FORCE_REARRANGE_MODE" value="1" />
+      <indentOptions>
+        <option name="CONTINUATION_INDENT_SIZE" value="4" />
+      </indentOptions>
+      <arrangement>
+        <rules>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>xmlns:android</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>xmlns:.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>BY_NAME</order>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*:id</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*:name</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>name</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>style</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>BY_NAME</order>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>ANDROID_ATTRIBUTE_ORDER</order>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>.*</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>BY_NAME</order>
+            </rule>
+          </section>
+        </rules>
+      </arrangement>
+    </codeStyleSettings>
+    <codeStyleSettings language="kotlin">
+      <indentOptions>
+        <option name="CONTINUATION_INDENT_SIZE" value="4" />
+      </indentOptions>
+    </codeStyleSettings>
+  </code_scheme>
+</component>
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/.idea/codeStyles/codeStyleConfig.xml b/packages/SettingsLib/Spa/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..0f7bc51
--- /dev/null
+++ b/packages/SettingsLib/Spa/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+  <state>
+    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+  </state>
+</component>
diff --git a/packages/SettingsLib/Spa/.idea/copyright/Apache_2.xml b/packages/SettingsLib/Spa/.idea/copyright/Apache_2.xml
new file mode 100644
index 0000000..d1866d0
--- /dev/null
+++ b/packages/SettingsLib/Spa/.idea/copyright/Apache_2.xml
@@ -0,0 +1,10 @@
+<component name="CopyrightManager">
+    <copyright>
+        <option name="notice"
+            value="Copyright (C) &amp;#36;today.year The Android Open Source Project&#10;&#10;Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10;you may not use this file except in compliance with the License.&#10;You may obtain a copy of the License at&#10;&#10;     http://www.apache.org/licenses/LICENSE-2.0&#10;&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10;See the License for the specific language governing permissions and&#10;limitations under the License."/>
+        <option name="keyword" value="Copyright"/>
+        <option name="allowReplaceKeyword" value=""/>
+        <option name="myName" value="Apache 2"/>
+        <option name="myLocal" value="true"/>
+    </copyright>
+</component>
diff --git a/packages/SettingsLib/Spa/.idea/copyright/profiles_settings.xml b/packages/SettingsLib/Spa/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..b011125
--- /dev/null
+++ b/packages/SettingsLib/Spa/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,8 @@
+<component name="CopyrightManager">
+  <settings default="Apache 2">
+    <LanguageOptions name="XML">
+      <option name="fileTypeOverride" value="3" />
+      <option name="prefixLines" value="false" />
+    </LanguageOptions>
+  </settings>
+</component>
diff --git a/packages/SettingsLib/Spa/.idea/vcs.xml b/packages/SettingsLib/Spa/.idea/vcs.xml
new file mode 100644
index 0000000..f3aa348
--- /dev/null
+++ b/packages/SettingsLib/Spa/.idea/vcs.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CommitMessageInspectionProfile">
+    <profile version="1.0">
+      <inspection_tool class="BodyLimit" enabled="true" level="WARNING" enabled_by_default="true" />
+      <inspection_tool class="SubjectBodySeparation" enabled="true" level="WARNING" enabled_by_default="true" />
+      <inspection_tool class="SubjectLimit" enabled="true" level="WARNING" enabled_by_default="true">
+        <option name="RIGHT_MARGIN" value="50" />
+      </inspection_tool>
+    </profile>
+  </component>
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$/../../.." vcs="Git" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/OWNERS b/packages/SettingsLib/Spa/OWNERS
index b352b04..2887872 100644
--- a/packages/SettingsLib/Spa/OWNERS
+++ b/packages/SettingsLib/Spa/OWNERS
@@ -1,6 +1,6 @@
+set noparent
+
 chaohuiw@google.com
 hanxu@google.com
 kellyz@google.com
 pierreqian@google.com
-
-per-file *.xml = set noparent
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
index 8c97eca..d380136 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -16,9 +16,9 @@
 
 buildscript {
     ext {
-        minSdk_version = 31
-        compose_version = '1.2.0-alpha04'
-        compose_material3_version = '1.0.0-alpha06'
+        spa_min_sdk = 31
+        jetpack_compose_version = '1.2.0-alpha04'
+        jetpack_compose_material3_version = '1.0.0-alpha06'
     }
 }
 plugins {
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt
deleted file mode 100644
index 862fc1e..0000000
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.codelab.page
-
-import com.android.settingslib.spa.api.SettingsPageRepository
-
-object Destinations {
-    const val Home = "Home"
-    const val Preference = "Preference"
-    const val Argument = "Argument"
-}
-
-val codelabPageRepository = SettingsPageRepository(
-    allPages = listOf(HomePageProvider, PreferencePageProvider, ArgumentPageProvider),
-    startDestination = Destinations.Home,
-)
diff --git a/packages/SettingsLib/Spa/codelab/Android.bp b/packages/SettingsLib/Spa/gallery/Android.bp
similarity index 96%
rename from packages/SettingsLib/Spa/codelab/Android.bp
rename to packages/SettingsLib/Spa/gallery/Android.bp
index 8fbbf9a..bc083c9 100644
--- a/packages/SettingsLib/Spa/codelab/Android.bp
+++ b/packages/SettingsLib/Spa/gallery/Android.bp
@@ -19,7 +19,7 @@
 }
 
 android_app {
-    name: "SpaLibCodelab",
+    name: "SpaLibGallery",
 
     srcs: ["src/**/*.kt"],
 
diff --git a/packages/SettingsLib/Spa/codelab/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
similarity index 79%
rename from packages/SettingsLib/Spa/codelab/AndroidManifest.xml
rename to packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index 9a89e5e..b5be7cd 100644
--- a/packages/SettingsLib/Spa/codelab/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -13,16 +13,16 @@
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
-  -->
+-->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.settingslib.spa.codelab">
+    package="com.android.settingslib.spa.gallery">
 
     <application
-        android:label="@string/app_name"
-        android:supportsRtl="true"
-        android:theme="@style/Theme.SettingsLib.Compose.DayNight">
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_label"
+        android:supportsRtl="true">
         <activity
-            android:name="com.android.settingslib.spa.codelab.MainActivity"
+            android:name="com.android.settingslib.spa.gallery.MainActivity"
             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/packages/SettingsLib/Spa/codelab/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
similarity index 87%
rename from packages/SettingsLib/Spa/codelab/build.gradle
rename to packages/SettingsLib/Spa/gallery/build.gradle
index 5251ddd..98e6745 100644
--- a/packages/SettingsLib/Spa/codelab/build.gradle
+++ b/packages/SettingsLib/Spa/gallery/build.gradle
@@ -20,12 +20,12 @@
 }
 
 android {
-    namespace 'com.android.settingslib.spa.codelab'
+    namespace 'com.android.settingslib.spa.gallery'
     compileSdk 33
 
     defaultConfig {
-        applicationId "com.android.settingslib.spa.codelab"
-        minSdk minSdk_version
+        applicationId "com.android.settingslib.spa.gallery"
+        minSdk spa_min_sdk
         targetSdk 33
         versionCode 1
         versionName "1.0"
@@ -52,7 +52,7 @@
         compose true
     }
     composeOptions {
-        kotlinCompilerExtensionVersion compose_version
+        kotlinCompilerExtensionVersion jetpack_compose_version
     }
     packagingOptions {
         resources {
diff --git a/packages/SettingsLib/Spa/codelab/res/values/strings.xml b/packages/SettingsLib/Spa/gallery/res/mipmap-anydpi-v26/ic_launcher.xml
similarity index 68%
copy from packages/SettingsLib/Spa/codelab/res/values/strings.xml
copy to packages/SettingsLib/Spa/gallery/res/mipmap-anydpi-v26/ic_launcher.xml
index 6fdbba0..623ef56 100644
--- a/packages/SettingsLib/Spa/codelab/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/gallery/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   Copyright (C) 2022 The Android Open Source Project
 
@@ -13,7 +14,8 @@
   See the License for the specific language governing permissions and
   limitations under the License.
   -->
-<resources>
-    <!-- Codelab App name. [DO NOT TRANSLATE] -->
-    <string name="app_name" translatable="false">SpaLib Codelab</string>
-</resources>
+
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@mipmap/ic_launcher_background" />
+    <foreground android:drawable="@mipmap/ic_launcher_foreground" />
+</adaptive-icon>
diff --git a/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher.png b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..c3f1ab9
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_background.png b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_background.png
new file mode 100644
index 0000000..7da1549
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_background.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..187964f
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/codelab/res/values/strings.xml b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
similarity index 70%
copy from packages/SettingsLib/Spa/codelab/res/values/strings.xml
copy to packages/SettingsLib/Spa/gallery/res/values/strings.xml
index 6fdbba0..0d08d68 100644
--- a/packages/SettingsLib/Spa/codelab/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   Copyright (C) 2022 The Android Open Source Project
 
@@ -14,6 +15,8 @@
   limitations under the License.
   -->
 <resources>
-    <!-- Codelab App name. [DO NOT TRANSLATE] -->
-    <string name="app_name" translatable="false">SpaLib Codelab</string>
+    <!-- Gallery App label. [DO NOT TRANSLATE] -->
+    <string name="app_label" translatable="false">Gallery</string>
+    <!-- Gallery App name. [DO NOT TRANSLATE] -->
+    <string name="app_name" translatable="false">SpaLib Gallery</string>
 </resources>
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/MainActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
similarity index 80%
rename from packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/MainActivity.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
index eef81e0..7db53b4 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/MainActivity.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.codelab
+package com.android.settingslib.spa.gallery
 
-import com.android.settingslib.spa.codelab.page.codelabPageRepository
 import com.android.settingslib.spa.framework.SpaActivity
+import com.android.settingslib.spa.gallery.page.galleryPageRepository
 
-class MainActivity : SpaActivity(codelabPageRepository)
+class MainActivity : SpaActivity(galleryPageRepository)
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
similarity index 80%
rename from packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/ArgumentPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index 484b604..36361dd 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -14,26 +14,27 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.codelab.page
+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
 import androidx.navigation.navArgument
-import com.android.settingslib.spa.api.SettingsPageProvider
-import com.android.settingslib.spa.framework.navigator
-import com.android.settingslib.spa.framework.toState
-import com.android.settingslib.spa.theme.SettingsTheme
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.compose.toState
+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
new file mode 100644
index 0000000..82005ec
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.gallery.page
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+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.compose.stateOf
+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"
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        FooterPage()
+    }
+
+    @Composable
+    fun EntryItem() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
+    }
+}
+
+@Composable
+private fun FooterPage() {
+    RegularScaffold(title = TITLE) {
+        Preference(remember {
+            object : PreferenceModel {
+                override val title = "Some Preference"
+                override val summary = stateOf("Some summary")
+            }
+        })
+        Footer(footerText = "Footer text always at the end of page.")
+    }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun FooterPagePreview() {
+    SettingsTheme {
+        FooterPage()
+    }
+}
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt
similarity index 77%
rename from packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt
index 27c70e4..6b7de8d 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.codelab.page
+package com.android.settingslib.spa.gallery.page
 
 import android.os.Bundle
 import androidx.compose.foundation.layout.Column
@@ -25,13 +25,13 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.api.SettingsPageProvider
-import com.android.settingslib.spa.codelab.R
-import com.android.settingslib.spa.theme.SettingsDimension
-import com.android.settingslib.spa.theme.SettingsTheme
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+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?) {
@@ -50,8 +50,12 @@
         )
 
         PreferencePageProvider.EntryItem()
-
+        SwitchPreferencePageProvider.EntryItem()
         ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = 0)
+
+        SliderPageProvider.EntryItem()
+        SettingsPagerPageProvider.EntryItem()
+        FooterPageProvider.EntryItem()
     }
 }
 
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
new file mode 100644
index 0000000..cbfc603
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.page
+
+import com.android.settingslib.spa.framework.api.SettingsPageRepository
+
+val galleryPageRepository = SettingsPageRepository(
+    allPages = listOf(
+        HomePageProvider,
+        PreferencePageProvider,
+        SwitchPreferencePageProvider,
+        ArgumentPageProvider,
+        SliderPageProvider,
+        SettingsPagerPageProvider,
+        FooterPageProvider,
+    ),
+    startDestination = HomePageProvider.name,
+)
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt
similarity index 69%
rename from packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PreferencePage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt
index 81627f6..0463e58 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt
@@ -14,32 +14,35 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.codelab.page
+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
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.produceState
-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.tooling.preview.Preview
-import com.android.settingslib.spa.api.SettingsPageProvider
-import com.android.settingslib.spa.framework.navigator
-import com.android.settingslib.spa.framework.toState
-import com.android.settingslib.spa.theme.SettingsTheme
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.compose.toState
+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?) {
@@ -49,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"
         })
@@ -75,14 +78,17 @@
             }
         })
 
-        var count by remember { mutableStateOf(0) }
+        var count by rememberSaveable { mutableStateOf(0) }
         Preference(object : PreferenceModel {
             override val title = "Click me"
             override val summary = derivedStateOf { count.toString() }
             override val onClick: (() -> Unit) = { count++ }
+            override val icon = @Composable {
+                SettingsIcon(imageVector = Icons.Outlined.TouchApp)
+            }
         })
 
-        var ticks by remember { mutableStateOf(0) }
+        var ticks by rememberSaveable { mutableStateOf(0) }
         LaunchedEffect(ticks) {
             delay(1000L)
             ticks++
@@ -96,6 +102,9 @@
             override val title = "Disabled"
             override val summary = "Disabled".toState()
             override val enabled = false.toState()
+            override val icon = @Composable {
+              SettingsIcon(imageVector = Icons.Outlined.DisabledByDefault)
+            }
         })
     }
 }
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
new file mode 100644
index 0000000..df48517
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.gallery.page
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+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.SettingsPager
+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"
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        SettingsPagerPage()
+    }
+
+    @Composable
+    fun EntryItem() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
+    }
+}
+
+@Composable
+private fun SettingsPagerPage() {
+    SettingsScaffold(title = TITLE) {
+        SettingsPager(listOf("Personal", "Work")) {
+            PlaceholderTitle("Page $it")
+        }
+    }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SettingsPagerPagePreview() {
+    SettingsTheme {
+        SettingsPagerPage()
+    }
+}
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
new file mode 100644
index 0000000..04046fa
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.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.spa.gallery.page
+
+import android.os.Bundle
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AccessAlarm
+import androidx.compose.material.icons.outlined.MusicNote
+import androidx.compose.material.icons.outlined.MusicOff
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+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 = "Slider"
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        SliderPage()
+    }
+
+    @Composable
+    fun EntryItem() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
+    }
+}
+
+@Composable
+private fun SliderPage() {
+    RegularScaffold(title = TITLE) {
+        SettingsSlider(object : SettingsSliderModel {
+            override val title = "Slider"
+            override val initValue = 40
+        })
+
+        SettingsSlider(object : SettingsSliderModel {
+            override val title = "Slider with icon"
+            override val initValue = 30
+            override val onValueChangeFinished = {
+                println("onValueChangeFinished")
+            }
+            override val icon = Icons.Outlined.AccessAlarm
+        })
+
+        val initValue = 0
+        var icon by remember { mutableStateOf(Icons.Outlined.MusicOff) }
+        var sliderPosition by remember { mutableStateOf(initValue) }
+        SettingsSlider(object : SettingsSliderModel {
+            override val title = "Slider with changeable icon"
+            override val initValue = initValue
+            override val onValueChange = { it: Int ->
+                sliderPosition = it
+                icon = if (it > 0) Icons.Outlined.MusicNote else Icons.Outlined.MusicOff
+            }
+            override val onValueChangeFinished = {
+                println("onValueChangeFinished: the value is $sliderPosition")
+            }
+            override val icon = icon
+        })
+
+        SettingsSlider(object : SettingsSliderModel {
+            override val title = "Slider with steps"
+            override val initValue = 2
+            override val valueRange = 1..5
+            override val showSteps = true
+        })
+    }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SliderPagePreview() {
+    SettingsTheme {
+        SliderPage()
+    }
+}
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
new file mode 100644
index 0000000..e9e5d35
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.gallery.page
+
+import android.os.Bundle
+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.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.compose.stateOf
+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.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 = "SwitchPreference"
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        SwitchPreferencePage()
+    }
+
+    @Composable
+    fun EntryItem() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
+    }
+}
+
+@Composable
+private fun SwitchPreferencePage() {
+    RegularScaffold(title = TITLE) {
+        SampleSwitchPreference()
+        SampleSwitchPreferenceWithSummary()
+        SampleSwitchPreferenceWithAsyncSummary()
+        SampleNotChangeableSwitchPreference()
+    }
+}
+
+@Composable
+private fun SampleSwitchPreference() {
+    val checked = rememberSaveable { mutableStateOf(false) }
+    SwitchPreference(remember {
+        object : SwitchPreferenceModel {
+            override val title = "SwitchPreference"
+            override val checked = checked
+            override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+        }
+    })
+}
+
+@Composable
+private fun SampleSwitchPreferenceWithSummary() {
+    val checked = rememberSaveable { mutableStateOf(true) }
+    SwitchPreference(remember {
+        object : SwitchPreferenceModel {
+            override val title = "SwitchPreference"
+            override val summary = stateOf("With summary")
+            override val checked = checked
+            override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+        }
+    })
+}
+
+@Composable
+private fun SampleSwitchPreferenceWithAsyncSummary() {
+    val checked = rememberSaveable { mutableStateOf(true) }
+    val summary = produceState(initialValue = " ") {
+        delay(1000L)
+        value = "Async summary"
+    }
+    SwitchPreference(remember {
+        object : SwitchPreferenceModel {
+            override val title = "SwitchPreference"
+            override val summary = summary
+            override val checked = checked
+            override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+        }
+    })
+}
+
+@Composable
+private fun SampleNotChangeableSwitchPreference() {
+    val checked = rememberSaveable { mutableStateOf(true) }
+    SwitchPreference(remember {
+        object : SwitchPreferenceModel {
+            override val title = "SwitchPreference"
+            override val summary = stateOf("Not changeable")
+            override val changeable = stateOf(false)
+            override val checked = checked
+            override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+        }
+    })
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SwitchPreferencePagePreview() {
+    SettingsTheme {
+        SwitchPreferencePage()
+    }
+}
diff --git a/packages/SettingsLib/Spa/settings.gradle b/packages/SettingsLib/Spa/settings.gradle
index 1b69c1e..897fa67 100644
--- a/packages/SettingsLib/Spa/settings.gradle
+++ b/packages/SettingsLib/Spa/settings.gradle
@@ -30,5 +30,5 @@
 }
 rootProject.name = "SpaLib"
 include ':spa'
-include ':codelab'
+include ':gallery'
 include ':tests'
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 463c076..3c8d91e 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -31,6 +31,9 @@
         "androidx.navigation_navigation-compose",
         "com.google.android.material_material",
     ],
-    kotlincflags: ["-Xjvm-default=all"],
+    kotlincflags: [
+        "-Xjvm-default=all",
+        "-Xopt-in=kotlin.RequiresOptIn",
+    ],
     min_sdk_version: "31",
 }
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 60794c8..ad69da31 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -24,7 +24,7 @@
     compileSdk 33
 
     defaultConfig {
-        minSdk minSdk_version
+        minSdk spa_min_sdk
         targetSdk 33
     }
 
@@ -43,13 +43,13 @@
     }
     kotlinOptions {
         jvmTarget = '1.8'
-        freeCompilerArgs = ["-Xjvm-default=all"]
+        freeCompilerArgs = ["-Xjvm-default=all", "-Xopt-in=kotlin.RequiresOptIn"]
     }
     buildFeatures {
         compose true
     }
     composeOptions {
-        kotlinCompilerExtensionVersion compose_version
+        kotlinCompilerExtensionVersion jetpack_compose_version
     }
     packagingOptions {
         resources {
@@ -59,11 +59,11 @@
 }
 
 dependencies {
-    api "androidx.compose.material3:material3:$compose_material3_version"
-    api "androidx.compose.material:material-icons-extended:$compose_version"
-    api "androidx.compose.runtime:runtime-livedata:$compose_version"
-    api "androidx.compose.ui:ui-tooling-preview:$compose_version"
+    api "androidx.compose.material3:material3:$jetpack_compose_material3_version"
+    api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
+    api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
+    api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
     api 'androidx.navigation:navigation-compose:2.5.0'
     api 'com.google.android.material:material:1.6.1'
-    debugApi "androidx.compose.ui:ui-tooling:$compose_version"
+    debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
 }
diff --git a/packages/SettingsLib/Spa/spa/res/values-night/themes.xml b/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
index 8b52b50..67dd2b0 100644
--- a/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
@@ -13,8 +13,8 @@
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
-  -->
+-->
 <resources>
 
-    <style name="Theme.SettingsLib.Compose.DayNight" />
+    <style name="Theme.SpaLib.DayNight" />
 </resources>
diff --git a/packages/SettingsLib/Spa/spa/res/values/themes.xml b/packages/SettingsLib/Spa/spa/res/values/themes.xml
index 01f9ea5..e0e5fc2 100644
--- a/packages/SettingsLib/Spa/spa/res/values/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values/themes.xml
@@ -13,15 +13,15 @@
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
-  -->
+-->
 <resources>
 
-    <style name="Theme.SettingsLib.Compose" parent="Theme.Material3.DayNight.NoActionBar">
+    <style name="Theme.SpaLib" parent="Theme.Material3.DayNight.NoActionBar">
         <item name="android:statusBarColor">@android:color/transparent</item>
         <item name="android:navigationBarColor">@android:color/transparent</item>
     </style>
 
-    <style name="Theme.SettingsLib.Compose.DayNight">
+    <style name="Theme.SpaLib.DayNight">
         <item name="android:windowLightStatusBar">true</item>
     </style>
 </resources>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt
index 28a5899e..51d3714 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt
@@ -24,14 +24,17 @@
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
 import androidx.navigation.compose.rememberNavController
-import com.android.settingslib.spa.api.SettingsPageProvider
-import com.android.settingslib.spa.api.SettingsPageRepository
-import com.android.settingslib.spa.theme.SettingsTheme
+import com.android.settingslib.spa.R
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.api.SettingsPageRepository
+import com.android.settingslib.spa.framework.compose.localNavController
+import com.android.settingslib.spa.framework.theme.SettingsTheme
 
 open class SpaActivity(
     private val settingsPageRepository: SettingsPageRepository,
 ) : ComponentActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
+        setTheme(R.style.Theme_SpaLib_DayNight)
         super.onCreate(savedInstanceState)
         setContent {
             MainContent()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/api/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageProvider.kt
similarity index 95%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/api/SettingsPageProvider.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageProvider.kt
index 0ad0003..84daf22 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/api/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageProvider.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.api
+package com.android.settingslib.spa.framework.api
 
 import android.os.Bundle
 import androidx.compose.runtime.Composable
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/api/SettingsPageRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageRepository.kt
similarity index 93%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/api/SettingsPageRepository.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageRepository.kt
index ce39f4f..4a270b1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/api/SettingsPageRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageRepository.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.api
+package com.android.settingslib.spa.framework.api
 
 data class SettingsPageRepository(
     val allPages: List<SettingsPageProvider>,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
new file mode 100644
index 0000000..ae325f8
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.graphics.drawable.Animatable
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
+import android.os.Handler
+import android.os.Looper
+import android.view.View
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.RememberObserver
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.asAndroidColorFilter
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.graphics.painter.BitmapPainter
+import androidx.compose.ui.graphics.painter.ColorPainter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.withSave
+import androidx.compose.ui.unit.LayoutDirection
+import kotlin.math.roundToInt
+
+/**
+ * *************************************************************************************************
+ * This file was forked from
+ * https://github.com/google/accompanist/blob/main/drawablepainter/src/main/java/com/google/accompanist/drawablepainter/DrawablePainter.kt
+ * and will be removed once it lands in AndroidX.
+ */
+
+private val MAIN_HANDLER by lazy(LazyThreadSafetyMode.NONE) {
+    Handler(Looper.getMainLooper())
+}
+
+/**
+ * A [Painter] which draws an Android [Drawable] and supports [Animatable] drawables. Instances
+ * should be remembered to be able to start and stop [Animatable] animations.
+ *
+ * Instances are usually retrieved from [rememberDrawablePainter].
+ */
+class DrawablePainter(
+    val drawable: Drawable
+) : Painter(), RememberObserver {
+    private var drawInvalidateTick by mutableStateOf(0)
+    private var drawableIntrinsicSize by mutableStateOf(drawable.intrinsicSize)
+
+    private val callback: Drawable.Callback by lazy {
+        object : Drawable.Callback {
+            override fun invalidateDrawable(d: Drawable) {
+                // Update the tick so that we get re-drawn
+                drawInvalidateTick++
+                // Update our intrinsic size too
+                drawableIntrinsicSize = drawable.intrinsicSize
+            }
+
+            override fun scheduleDrawable(d: Drawable, what: Runnable, time: Long) {
+                MAIN_HANDLER.postAtTime(what, time)
+            }
+
+            override fun unscheduleDrawable(d: Drawable, what: Runnable) {
+                MAIN_HANDLER.removeCallbacks(what)
+            }
+        }
+    }
+
+    init {
+        if (drawable.intrinsicWidth >= 0 && drawable.intrinsicHeight >= 0) {
+            // Update the drawable's bounds to match the intrinsic size
+            drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
+        }
+    }
+
+    override fun onRemembered() {
+        drawable.callback = callback
+        drawable.setVisible(true, true)
+        if (drawable is Animatable) drawable.start()
+    }
+
+    override fun onAbandoned() = onForgotten()
+
+    override fun onForgotten() {
+        if (drawable is Animatable) drawable.stop()
+        drawable.setVisible(false, false)
+        drawable.callback = null
+    }
+
+    override fun applyAlpha(alpha: Float): Boolean {
+        drawable.alpha = (alpha * 255).roundToInt().coerceIn(0, 255)
+        return true
+    }
+
+    override fun applyColorFilter(colorFilter: ColorFilter?): Boolean {
+        drawable.colorFilter = colorFilter?.asAndroidColorFilter()
+        return true
+    }
+
+    override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean =
+        drawable.setLayoutDirection(
+            when (layoutDirection) {
+                LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
+                LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
+            }
+        )
+
+    override val intrinsicSize: Size get() = drawableIntrinsicSize
+
+    override fun DrawScope.onDraw() {
+        drawIntoCanvas { canvas ->
+            // Reading this ensures that we invalidate when invalidateDrawable() is called
+            drawInvalidateTick
+
+            // Update the Drawable's bounds
+            drawable.setBounds(0, 0, size.width.roundToInt(), size.height.roundToInt())
+
+            canvas.withSave {
+                drawable.draw(canvas.nativeCanvas)
+            }
+        }
+    }
+}
+
+/**
+ * Remembers [Drawable] wrapped up as a [Painter]. This function attempts to un-wrap the
+ * drawable contents and use Compose primitives where possible.
+ *
+ * If the provided [drawable] is `null`, an empty no-op painter is returned.
+ *
+ * This function tries to dispatch lifecycle events to [drawable] as much as possible from
+ * within Compose.
+ *
+ * @sample com.google.accompanist.sample.drawablepainter.BasicSample
+ */
+@Composable
+fun rememberDrawablePainter(drawable: Drawable?): Painter = remember(drawable) {
+    when (drawable) {
+        null -> EmptyPainter
+        is BitmapDrawable -> BitmapPainter(drawable.bitmap.asImageBitmap())
+        is ColorDrawable -> ColorPainter(Color(drawable.color))
+        // Since the DrawablePainter will be remembered and it implements RememberObserver, it
+        // will receive the necessary events
+        else -> DrawablePainter(drawable.mutate())
+    }
+}
+
+private val Drawable.intrinsicSize: Size
+    get() = when {
+        // Only return a finite size if the drawable has an intrinsic size
+        intrinsicWidth >= 0 && intrinsicHeight >= 0 -> {
+            Size(width = intrinsicWidth.toFloat(), height = intrinsicHeight.toFloat())
+        }
+        else -> Size.Unspecified
+    }
+
+internal object EmptyPainter : Painter() {
+    override val intrinsicSize: Size get() = Size.Unspecified
+    override fun DrawScope.onDraw() {}
+}
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/NavControllerWrapper.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
similarity index 96%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/NavControllerWrapper.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
index 35112ad..c68d5de 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/NavControllerWrapper.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.framework
+package com.android.settingslib.spa.framework.compose
 
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.compositionLocalOf
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/RuntimeUtils.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/RuntimeUtils.kt
similarity index 77%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/RuntimeUtils.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/RuntimeUtils.kt
index 22d85e0..ba88546 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/RuntimeUtils.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/RuntimeUtils.kt
@@ -14,11 +14,19 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.framework
+package com.android.settingslib.spa.framework.compose
 
+import android.content.Context
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+
+@Composable
+fun <T> rememberContext(constructor: (Context) -> T): T {
+    val context = LocalContext.current
+    return remember(context) { constructor(context) }
+}
 
 /**
  * Remember the [State] initialized with the [this].
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/MaterialColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt
similarity index 95%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/MaterialColors.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt
index 8cd9184..4626f0b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/MaterialColors.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.theme
+package com.android.settingslib.spa.framework.theme
 
 import androidx.compose.material3.ColorScheme
 import androidx.compose.material3.dynamicDarkColorScheme
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt
new file mode 100644
index 0000000..27fdc91
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.theme
+
+import android.content.Context
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+
+data class SettingsColorScheme(
+    val background: Color = Color.Unspecified,
+    val categoryTitle: Color = Color.Unspecified,
+    val surface: Color = Color.Unspecified,
+    val surfaceHeader: Color = Color.Unspecified,
+    val secondaryText: Color = Color.Unspecified,
+    val primaryContainer: Color = Color.Unspecified,
+    val onPrimaryContainer: Color = Color.Unspecified,
+)
+
+internal val LocalColorScheme = staticCompositionLocalOf { SettingsColorScheme() }
+
+@Composable
+internal fun settingsColorScheme(isDarkTheme: Boolean): SettingsColorScheme {
+    val context = LocalContext.current
+    return remember(isDarkTheme) {
+        when {
+            isDarkTheme -> dynamicDarkColorScheme(context)
+            else -> dynamicLightColorScheme(context)
+        }
+    }
+}
+
+/**
+ * Creates a light dynamic color scheme.
+ *
+ * Use this function to create a color scheme based off the system wallpaper. If the developer
+ * changes the wallpaper this color scheme will change accordingly. This dynamic scheme is a
+ * light theme variant.
+ *
+ * @param context The context required to get system resource data.
+ */
+private fun dynamicLightColorScheme(context: Context): SettingsColorScheme {
+    val tonalPalette = dynamicTonalPalette(context)
+    return SettingsColorScheme(
+        background = tonalPalette.neutral95,
+        categoryTitle = tonalPalette.primary40,
+        surface = tonalPalette.neutral99,
+        surfaceHeader = tonalPalette.neutral90,
+        secondaryText = tonalPalette.neutralVariant30,
+        primaryContainer = tonalPalette.primary90,
+        onPrimaryContainer = tonalPalette.neutral10,
+    )
+}
+
+/**
+ * Creates a dark dynamic color scheme.
+ *
+ * Use this function to create a color scheme based off the system wallpaper. If the developer
+ * changes the wallpaper this color scheme will change accordingly. This dynamic scheme is a dark
+ * theme variant.
+ *
+ * @param context The context required to get system resource data.
+ */
+private fun dynamicDarkColorScheme(context: Context): SettingsColorScheme {
+    val tonalPalette = dynamicTonalPalette(context)
+    return SettingsColorScheme(
+        background = tonalPalette.neutral10,
+        categoryTitle = tonalPalette.primary90,
+        surface = tonalPalette.neutral20,
+        surfaceHeader = tonalPalette.neutral30,
+        secondaryText = tonalPalette.neutralVariant80,
+        primaryContainer = tonalPalette.secondary90,
+        onPrimaryContainer = tonalPalette.neutral10,
+    )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
similarity index 77%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsDimension.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 51e75c5..9654368 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.theme
+package com.android.settingslib.spa.framework.theme
 
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.ui.unit.dp
 
 object SettingsDimension {
+    val itemIconSize = 24.dp
     val itemIconContainerSize = 72.dp
     val itemPaddingStart = 24.dp
     val itemPaddingEnd = 16.dp
@@ -30,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/theme/SettingsOpacity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
similarity index 92%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
index 5f4da8a..04ee3c3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.theme
+package com.android.settingslib.spa.framework.theme
 
 object SettingsOpacity {
     const val Full = 1f
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
similarity index 73%
copy from packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
copy to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
index 7c9df10..20020d0 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.common.data.model
+package com.android.settingslib.spa.framework.theme
 
-/** Models a two-dimensional position */
-data class Position(
-    val x: Int,
-    val y: Int,
-)
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.ui.unit.dp
+
+object SettingsShape {
+    val CornerMedium = RoundedCornerShape(12.dp)
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
new file mode 100644
index 0000000..e6fa74e
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.ReadOnlyComposable
+
+/**
+ * The Material 3 Theme for Settings.
+ */
+@Composable
+fun SettingsTheme(content: @Composable () -> Unit) {
+    val isDarkTheme = isSystemInDarkTheme()
+    val settingsColorScheme = settingsColorScheme(isDarkTheme)
+    val colorScheme = materialColorScheme(isDarkTheme).copy(
+        background = settingsColorScheme.background,
+    )
+
+    CompositionLocalProvider(LocalColorScheme provides settingsColorScheme(isDarkTheme)) {
+        MaterialTheme(colorScheme = colorScheme, typography = rememberSettingsTypography()) {
+            content()
+        }
+    }
+}
+
+object SettingsTheme {
+    val colorScheme: SettingsColorScheme
+        @Composable
+        @ReadOnlyComposable
+        get() = LocalColorScheme.current
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt
new file mode 100644
index 0000000..f81f5e7
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.theme
+
+import android.R
+import android.content.Context
+import androidx.annotation.ColorRes
+import androidx.annotation.DoNotInline
+import androidx.compose.ui.graphics.Color
+
+/**
+ * Tonal Palette structure in Material.
+ *
+ * A tonal palette is comprised of 5 tonal ranges. Each tonal range includes the 13 stops, or
+ * tonal swatches.
+ *
+ * Tonal range names are:
+ * - Neutral (N)
+ * - Neutral variant (NV)
+ * - Primary (P)
+ * - Secondary (S)
+ * - Tertiary (T)
+ */
+internal class SettingsTonalPalette(
+    // The neutral tonal range from the generated dynamic color palette.
+    // Ordered from the lightest shade [neutral100] to the darkest shade [neutral0].
+    val neutral100: Color,
+    val neutral99: Color,
+    val neutral95: Color,
+    val neutral90: Color,
+    val neutral80: Color,
+    val neutral70: Color,
+    val neutral60: Color,
+    val neutral50: Color,
+    val neutral40: Color,
+    val neutral30: Color,
+    val neutral20: Color,
+    val neutral10: Color,
+    val neutral0: Color,
+
+    // The neutral variant tonal range, sometimes called "neutral 2",  from the
+    // generated dynamic color palette.
+    // Ordered from the lightest shade [neutralVariant100] to the darkest shade [neutralVariant0].
+    val neutralVariant100: Color,
+    val neutralVariant99: Color,
+    val neutralVariant95: Color,
+    val neutralVariant90: Color,
+    val neutralVariant80: Color,
+    val neutralVariant70: Color,
+    val neutralVariant60: Color,
+    val neutralVariant50: Color,
+    val neutralVariant40: Color,
+    val neutralVariant30: Color,
+    val neutralVariant20: Color,
+    val neutralVariant10: Color,
+    val neutralVariant0: Color,
+
+    // The primary tonal range from the generated dynamic color palette.
+    // Ordered from the lightest shade [primary100] to the darkest shade [primary0].
+    val primary100: Color,
+    val primary99: Color,
+    val primary95: Color,
+    val primary90: Color,
+    val primary80: Color,
+    val primary70: Color,
+    val primary60: Color,
+    val primary50: Color,
+    val primary40: Color,
+    val primary30: Color,
+    val primary20: Color,
+    val primary10: Color,
+    val primary0: Color,
+
+    // The secondary tonal range from the generated dynamic color palette.
+    // Ordered from the lightest shade [secondary100] to the darkest shade [secondary0].
+    val secondary100: Color,
+    val secondary99: Color,
+    val secondary95: Color,
+    val secondary90: Color,
+    val secondary80: Color,
+    val secondary70: Color,
+    val secondary60: Color,
+    val secondary50: Color,
+    val secondary40: Color,
+    val secondary30: Color,
+    val secondary20: Color,
+    val secondary10: Color,
+    val secondary0: Color,
+
+    // The tertiary tonal range from the generated dynamic color palette.
+    // Ordered from the lightest shade [tertiary100] to the darkest shade [tertiary0].
+    val tertiary100: Color,
+    val tertiary99: Color,
+    val tertiary95: Color,
+    val tertiary90: Color,
+    val tertiary80: Color,
+    val tertiary70: Color,
+    val tertiary60: Color,
+    val tertiary50: Color,
+    val tertiary40: Color,
+    val tertiary30: Color,
+    val tertiary20: Color,
+    val tertiary10: Color,
+    val tertiary0: Color,
+)
+
+/** Dynamic colors in Material. */
+internal fun dynamicTonalPalette(context: Context) = SettingsTonalPalette(
+    // The neutral tonal range from the generated dynamic color palette.
+    neutral100 = ColorResourceHelper.getColor(context, R.color.system_neutral1_0),
+    neutral99 = ColorResourceHelper.getColor(context, R.color.system_neutral1_10),
+    neutral95 = ColorResourceHelper.getColor(context, R.color.system_neutral1_50),
+    neutral90 = ColorResourceHelper.getColor(context, R.color.system_neutral1_100),
+    neutral80 = ColorResourceHelper.getColor(context, R.color.system_neutral1_200),
+    neutral70 = ColorResourceHelper.getColor(context, R.color.system_neutral1_300),
+    neutral60 = ColorResourceHelper.getColor(context, R.color.system_neutral1_400),
+    neutral50 = ColorResourceHelper.getColor(context, R.color.system_neutral1_500),
+    neutral40 = ColorResourceHelper.getColor(context, R.color.system_neutral1_600),
+    neutral30 = ColorResourceHelper.getColor(context, R.color.system_neutral1_700),
+    neutral20 = ColorResourceHelper.getColor(context, R.color.system_neutral1_800),
+    neutral10 = ColorResourceHelper.getColor(context, R.color.system_neutral1_900),
+    neutral0 = ColorResourceHelper.getColor(context, R.color.system_neutral1_1000),
+
+    // The neutral variant tonal range, sometimes called "neutral 2",  from the
+    // generated dynamic color palette.
+    neutralVariant100 = ColorResourceHelper.getColor(context, R.color.system_neutral2_0),
+    neutralVariant99 = ColorResourceHelper.getColor(context, R.color.system_neutral2_10),
+    neutralVariant95 = ColorResourceHelper.getColor(context, R.color.system_neutral2_50),
+    neutralVariant90 = ColorResourceHelper.getColor(context, R.color.system_neutral2_100),
+    neutralVariant80 = ColorResourceHelper.getColor(context, R.color.system_neutral2_200),
+    neutralVariant70 = ColorResourceHelper.getColor(context, R.color.system_neutral2_300),
+    neutralVariant60 = ColorResourceHelper.getColor(context, R.color.system_neutral2_400),
+    neutralVariant50 = ColorResourceHelper.getColor(context, R.color.system_neutral2_500),
+    neutralVariant40 = ColorResourceHelper.getColor(context, R.color.system_neutral2_600),
+    neutralVariant30 = ColorResourceHelper.getColor(context, R.color.system_neutral2_700),
+    neutralVariant20 = ColorResourceHelper.getColor(context, R.color.system_neutral2_800),
+    neutralVariant10 = ColorResourceHelper.getColor(context, R.color.system_neutral2_900),
+    neutralVariant0 = ColorResourceHelper.getColor(context, R.color.system_neutral2_1000),
+
+    // The primary tonal range from the generated dynamic color palette.
+    primary100 = ColorResourceHelper.getColor(context, R.color.system_accent1_0),
+    primary99 = ColorResourceHelper.getColor(context, R.color.system_accent1_10),
+    primary95 = ColorResourceHelper.getColor(context, R.color.system_accent1_50),
+    primary90 = ColorResourceHelper.getColor(context, R.color.system_accent1_100),
+    primary80 = ColorResourceHelper.getColor(context, R.color.system_accent1_200),
+    primary70 = ColorResourceHelper.getColor(context, R.color.system_accent1_300),
+    primary60 = ColorResourceHelper.getColor(context, R.color.system_accent1_400),
+    primary50 = ColorResourceHelper.getColor(context, R.color.system_accent1_500),
+    primary40 = ColorResourceHelper.getColor(context, R.color.system_accent1_600),
+    primary30 = ColorResourceHelper.getColor(context, R.color.system_accent1_700),
+    primary20 = ColorResourceHelper.getColor(context, R.color.system_accent1_800),
+    primary10 = ColorResourceHelper.getColor(context, R.color.system_accent1_900),
+    primary0 = ColorResourceHelper.getColor(context, R.color.system_accent1_1000),
+
+    // The secondary tonal range from the generated dynamic color palette.
+    secondary100 = ColorResourceHelper.getColor(context, R.color.system_accent2_0),
+    secondary99 = ColorResourceHelper.getColor(context, R.color.system_accent2_10),
+    secondary95 = ColorResourceHelper.getColor(context, R.color.system_accent2_50),
+    secondary90 = ColorResourceHelper.getColor(context, R.color.system_accent2_100),
+    secondary80 = ColorResourceHelper.getColor(context, R.color.system_accent2_200),
+    secondary70 = ColorResourceHelper.getColor(context, R.color.system_accent2_300),
+    secondary60 = ColorResourceHelper.getColor(context, R.color.system_accent2_400),
+    secondary50 = ColorResourceHelper.getColor(context, R.color.system_accent2_500),
+    secondary40 = ColorResourceHelper.getColor(context, R.color.system_accent2_600),
+    secondary30 = ColorResourceHelper.getColor(context, R.color.system_accent2_700),
+    secondary20 = ColorResourceHelper.getColor(context, R.color.system_accent2_800),
+    secondary10 = ColorResourceHelper.getColor(context, R.color.system_accent2_900),
+    secondary0 = ColorResourceHelper.getColor(context, R.color.system_accent2_1000),
+
+    // The tertiary tonal range from the generated dynamic color palette.
+    tertiary100 = ColorResourceHelper.getColor(context, R.color.system_accent3_0),
+    tertiary99 = ColorResourceHelper.getColor(context, R.color.system_accent3_10),
+    tertiary95 = ColorResourceHelper.getColor(context, R.color.system_accent3_50),
+    tertiary90 = ColorResourceHelper.getColor(context, R.color.system_accent3_100),
+    tertiary80 = ColorResourceHelper.getColor(context, R.color.system_accent3_200),
+    tertiary70 = ColorResourceHelper.getColor(context, R.color.system_accent3_300),
+    tertiary60 = ColorResourceHelper.getColor(context, R.color.system_accent3_400),
+    tertiary50 = ColorResourceHelper.getColor(context, R.color.system_accent3_500),
+    tertiary40 = ColorResourceHelper.getColor(context, R.color.system_accent3_600),
+    tertiary30 = ColorResourceHelper.getColor(context, R.color.system_accent3_700),
+    tertiary20 = ColorResourceHelper.getColor(context, R.color.system_accent3_800),
+    tertiary10 = ColorResourceHelper.getColor(context, R.color.system_accent3_900),
+    tertiary0 = ColorResourceHelper.getColor(context, R.color.system_accent3_1000),
+)
+
+private object ColorResourceHelper {
+    @DoNotInline
+    fun getColor(context: Context, @ColorRes id: Int): Color {
+        return Color(context.resources.getColor(id, context.theme))
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
new file mode 100644
index 0000000..07f09ba
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.em
+import androidx.compose.ui.unit.sp
+
+private class SettingsTypography {
+    private val brand = FontFamily.Default
+    private val plain = FontFamily.Default
+
+    val typography = Typography(
+        displayLarge = TextStyle(
+            fontFamily = brand,
+            fontWeight = FontWeight.Normal,
+            fontSize = 57.sp,
+            lineHeight = 64.sp,
+            letterSpacing = (-0.2).sp
+        ),
+        displayMedium = TextStyle(
+            fontFamily = brand,
+            fontWeight = FontWeight.Normal,
+            fontSize = 45.sp,
+            lineHeight = 52.sp,
+            letterSpacing = 0.0.sp
+        ),
+        displaySmall = TextStyle(
+            fontFamily = brand,
+            fontWeight = FontWeight.Normal,
+            fontSize = 36.sp,
+            lineHeight = 44.sp,
+            letterSpacing = 0.0.sp
+        ),
+        headlineLarge = TextStyle(
+            fontFamily = brand,
+            fontWeight = FontWeight.Normal,
+            fontSize = 32.sp,
+            lineHeight = 40.sp,
+            letterSpacing = 0.0.sp
+        ),
+        headlineMedium = TextStyle(
+            fontFamily = brand,
+            fontWeight = FontWeight.Normal,
+            fontSize = 28.sp,
+            lineHeight = 36.sp,
+            letterSpacing = 0.0.sp
+        ),
+        headlineSmall = TextStyle(
+            fontFamily = brand,
+            fontWeight = FontWeight.Normal,
+            fontSize = 24.sp,
+            lineHeight = 32.sp,
+            letterSpacing = 0.0.sp
+        ),
+        titleLarge = TextStyle(
+            fontFamily = brand,
+            fontWeight = FontWeight.Normal,
+            fontSize = 22.sp,
+            lineHeight = 28.sp,
+            letterSpacing = 0.02.em,
+        ),
+        titleMedium = TextStyle(
+            fontFamily = brand,
+            fontWeight = FontWeight.Normal,
+            fontSize = 20.sp,
+            lineHeight = 24.sp,
+            letterSpacing = 0.02.em,
+        ),
+        titleSmall = TextStyle(
+            fontFamily = brand,
+            fontWeight = FontWeight.Normal,
+            fontSize = 18.sp,
+            lineHeight = 20.sp,
+            letterSpacing = 0.02.em,
+        ),
+        bodyLarge = TextStyle(
+            fontFamily = plain,
+            fontWeight = FontWeight.Normal,
+            fontSize = 16.sp,
+            lineHeight = 24.sp,
+            letterSpacing = 0.01.em,
+        ),
+        bodyMedium = TextStyle(
+            fontFamily = plain,
+            fontWeight = FontWeight.Normal,
+            fontSize = 14.sp,
+            lineHeight = 20.sp,
+            letterSpacing = 0.01.em,
+        ),
+        bodySmall = TextStyle(
+            fontFamily = plain,
+            fontWeight = FontWeight.Normal,
+            fontSize = 12.sp,
+            lineHeight = 16.sp,
+            letterSpacing = 0.01.em,
+        ),
+        labelLarge = TextStyle(
+            fontFamily = plain,
+            fontWeight = FontWeight.Medium,
+            fontSize = 16.sp,
+            lineHeight = 24.sp,
+            letterSpacing = 0.01.em,
+        ),
+        labelMedium = TextStyle(
+            fontFamily = plain,
+            fontWeight = FontWeight.Medium,
+            fontSize = 14.sp,
+            lineHeight = 20.sp,
+            letterSpacing = 0.01.em,
+        ),
+        labelSmall = TextStyle(
+            fontFamily = plain,
+            fontWeight = FontWeight.Medium,
+            fontSize = 12.sp,
+            lineHeight = 16.sp,
+            letterSpacing = 0.01.em,
+        ),
+    )
+}
+
+@Composable
+internal fun rememberSettingsTypography(): Typography {
+    return remember { SettingsTypography().typography }
+}
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/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
index d415e9b..9a34dbf 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
@@ -25,8 +25,6 @@
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.material3.Divider
-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
@@ -35,10 +33,11 @@
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
-import com.android.settingslib.spa.framework.toState
-import com.android.settingslib.spa.theme.SettingsDimension
-import com.android.settingslib.spa.theme.SettingsOpacity
-import com.android.settingslib.spa.theme.SettingsTheme
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsOpacity
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.ui.SettingsTitle
 
 @Composable
 internal fun BaseLayout(
@@ -94,11 +93,7 @@
 @Composable
 private fun Titles(title: String, subTitle: @Composable () -> Unit, modifier: Modifier) {
     Column(modifier) {
-        Text(
-            text = title,
-            color = MaterialTheme.colorScheme.onSurface,
-            style = MaterialTheme.typography.titleMedium,
-        )
+        SettingsTitle(title)
         subTitle()
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
index 563a47a..4b2c8e4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
@@ -19,16 +19,15 @@
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.BatteryChargingFull
 import androidx.compose.material3.Icon
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.Dp
-import com.android.settingslib.spa.framework.toState
-import com.android.settingslib.spa.theme.SettingsDimension
-import com.android.settingslib.spa.theme.SettingsTheme
+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.ui.SettingsBody
 
 @Composable
 internal fun BasePreference(
@@ -44,15 +43,7 @@
 ) {
     BaseLayout(
         title = title,
-        subTitle = {
-            if (summary.value.isNotEmpty()) {
-                Text(
-                    text = summary.value,
-                    color = MaterialTheme.colorScheme.onSurfaceVariant,
-                    style = MaterialTheme.typography.bodyMedium,
-                )
-            }
-        },
+        subTitle = { SettingsBody(summary) },
         modifier = modifier,
         icon = icon,
         enabled = enabled,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
index ee20280..0e6f53a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
@@ -19,8 +19,9 @@
 import androidx.compose.foundation.clickable
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
-import com.android.settingslib.spa.framework.stateOf
+import com.android.settingslib.spa.framework.compose.stateOf
 
 /**
  * The widget model for [Preference] widget.
@@ -38,6 +39,14 @@
         get() = stateOf("")
 
     /**
+     * The icon of this [Preference].
+     *
+     * Default is `null` which means no icon.
+     */
+    val icon: (@Composable () -> Unit)?
+        get() = null
+
+    /**
      * Indicates whether this [Preference] is enabled.
      *
      * Disabled [Preference] will be displayed in disabled style.
@@ -61,13 +70,16 @@
  */
 @Composable
 fun Preference(model: PreferenceModel) {
-    val modifier = model.onClick?.let { onClick ->
-        Modifier.clickable(enabled = model.enabled.value) { onClick() }
-    } ?: Modifier
+    val modifier = remember(model.enabled.value, model.onClick) {
+      model.onClick?.let { onClick ->
+        Modifier.clickable(enabled = model.enabled.value, onClick = onClick)
+      } ?: Modifier
+    }
     BasePreference(
         title = model.title,
         summary = model.summary,
         modifier = modifier,
+        icon = model.icon,
         enabled = model.enabled,
     )
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
new file mode 100644
index 0000000..b6d6936
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.selection.toggleable
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import com.android.settingslib.spa.framework.compose.stateOf
+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.ui.SettingsSwitch
+
+/**
+ * The widget model for [SwitchPreference] widget.
+ */
+interface SwitchPreferenceModel {
+    /**
+     * The title of this [SwitchPreference].
+     */
+    val title: String
+
+    /**
+     * The summary of this [SwitchPreference].
+     */
+    val summary: State<String>
+        get() = stateOf("")
+
+    /**
+     * Indicates whether this [SwitchPreference] is checked.
+     *
+     * This can be `null` during the data loading before the data is available.
+     */
+    val checked: State<Boolean?>
+
+    /**
+     * Indicates whether this [SwitchPreference] is changeable.
+     *
+     * Not changeable [SwitchPreference] will be displayed in disabled style.
+     */
+    val changeable: State<Boolean>
+        get() = stateOf(true)
+
+    /**
+     * The switch change handler of this [SwitchPreference].
+     *
+     * If `null`, this [SwitchPreference] is not [toggleable].
+     */
+    val onCheckedChange: ((newChecked: Boolean) -> Unit)?
+}
+
+/**
+ * SwitchPreference widget.
+ *
+ * Data is provided through [SwitchPreferenceModel].
+ */
+@Composable
+fun SwitchPreference(model: SwitchPreferenceModel) {
+    InternalSwitchPreference(
+        title = model.title,
+        summary = model.summary,
+        checked = model.checked,
+        changeable = model.changeable,
+        onCheckedChange = model.onCheckedChange,
+    )
+}
+
+@Composable
+internal fun InternalSwitchPreference(
+    title: String,
+    summary: State<String> = "".toState(),
+    checked: State<Boolean?>,
+    changeable: State<Boolean> = true.toState(),
+    paddingStart: Dp = SettingsDimension.itemPaddingStart,
+    paddingEnd: Dp = SettingsDimension.itemPaddingEnd,
+    paddingVertical: Dp = SettingsDimension.itemPaddingVertical,
+    onCheckedChange: ((newChecked: Boolean) -> Unit)?,
+) {
+    val checkedValue = checked.value
+    val indication = LocalIndication.current
+    val modifier = remember(checkedValue, changeable.value) {
+        if (checkedValue != null && onCheckedChange != null) {
+            Modifier.toggleable(
+                value = checkedValue,
+                interactionSource = MutableInteractionSource(),
+                indication = indication,
+                enabled = changeable.value,
+                role = Role.Switch,
+                onValueChange = onCheckedChange,
+            )
+        } else Modifier
+    }
+    BasePreference(
+        title = title,
+        summary = summary,
+        modifier = modifier,
+        enabled = changeable,
+        paddingStart = paddingStart,
+        paddingEnd = paddingEnd,
+        paddingVertical = paddingVertical,
+    ) {
+        SettingsSwitch(checked = checked, changeable = changeable)
+    }
+}
+
+@Preview
+@Composable
+private fun SwitchPreferencePreview() {
+    SettingsTheme {
+        Column {
+            InternalSwitchPreference(
+                title = "Use Dark theme",
+                checked = true.toState(),
+                onCheckedChange = {},
+            )
+            InternalSwitchPreference(
+                title = "Use Dark theme",
+                summary = "Summary".toState(),
+                checked = false.toState(),
+                onCheckedChange = {},
+            )
+        }
+    }
+}
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
new file mode 100644
index 0000000..e0e9b95
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.padding
+import androidx.compose.material3.TabRow
+import androidx.compose.runtime.Composable
+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) {
+    check(titles.isNotEmpty())
+    if (titles.size == 1) {
+        content(0)
+        return
+    }
+
+    Column {
+        val coroutineScope = rememberCoroutineScope()
+        val pagerState = rememberPagerState()
+
+        TabRow(
+            selectedTabIndex = pagerState.currentPage,
+            modifier = Modifier.padding(horizontal = SettingsDimension.itemPaddingEnd),
+            containerColor = Color.Transparent,
+            indicator = {},
+            divider = {},
+        ) {
+            titles.forEachIndexed { page, title ->
+                SettingsTab(
+                    title = title,
+                    selected = pagerState.currentPage == page,
+                    currentPageOffset = pagerState.currentPageOffset.absoluteValue,
+                    onClick = {
+                        coroutineScope.launch {
+                            pagerState.animateScrollToPage(page)
+                        }
+                    },
+                )
+            }
+        }
+
+        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
new file mode 100644
index 0000000..30a4349
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Tab
+import androidx.compose.material3.Text
+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
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+@Composable
+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,
+        modifier = Modifier
+            .height(48.dp)
+            .padding(horizontal = 4.dp, vertical = 6.dp)
+            .clip(SettingsShape.CornerMedium)
+            .background(
+                color = lerp(
+                    start = SettingsTheme.colorScheme.primaryContainer,
+                    stop = SettingsTheme.colorScheme.surface,
+                    fraction = colorFraction,
+                ),
+            ),
+    ) {
+        Text(
+            text = title,
+            style = MaterialTheme.typography.labelLarge,
+            color = lerp(
+                start = SettingsTheme.colorScheme.onPrimaryContainer,
+                stop = SettingsTheme.colorScheme.secondaryText,
+                fraction = colorFraction,
+            ),
+        )
+    }
+}
+
+@Preview
+@Composable
+fun SettingsTabPreview() {
+    SettingsTheme {
+        Column {
+            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/Footer.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt
new file mode 100644
index 0000000..296cf3b
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.ui
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Info
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+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
+
+@Composable
+fun Footer(footerText: String) {
+    if (footerText.isEmpty()) return
+    Column(Modifier.padding(SettingsDimension.itemPadding)) {
+        Icon(
+            imageVector = Icons.Outlined.Info,
+            contentDescription = null,
+            modifier = Modifier.size(SettingsDimension.itemIconSize),
+            tint = MaterialTheme.colorScheme.onSurfaceVariant,
+        )
+        Spacer(modifier = Modifier.height(SettingsDimension.itemPaddingVertical))
+        SettingsBody(footerText)
+    }
+}
+
+@Preview
+@Composable
+private fun FooterPreview() {
+    SettingsTheme {
+        Footer("Footer text always at the end of page.")
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt
new file mode 100644
index 0000000..4f28e37
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.ui
+
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+
+@Composable
+fun SettingsIcon(imageVector: ImageVector) {
+    Icon(
+        imageVector = imageVector,
+        contentDescription = null,
+        modifier = Modifier.size(SettingsDimension.itemIconSize),
+        tint = MaterialTheme.colorScheme.onSurface,
+    )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt
new file mode 100644
index 0000000..0454ac3
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.ui
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AccessAlarm
+import androidx.compose.material.icons.outlined.MusicNote
+import androidx.compose.material.icons.outlined.MusicOff
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Slider
+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.graphics.vector.ImageVector
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.BaseLayout
+import kotlin.math.roundToInt
+
+/**
+ * The widget model for [SettingsSlider] widget.
+ */
+interface SettingsSliderModel {
+    /**
+     * The title of this [SettingsSlider].
+     */
+    val title: String
+
+    /**
+     * The initial position of the [SettingsSlider].
+     */
+    val initValue: Int
+
+    /**
+     * The value range for this [SettingsSlider].
+     */
+    val valueRange: IntRange
+        get() = 0..100
+
+    /**
+     * The lambda to be invoked during the value change by dragging or a click. This callback is
+     * used to get the real time value of the [SettingsSlider].
+     */
+    val onValueChange: ((value: Int) -> Unit)?
+        get() = null
+
+    /**
+     * The lambda to be invoked when value change has ended. This callback is used to get when the
+     * user has completed selecting a new value by ending a drag or a click.
+     */
+    val onValueChangeFinished: (() -> Unit)?
+        get() = null
+
+    /**
+     * The icon image for [SettingsSlider]. If not specified, the slider hides the icon by default.
+     */
+    val icon: ImageVector?
+        get() = null
+
+    /**
+     * Indicates whether to show step marks. If show step marks, when user finish sliding,
+     * the slider will automatically jump to the nearest step mark. Otherwise, the slider hides
+     * the step marks by default.
+     *
+     * The step is fixed to 1.
+     */
+    val showSteps: Boolean
+        get() = false
+}
+
+/**
+ * Settings slider widget.
+ *
+ * Data is provided through [SettingsSliderModel].
+ */
+@Composable
+fun SettingsSlider(model: SettingsSliderModel) {
+    SettingsSlider(
+        title = model.title,
+        initValue = model.initValue,
+        valueRange = model.valueRange,
+        onValueChange = model.onValueChange,
+        onValueChangeFinished = model.onValueChangeFinished,
+        icon = model.icon,
+        showSteps = model.showSteps,
+    )
+}
+
+@Composable
+internal fun SettingsSlider(
+    title: String,
+    initValue: Int,
+    valueRange: IntRange = 0..100,
+    onValueChange: ((value: Int) -> Unit)? = null,
+    onValueChangeFinished: (() -> Unit)? = null,
+    icon: ImageVector? = null,
+    showSteps: Boolean = false,
+    modifier: Modifier = Modifier,
+) {
+    var sliderPosition by rememberSaveable { mutableStateOf(initValue.toFloat()) }
+    BaseLayout(
+        title = title,
+        subTitle = {
+            Slider(
+                value = sliderPosition,
+                onValueChange = {
+                    sliderPosition = it
+                    onValueChange?.invoke(sliderPosition.roundToInt())
+                },
+                modifier = modifier,
+                valueRange = valueRange.first.toFloat()..valueRange.last.toFloat(),
+                steps = if (showSteps) (valueRange.count() - 2) else 0,
+                onValueChangeFinished = onValueChangeFinished,
+            )
+        },
+        icon = if (icon != null) ({
+            Icon(imageVector = icon, contentDescription = null)
+        }) else null,
+    )
+}
+
+@Preview
+@Composable
+private fun SettingsSliderPreview() {
+    SettingsTheme {
+        val initValue = 30
+        var sliderPosition by rememberSaveable { mutableStateOf(initValue) }
+        SettingsSlider(
+            title = "Alarm Volume",
+            initValue = 30,
+            onValueChange = { sliderPosition = it },
+            onValueChangeFinished = {
+                println("onValueChangeFinished: the value is $sliderPosition")
+            },
+            icon = Icons.Outlined.AccessAlarm,
+        )
+    }
+}
+
+@Preview
+@Composable
+private fun SettingsSliderIconChangePreview() {
+    SettingsTheme {
+        var icon by remember { mutableStateOf(Icons.Outlined.MusicNote) }
+        SettingsSlider(
+            title = "Media Volume",
+            initValue = 40,
+            onValueChange = { it: Int ->
+                icon = if (it > 0) Icons.Outlined.MusicNote else Icons.Outlined.MusicOff
+            },
+            icon = icon,
+        )
+    }
+}
+
+@Preview
+@Composable
+private fun SettingsSliderStepsPreview() {
+    SettingsTheme {
+        SettingsSlider(
+            title = "Display Text",
+            initValue = 2,
+            valueRange = 1..5,
+            showSteps = true,
+        )
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
new file mode 100644
index 0000000..45d5f6b
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.ui
+
+import androidx.compose.material3.Checkbox
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SettingsSwitch(
+    checked: State<Boolean?>,
+    changeable: State<Boolean>,
+    onCheckedChange: ((newChecked: Boolean) -> Unit)? = null,
+) {
+    // TODO: Replace Checkbox with Switch when the androidx.compose.material3_material3 library is
+    //       updated to date.
+    val checkedValue = checked.value
+    if (checkedValue != null) {
+        Checkbox(
+            checked = checkedValue,
+            onCheckedChange = onCheckedChange,
+            enabled = changeable.value,
+        )
+    } else {
+        Checkbox(
+            checked = false,
+            onCheckedChange = null,
+            enabled = false,
+        )
+    }
+}
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
new file mode 100644
index 0000000..59b413c
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.ui
+
+import androidx.compose.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>) {
+    SettingsTitle(title.value)
+}
+
+@Composable
+fun SettingsTitle(title: String) {
+    Text(
+        text = title,
+        color = MaterialTheme.colorScheme.onSurface,
+        style = MaterialTheme.typography.titleMedium,
+    )
+}
+
+@Composable
+fun SettingsBody(body: State<String>) {
+    SettingsBody(body.value)
+}
+
+@Composable
+fun SettingsBody(body: String) {
+    if (body.isNotEmpty()) {
+        Text(
+            text = body,
+            color = MaterialTheme.colorScheme.onSurfaceVariant,
+            style = MaterialTheme.typography.bodyMedium,
+        )
+    }
+}
+
+@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/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
index 707017e..be5a5ec 100644
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ b/packages/SettingsLib/Spa/tests/build.gradle
@@ -24,7 +24,7 @@
     compileSdk 33
 
     defaultConfig {
-        minSdk minSdk_version
+        minSdk spa_min_sdk
         targetSdk 33
 
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -50,7 +50,7 @@
         compose true
     }
     composeOptions {
-        kotlinCompilerExtensionVersion compose_version
+        kotlinCompilerExtensionVersion jetpack_compose_version
     }
     packagingOptions {
         resources {
@@ -62,6 +62,6 @@
 dependencies {
     androidTestImplementation(project(":spa"))
     androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3'
-    androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version")
-    androidTestDebugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
+    androidTestImplementation("androidx.compose.ui:ui-test-junit4:$jetpack_compose_version")
+    androidTestDebugImplementation "androidx.compose.ui:ui-test-manifest:$jetpack_compose_version"
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt
index 4097946..a92f871 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt
@@ -26,7 +26,7 @@
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.toState
+import com.android.settingslib.spa.framework.compose.toState
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SwitchPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SwitchPreferenceTest.kt
new file mode 100644
index 0000000..d6c8fbc
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SwitchPreferenceTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsOff
+import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.isToggleable
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SwitchPreferenceTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun title_displayed() {
+        composeTestRule.setContent {
+            TestSwitchPreference(changeable = true)
+        }
+
+        composeTestRule.onNodeWithText("SwitchPreference").assertIsDisplayed()
+    }
+
+    @Test
+    fun toggleable_initialStateIsCorrect() {
+        composeTestRule.setContent {
+            TestSwitchPreference(changeable = true)
+        }
+
+        composeTestRule.onNode(isToggleable()).assertIsOff()
+    }
+
+    @Test
+    fun click_changeable_withEffect() {
+        composeTestRule.setContent {
+            TestSwitchPreference(changeable = true)
+        }
+
+        composeTestRule.onNodeWithText("SwitchPreference").performClick()
+        composeTestRule.onNode(isToggleable()).assertIsOn()
+    }
+
+    @Test
+    fun click_notChangeable_noEffect() {
+        composeTestRule.setContent {
+            TestSwitchPreference(changeable = false)
+        }
+
+        composeTestRule.onNodeWithText("SwitchPreference").performClick()
+        composeTestRule.onNode(isToggleable()).assertIsOff()
+    }
+}
+
+@Composable
+private fun TestSwitchPreference(changeable: Boolean) {
+    val checked = rememberSaveable { mutableStateOf(false) }
+    SwitchPreference(remember {
+        object : SwitchPreferenceModel {
+            override val title = "SwitchPreference"
+            override val checked = checked
+            override val changeable = stateOf(changeable)
+            override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+        }
+    })
+}
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
new file mode 100644
index 0000000..0c84eac
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt
@@ -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.settingslib.spa.widget.scaffold
+
+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
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.ui.SettingsTitle
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsPagerKtTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun twoPage_initialState() {
+        composeTestRule.setContent {
+            TestTwoPage()
+        }
+
+        composeTestRule.onNodeWithText("Personal").assertIsSelected()
+        composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
+        composeTestRule.onNodeWithText("Work").assertIsNotSelected()
+        composeTestRule.onNodeWithText("Page 1").assertIsNotDisplayed()
+    }
+
+    @Test
+    fun twoPage_afterSwitch() {
+        composeTestRule.setContent {
+            TestTwoPage()
+        }
+
+        composeTestRule.onNodeWithText("Work").performClick()
+
+        composeTestRule.onNodeWithText("Personal").assertIsNotSelected()
+        composeTestRule.onNodeWithText("Page 0").assertIsNotDisplayed()
+        composeTestRule.onNodeWithText("Work").assertIsSelected()
+        composeTestRule.onNodeWithText("Page 1").assertIsDisplayed()
+    }
+
+    @Test
+    fun onePage_initialState() {
+        composeTestRule.setContent {
+            SettingsPager(listOf("Personal")) {
+                SettingsTitle(title = "Page $it")
+            }
+        }
+
+        composeTestRule.onNodeWithText("Personal").assertDoesNotExist()
+        composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
+        composeTestRule.onNodeWithText("Page 1").assertDoesNotExist()
+    }
+}
+
+@Composable
+private fun TestTwoPage() {
+    SettingsPager(listOf("Personal", "Work")) {
+        SettingsTitle(title = "Page $it")
+    }
+}
diff --git a/packages/SettingsLib/Spa/codelab/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
similarity index 82%
copy from packages/SettingsLib/Spa/codelab/Android.bp
copy to packages/SettingsLib/SpaPrivileged/Android.bp
index 8fbbf9a..a6469b5 100644
--- a/packages/SettingsLib/Spa/codelab/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -18,16 +18,18 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-android_app {
-    name: "SpaLibCodelab",
+android_library {
+    name: "SpaPrivilegedLib",
 
     srcs: ["src/**/*.kt"],
 
     static_libs: [
         "SpaLib",
+        "SettingsLib",
         "androidx.compose.runtime_runtime",
     ],
-    kotlincflags: ["-Xjvm-default=all"],
-    platform_apis: true,
-    min_sdk_version: "31",
+    kotlincflags: [
+        "-Xjvm-default=all",
+        "-Xopt-in=kotlin.RequiresOptIn",
+    ],
 }
diff --git a/packages/SettingsLib/Spa/codelab/res/values/strings.xml b/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
similarity index 79%
rename from packages/SettingsLib/Spa/codelab/res/values/strings.xml
rename to packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
index 6fdbba0..2efa107 100644
--- a/packages/SettingsLib/Spa/codelab/res/values/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   Copyright (C) 2022 The Android Open Source Project
 
@@ -12,8 +13,6 @@
   WITHOUT 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>
-    <!-- Codelab App name. [DO NOT TRANSLATE] -->
-    <string name="app_name" translatable="false">SpaLib Codelab</string>
-</resources>
+-->
+
+<manifest package="com.android.settingslib.spaprivileged" />
diff --git a/packages/SettingsLib/SpaPrivileged/OWNERS b/packages/SettingsLib/SpaPrivileged/OWNERS
new file mode 100644
index 0000000..9256ca5
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/packages/SettingsLib/Spa/OWNERS
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/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
new file mode 100644
index 0000000..c2d85a5
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.app
+
+import android.app.AppOpsManager
+import android.app.AppOpsManager.MODE_ALLOWED
+import android.app.AppOpsManager.MODE_ERRORED
+import android.app.AppOpsManager.Mode
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+
+class AppOpsController(
+    context: Context,
+    private val app: ApplicationInfo,
+    private val op: Int,
+) {
+    private val appOpsManager = checkNotNull(context.getSystemService(AppOpsManager::class.java))
+
+    val isAllowed: LiveData<Boolean>
+        get() = _isAllowed
+
+    fun setAllowed(allowed: Boolean) {
+        val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED
+        appOpsManager.setMode(op, app.uid, app.packageName, mode)
+        _isAllowed.postValue(allowed)
+    }
+
+    @Mode
+    private fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName)
+
+    private val _isAllowed = object : MutableLiveData<Boolean>() {
+        override fun onActive() {
+            postValue(getMode() == MODE_ALLOWED)
+        }
+
+        override fun onInactive() {
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRecord.kt
similarity index 79%
copy from packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
copy to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRecord.kt
index 7c9df10..2978688 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRecord.kt
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.common.data.model
+package com.android.settingslib.spaprivileged.model.app
 
-/** Models a two-dimensional position */
-data class Position(
-    val x: Int,
-    val y: Int,
-)
+import android.content.pm.ApplicationInfo
+
+interface AppRecord {
+    val app: ApplicationInfo
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
new file mode 100644
index 0000000..34f12af
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.graphics.drawable.Drawable
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.produceState
+import com.android.settingslib.Utils
+import com.android.settingslib.spa.framework.compose.rememberContext
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+@Composable
+fun rememberAppRepository(): AppRepository = rememberContext(::AppRepositoryImpl)
+
+interface AppRepository {
+    fun loadLabel(app: ApplicationInfo): String
+
+    @Composable
+    fun produceLabel(app: ApplicationInfo): State<String>
+
+    @Composable
+    fun produceIcon(app: ApplicationInfo): State<Drawable?>
+}
+
+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) {
+            value = app.loadLabel(packageManager).toString()
+        }
+    }
+
+    @Composable
+    override fun produceIcon(app: ApplicationInfo) =
+        produceState<Drawable?>(initialValue = null, app) {
+            withContext(Dispatchers.Default) {
+                value = Utils.getBadgedIcon(context, app)
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
similarity index 70%
copy from packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
copy to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
index 7c9df10..6e1afd9 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
@@ -14,10 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.common.data.model
+package com.android.settingslib.spaprivileged.model.app
 
-/** Models a two-dimensional position */
-data class Position(
-    val x: Int,
-    val y: Int,
-)
+import android.content.pm.ApplicationInfo
+import android.os.UserHandle
+
+val ApplicationInfo.userId: Int
+    get() = UserHandle.getUserId(uid)
+
+fun ApplicationInfo.toRoute() = "$packageName/$userId"
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/model/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
new file mode 100644
index 0000000..0cc497a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+
+object PackageManagers {
+    fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo =
+        PackageManager.getPackageInfoAsUserCached(packageName, 0, userId)
+
+    fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo =
+        PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId)
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
new file mode 100644
index 0000000..fab3ae8
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.enterprise
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER
+import android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER
+import android.content.Context
+import com.android.settingslib.spaprivileged.R
+
+class EnterpriseRepository(private val context: Context) {
+    private val resources by lazy {
+        checkNotNull(context.getSystemService(DevicePolicyManager::class.java)).resources
+    }
+
+    fun getEnterpriseString(updatableStringId: String, resId: Int): String =
+        resources.getString(updatableStringId) { context.getString(resId) }
+
+    fun getProfileTitle(isManagedProfile: Boolean): String = if (isManagedProfile) {
+        getEnterpriseString(WORK_CATEGORY_HEADER, R.string.category_work)
+    } else {
+        getEnterpriseString(PERSONAL_CATEGORY_HEADER, R.string.category_personal)
+    }
+}
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
new file mode 100644
index 0000000..99deb70
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.content.pm.ApplicationInfo
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+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.model.app.PackageManagers
+import com.android.settingslib.spaprivileged.model.app.rememberAppRepository
+
+@Composable
+fun AppInfo(packageName: String, userId: Int) {
+    Column(
+        modifier = Modifier
+            .fillMaxWidth()
+            .padding(
+                horizontal = SettingsDimension.itemPaddingStart,
+                vertical = SettingsDimension.itemPaddingVertical,
+            ),
+        horizontalAlignment = Alignment.CenterHorizontally,
+    ) {
+        val packageInfo = remember { PackageManagers.getPackageInfoAsUser(packageName, userId) }
+        Box(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) {
+            AppIcon(app = packageInfo.applicationInfo, size = SettingsDimension.appIconInfoSize)
+        }
+        AppLabel(packageInfo.applicationInfo)
+        AppVersion(packageInfo.versionName)
+    }
+}
+
+@Composable
+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)
+    )
+}
+
+@Composable
+fun AppLabel(app: ApplicationInfo) {
+    val appRepository = rememberAppRepository()
+    SettingsTitle(appRepository.produceLabel(app))
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsTheme.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
similarity index 62%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsTheme.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
index fce9f2b..9b45318 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsTheme.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
@@ -14,21 +14,25 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.theme
+package com.android.settingslib.spaprivileged.template.app
 
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Footer
 
-/**
- * The Material 3 Theme for Settings.
- */
 @Composable
-fun SettingsTheme(content: @Composable () -> Unit) {
-    val isDarkTheme = isSystemInDarkTheme()
-    val colorScheme = materialColorScheme(isDarkTheme)
+fun AppInfoPage(
+    title: String,
+    packageName: String,
+    userId: Int,
+    footerText: String,
+    content: @Composable () -> Unit,
+) {
+    RegularScaffold(title = title) {
+        AppInfo(packageName, userId)
 
-    MaterialTheme(colorScheme = colorScheme) {
         content()
+
+        Footer(footerText)
     }
 }
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
new file mode 100644
index 0000000..298ecd5
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.LaunchedEffect
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+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.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.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"
+
+internal class TogglePermissionAppInfoPageProvider(
+    private val factory: TogglePermissionAppListModelFactory,
+) : SettingsPageProvider {
+    override val name = NAME
+
+    override val arguments = listOf(
+        navArgument(PERMISSION) { type = NavType.StringType },
+        navArgument(PACKAGE_NAME) { type = NavType.StringType },
+        navArgument(USER_ID) { type = NavType.IntType },
+    )
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        checkNotNull(arguments)
+        val permission = checkNotNull(arguments.getString(PERMISSION))
+        val packageName = checkNotNull(arguments.getString(PACKAGE_NAME))
+        val userId = arguments.getInt(USER_ID)
+        val listModel = rememberContext { context -> factory.createModel(permission, context) }
+        TogglePermissionAppInfoPage(listModel, packageName, userId)
+    }
+
+    companion object {
+        @Composable
+        internal fun navigator(permissionType: String, app: ApplicationInfo) =
+            navigator(route = "$NAME/$permissionType/${app.toRoute()}")
+    }
+}
+
+@Composable
+private fun TogglePermissionAppInfoPage(
+    listModel: TogglePermissionAppListModel<out AppRecord>,
+    packageName: String,
+    userId: Int,
+) {
+    AppInfoPage(
+        title = stringResource(listModel.pageTitleResId),
+        packageName = packageName,
+        userId = userId,
+        footerText = stringResource(listModel.footerResId),
+    ) {
+        val model = createSwitchModel(listModel, packageName, userId)
+        LaunchedEffect(model, Dispatchers.Default) {
+            model.initState()
+        }
+        SwitchPreference(model)
+    }
+}
+
+@Composable
+private fun <T : AppRecord> createSwitchModel(
+    listModel: TogglePermissionAppListModel<T>,
+    packageName: String,
+    userId: Int,
+): TogglePermissionSwitchModel<T> {
+    val record = remember {
+        val app = PackageManagers.getApplicationInfoAsUser(packageName, userId)
+        listModel.transformItem(app)
+    }
+    val context = LocalContext.current
+    val isAllowed = listModel.isAllowed(record)
+    return remember {
+        TogglePermissionSwitchModel(context, listModel, record, isAllowed)
+    }
+}
+
+private class TogglePermissionSwitchModel<T : AppRecord>(
+    context: Context,
+    private val listModel: TogglePermissionAppListModel<T>,
+    private val record: T,
+    isAllowed: State<Boolean?>,
+) : SwitchPreferenceModel {
+    override val title: String = context.getString(listModel.switchTitleResId)
+    override val checked = isAllowed
+    override val changeable = mutableStateOf(true)
+
+    fun initState() {
+        changeable.value = listModel.isChangeable(record)
+    }
+
+    override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
+        listModel.setAllowed(record, newChecked)
+    }
+}
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
new file mode 100644
index 0000000..70ff9a4
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+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?>
+
+    fun isChangeable(record: T): Boolean
+    fun setAllowed(record: T, newAllowed: Boolean)
+}
+
+interface TogglePermissionAppListModelFactory {
+    fun createModel(
+        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/common/WorkProfilePager.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/WorkProfilePager.kt
new file mode 100644
index 0000000..aa5ccf1
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/WorkProfilePager.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.common
+
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import com.android.settingslib.spa.widget.scaffold.SettingsPager
+import com.android.settingslib.spaprivileged.model.enterprise.EnterpriseRepository
+
+@Composable
+fun WorkProfilePager(content: @Composable (userInfo: UserInfo) -> Unit) {
+    val context = LocalContext.current
+    val profiles = remember {
+        val userManager = checkNotNull(context.getSystemService(UserManager::class.java))
+        userManager.getProfiles(UserHandle.myUserId())
+    }
+    val titles = remember {
+        val enterpriseRepository = EnterpriseRepository(context)
+        profiles.map {
+            enterpriseRepository.getProfileTitle(isManagedProfile = it.isManagedProfile)
+        }
+    }
+
+    SettingsPager(titles) { page ->
+        content(profiles[page])
+    }
+}
diff --git a/packages/SettingsLib/res/values-af/arrays.xml b/packages/SettingsLib/res/values-af/arrays.xml
index 1de7668..a7e44d3 100644
--- a/packages/SettingsLib/res/values-af/arrays.xml
+++ b/packages/SettingsLib/res/values-af/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Gebruik stelselkeuse (verstek)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-oudio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-oudio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Gebruik stelselkeuse (verstek)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-oudio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-oudio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Gebruik stelselkeuse (verstek)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index fe8030f..0b23dd1 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luggehalte"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Uitsaai-inligting"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Huiskontroles"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Kies \'n profielprent"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Verstekgebruikerikoon"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fisieke sleutelbord"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index d3c034f..2bf9cb2 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"የአየር ሁኔታ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"የአየር ጥራት"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"የCast መረጃ"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"የቤት ውስጥ ቁጥጥሮች"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"የመገለጫ ሥዕል ይምረጡ"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ነባሪ የተጠቃሚ አዶ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 6783654..6c71d9c 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"الطقس"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"جودة الهواء"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"معلومات البث"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"إدارة آلية للمنزل"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"اختيار صورة الملف الشخصي"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"رمز المستخدم التلقائي"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 543c703..299df77 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -121,7 +121,7 @@
     <string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"সম্পৰ্কসূচী আৰু কলৰ ইতিহাস শ্বেয়াৰ কৰাৰ বাবে ব্যৱহাৰ কৰক"</string>
     <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"ইণ্টাৰনেট সংযোগ শ্বেয়াৰ"</string>
     <string name="bluetooth_profile_map" msgid="8907204701162107271">"পাঠ বাৰ্তা"</string>
-    <string name="bluetooth_profile_sap" msgid="8304170950447934386">"ছিম প্ৰৱেশ"</string>
+    <string name="bluetooth_profile_sap" msgid="8304170950447934386">"ছিমৰ এক্সেছ"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"এইচ্ছডি অডি\'অ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"এইচ্ছডি অডিঅ’"</string>
     <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"শ্ৰৱণ যন্ত্ৰ"</string>
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"বতৰ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"বায়ুৰ গুণগত মান"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"কাষ্টৰ তথ্য"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"গৃহ নিয়ন্ত্ৰণ"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"এখন প্ৰ’ফাইল চিত্ৰ বাছনি কৰক"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ডিফ’ল্ট ব্যৱহাৰকাৰীৰ চিহ্ন"</string>
diff --git a/packages/SettingsLib/res/values-az/arrays.xml b/packages/SettingsLib/res/values-az/arrays.xml
index 48974a7..d1f157a 100644
--- a/packages/SettingsLib/res/values-az/arrays.xml
+++ b/packages/SettingsLib/res/values-az/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Sistem Seçimini istifadə edin (Defolt)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Sistem Seçimini istifadə edin (Defolt)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Sistem Seçimini istifadə edin (Defolt)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index f3e5d95..fd79192 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Hava"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Havanın keyfiyyəti"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Yayım məlumatı"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Əsas səhifə kontrolları"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Profil şəkli seçin"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Defolt istifadəçi ikonası"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fiziki klaviatura"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml b/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml
index 337da26..63b08fa 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Koristi izbor sistema (podrazumevano)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Koristi izbor sistema (podrazumevano)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Koristi izbor sistema (podrazumevano)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 6a880aa..f7d9be9 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Vreme"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kvalitet vazduha"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Podaci o prebacivanju"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Upravljanje domom"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"SmartSpace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Odaberite sliku profila"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Podrazumevana ikona korisnika"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fizička tastatura"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index a902426..431bcd5 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Надвор\'е"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Якасць паветра"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Даныя пра трансляцыю"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Кіраванне домам"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Выберыце відарыс профілю"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Стандартны карыстальніцкі значок"</string>
diff --git a/packages/SettingsLib/res/values-bg/arrays.xml b/packages/SettingsLib/res/values-bg/arrays.xml
index 1aad6ca7..849e694 100644
--- a/packages/SettingsLib/res/values-bg/arrays.xml
+++ b/packages/SettingsLib/res/values-bg/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Използване на сист. избор (стандартно)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"Разширено аудиокодиране (AAC)"</item>
+    <item msgid="1049450003868150455">"Аудио: <xliff:g id="APTX">aptX™</xliff:g> от <xliff:g id="QUALCOMM">Qualcomm®</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Аудио: <xliff:g id="APTX_HD">aptX™ HD</xliff:g> от <xliff:g id="QUALCOMM">Qualcomm®</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Използване на сист. избор (стандартно)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"Разширено аудиокодиране (AAC)"</item>
+    <item msgid="8627333814413492563">"Аудио: <xliff:g id="APTX">aptX™</xliff:g> от <xliff:g id="QUALCOMM">Qualcomm®</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Аудио: <xliff:g id="APTX_HD">aptX™ HD</xliff:g> от <xliff:g id="QUALCOMM">Qualcomm®</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Използване на сист. избор (стандартно)"</item>
     <item msgid="8003118270854840095">"44,1 кХц"</item>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index c512366..cbc4322 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Времето"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Качество на въздуха"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Предаване: Инф."</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Контроли за дома"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Изберете снимка на потребителския профил"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Икона за основния потребител"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Физическа клавиатура"</string>
diff --git a/packages/SettingsLib/res/values-bn/arrays.xml b/packages/SettingsLib/res/values-bn/arrays.xml
index 5e6bb95..a3bc4fd 100644
--- a/packages/SettingsLib/res/values-bn/arrays.xml
+++ b/packages/SettingsLib/res/values-bn/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"সিস্টেমের নির্বাচন ব্যবহার করুন (ডিফল্ট)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> অডিও"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> অডিও"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"সিস্টেমের নির্বাচন ব্যবহার করুন (ডিফল্ট)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> অডিও"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> অডিও"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"সিস্টেমের নির্বাচন ব্যবহার করুন (ডিফল্ট)"</item>
     <item msgid="8003118270854840095">"৪৪.১ kHz"</item>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 23eed04..3394eea 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"এয়ার কোয়ালিটি"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"কাস্ট সম্পর্কিত তথ্য"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"হোম কন্ট্রোল"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"একটি প্রোফাইল ছবি বেছে নিন"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ডিফল্ট ব্যবহারকারীর আইকন"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"ফিজিক্যাল কীবোর্ড"</string>
diff --git a/packages/SettingsLib/res/values-bs/arrays.xml b/packages/SettingsLib/res/values-bs/arrays.xml
index 262a35f..926ad84 100644
--- a/packages/SettingsLib/res/values-bs/arrays.xml
+++ b/packages/SettingsLib/res/values-bs/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Korištenje odabira sistema (zadano)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Korištenje odabira sistema (zadano)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Korištenje odabira sistema (zadano)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 78a484f..1547e83 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -265,7 +265,7 @@
     <string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, otklanjanje grešaka, programer"</string>
     <string name="bugreport_in_power" msgid="8664089072534638709">"Prečica za izvještaj o greškama"</string>
     <string name="bugreport_in_power_summary" msgid="1885529649381831775">"Vidite dugme za prijavu grešaka u meniju napajanja"</string>
-    <string name="keep_screen_on" msgid="1187161672348797558">"Ne zaključavaj ekran"</string>
+    <string name="keep_screen_on" msgid="1187161672348797558">"Ne zaključavaj"</string>
     <string name="keep_screen_on_summary" msgid="1510731514101925829">"Ekran neće prelaziti u stanje mirovanja tokom punjenja"</string>
     <string name="bt_hci_snoop_log" msgid="7291287955649081448">"Omogući Bluetooth HCI snoop zapis"</string>
     <string name="bt_hci_snoop_log_summary" msgid="6808538971394092284">"Snimite Bluetooth pakete. (Uključite/isključite Bluetooth nakon što promijenite ovu postavku)"</string>
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Vremenska prognoza"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kvalitet zraka"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Podaci o emitiranju"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kontrole doma"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Odaberite sliku profila"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Zadana ikona korisnika"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fizička tastatura"</string>
diff --git a/packages/SettingsLib/res/values-ca/arrays.xml b/packages/SettingsLib/res/values-ca/arrays.xml
index 8c34a1f..b6f1590 100644
--- a/packages/SettingsLib/res/values-ca/arrays.xml
+++ b/packages/SettingsLib/res/values-ca/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Utilitza la selecció del sistema (predeterminada)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Àudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Àudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Utilitza la selecció del sistema (predeterminada)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Àudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Àudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Utilitza la selecció del sistema (predeterminada)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 606a379..c97a6c1 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Temps"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualitat de l\'aire"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Informació d\'emissió"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Domòtica"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"D\'una ullada"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Tria una foto de perfil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Icona d\'usuari predeterminat"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Teclat físic"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 37c0bb4..dfd6d61 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Počasí"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kvalita vzduchu"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info o odesílání"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Ovládání domácnosti"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Vyberte profilový obrázek"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Výchozí uživatelská ikona"</string>
diff --git a/packages/SettingsLib/res/values-da/arrays.xml b/packages/SettingsLib/res/values-da/arrays.xml
index 155104ae..48a33f6 100644
--- a/packages/SettingsLib/res/values-da/arrays.xml
+++ b/packages/SettingsLib/res/values-da/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Brug systemvalg (standard)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-lyd"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-lyd"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Brug systemvalg (standard)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-lyd"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-lyd"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Brug systemvalg (standard)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index d80e1ec..790819c 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Vejr"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luftkvalitet"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Cast-oplysninger"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Styring af hjem"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Overblik"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Vælg et profilbillede"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ikon for standardbruger"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fysisk tastatur"</string>
diff --git a/packages/SettingsLib/res/values-de/arrays.xml b/packages/SettingsLib/res/values-de/arrays.xml
index 31126a8..ca999db 100644
--- a/packages/SettingsLib/res/values-de/arrays.xml
+++ b/packages/SettingsLib/res/values-de/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Systemauswahl verwenden (Standard)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-Audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-Audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Systemauswahl verwenden (Standard)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-Audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-Audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Systemauswahl verwenden (Standard)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index d4fafe1..bd919f4 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Wetter"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luftqualität"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Streaming-Info"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Smart-Home-Steuerung"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Profilbild auswählen"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Standardmäßiges Nutzersymbol"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Physische Tastatur"</string>
diff --git a/packages/SettingsLib/res/values-el/arrays.xml b/packages/SettingsLib/res/values-el/arrays.xml
index 70000e1..b95f6fc 100644
--- a/packages/SettingsLib/res/values-el/arrays.xml
+++ b/packages/SettingsLib/res/values-el/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Χρήση επιλογής συστήματος (Προεπιλογή)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Ήχος <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Ήχος <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Χρήση επιλογής συστήματος (Προεπιλογή)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Ήχος <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Ήχος <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Χρήση επιλογής συστήματος (Προεπιλογή)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index aa613a5..9326dac 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Καιρός"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Ποιότητα αέρα"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Πληροφορίες ηθοποιών"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Οικιακοί έλεγχοι"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Επιλογή φωτογραφίας προφίλ"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Προεπιλεγμένο εικονίδιο χρήστη"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Φυσικό πληκτρολόγιο"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/arrays.xml b/packages/SettingsLib/res/values-en-rAU/arrays.xml
index fc6f791..327e4e9 100644
--- a/packages/SettingsLib/res/values-en-rAU/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rAU/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Use system selection (default)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Use system selection (default)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Use system selection (default)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 62c8e21..667e06a 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Air quality"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Cast info"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Home Controls"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Choose a profile picture"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Default user icon"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Physical keyboard"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/arrays.xml b/packages/SettingsLib/res/values-en-rCA/arrays.xml
index fc6f791..327e4e9 100644
--- a/packages/SettingsLib/res/values-en-rCA/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rCA/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Use system selection (default)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Use system selection (default)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Use system selection (default)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 8650b77..bf20fcc 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Air quality"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Cast info"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Home Controls"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Choose a profile picture"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Default user icon"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Physical keyboard"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/arrays.xml b/packages/SettingsLib/res/values-en-rGB/arrays.xml
index fc6f791..327e4e9 100644
--- a/packages/SettingsLib/res/values-en-rGB/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rGB/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Use system selection (default)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Use system selection (default)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Use system selection (default)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 62c8e21..667e06a 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Air quality"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Cast info"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Home Controls"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Choose a profile picture"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Default user icon"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Physical keyboard"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/arrays.xml b/packages/SettingsLib/res/values-en-rIN/arrays.xml
index fc6f791..327e4e9 100644
--- a/packages/SettingsLib/res/values-en-rIN/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rIN/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Use system selection (default)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Use system selection (default)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Use system selection (default)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 62c8e21..667e06a 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Air quality"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Cast info"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Home Controls"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Choose a profile picture"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Default user icon"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Physical keyboard"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/arrays.xml b/packages/SettingsLib/res/values-en-rXC/arrays.xml
index 34db380..8af0a4a 100644
--- a/packages/SettingsLib/res/values-en-rXC/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rXC/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‏‎‎‎map13‎‏‎‎‏‎"</item>
     <item msgid="8147982633566548515">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‎‏‏‎‎‎‎‏‎‎‏‎‎‎‎‏‏‎‏‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‎‎‎‏‎‎‎‏‏‎map14‎‏‎‎‏‎"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‎‎‏‏‏‎‏‎‎‏‎‎‏‏‏‎‎‎‏‎‏‏‏‎‎‎‎‏‏‎‏‎‏‏‏‎‏‏‎Use System Selection (Default)‎‏‎‎‏‎"</item>
+    <item msgid="4055460186095649420">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎‎‎‏‏‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‎‎‎‏‎‎‎‎‎‏‎‏‎‎‎‏‏‎‎‎SBC‎‏‎‎‏‎"</item>
+    <item msgid="720249083677397051">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‎‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‏‏‎‎‏‎‎‎‏‎‏‎‎‎‎‎‎‏‏‏‎‏‏‎AAC‎‏‎‎‏‎"</item>
+    <item msgid="1049450003868150455">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‎‎‏‎‏‎‎‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="QUALCOMM">Qualcomm®</xliff:g>‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎<xliff:g id="APTX">aptX™</xliff:g>‎‏‎‎‏‏‏‎ audio‎‏‎‎‏‎"</item>
+    <item msgid="2908219194098827570">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‏‏‎‎‏‎‎‏‏‏‎‏‏‏‏‎‎‎‎‎‏‎‎‏‏‎‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="QUALCOMM">Qualcomm®</xliff:g>‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎<xliff:g id="APTX_HD">aptX™ HD</xliff:g>‎‏‎‎‏‏‏‎ audio‎‏‎‎‏‎"</item>
+    <item msgid="3825367753087348007">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‎‏‏‎‎‏‏‎‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‏‎‏‎‎‎‎‎‎‎‎‏‎‏‏‎‏‏‎‏‎‎‏‎‎‏‏‏‎LDAC‎‏‎‎‏‎"</item>
+    <item msgid="328951785723550863">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‎‏‏‏‏‏‎‏‎‎‎‎‎‎‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‏‏‎‎‏‎‎‎‏‏‏‏‎LC3‎‏‎‎‏‎"</item>
+    <item msgid="506175145534048710">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‎‎‎‎‎‏‏‎‎‏‎‎‏‎‏‏‏‎‎‏‏‏‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‏‏‎‎‎‏‏‎‎Opus‎‏‎‎‏‎"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‏‏‏‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‎‎‏‎‏‎‎‏‎‏‎‎‎‏‏‎‎‏‎‎‏‏‏‎‏‏‏‎‎‎‎‎Use System Selection (Default)‎‏‎‎‏‎"</item>
+    <item msgid="9024885861221697796">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‎‎‎‎‏‎‎‎‏‎‎‏‎‎‎‎‎‏‎‎‎SBC‎‏‎‎‏‎"</item>
+    <item msgid="4688890470703790013">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎‏‎‎‏‎‎‏‎‎‏‎‎‎‎‏‎‏‎‎‏‎‏‎‎‎‏‎‎‎‎‎‎‏‏‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‏‎AAC‎‏‎‎‏‎"</item>
+    <item msgid="8627333814413492563">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎‎‏‎‏‎‎‎‎‎‏‏‎‏‏‎‏‎‏‎‏‎‎‎‎‎‎‎‏‎‏‎‏‎‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="QUALCOMM">Qualcomm®</xliff:g>‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎<xliff:g id="APTX">aptX™</xliff:g>‎‏‎‎‏‏‏‎ audio‎‏‎‎‏‎"</item>
+    <item msgid="3517061573669307965">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‏‏‏‏‎‎‎‏‏‏‎‎‏‏‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‏‏‎‎‎‏‏‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="QUALCOMM">Qualcomm®</xliff:g>‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎<xliff:g id="APTX_HD">aptX™ HD</xliff:g>‎‏‎‎‏‏‏‎ audio‎‏‎‎‏‎"</item>
+    <item msgid="2553206901068987657">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎‎‏‏‎‎‎‏‎‏‎‎‎‎‏‎‎‏‎‏‎‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎LDAC‎‏‎‎‏‎"</item>
+    <item msgid="3940992993241040716">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‏‎‎‎‏‎‎‏‏‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‏‎‎‎‎‏‏‏‎‏‎‎‏‏‎‎‎LC3‎‏‎‎‏‎"</item>
+    <item msgid="7940970833006181407">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‏‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‎‎‏‎‎‏‎‎‎‏‎‎‏‎‎‎‏‏‏‏‎‎‎‎‎‏‏‏‏‏‎Opus‎‏‎‎‏‎"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‎‎‏‏‎‏‏‏‎‎‏‎‏‏‎‎‎‎‎‎‎‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‎‏‏‎‎‏‏‏‏‎Use System Selection (Default)‎‏‎‎‏‎"</item>
     <item msgid="8003118270854840095">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎‏‎‏‎‏‎‎‎‏‏‏‏‎‏‏‎‏‏‏‏‎‏‏‎‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎44.1 kHz‎‏‎‎‏‎"</item>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 21697b9..21c1bb7 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‏‎‎‏‎‏‎‏‎‏‏‏‏‎‏‎‏‎‏‏‎‎‏‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎Air Quality‎‏‎‎‏‎"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‎‏‏‎‎‏‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎‏‏‏‎‏‏‎‎‏‏‏‎‎‎Cast Info‎‏‎‎‏‎"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‏‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎‎‎‏‏‎‎‏‎‏‎‏‏‏‎‎‎‏‏‏‎‏‎‎‏‏‏‏‏‎‏‏‎Home Controls‎‏‎‎‏‎"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‎‎‎‎‎‏‏‎‎‎‎‎‏‏‎‎‎‏‎‎‏‎‎‎‏‎‎‏‎‏‏‎‎‎‏‎‏‏‎‎‎‎‎Smartspace‎‏‎‎‏‎"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‏‎‎‏‏‎‎‏‎‎‏‎‎‏‎‏‎‎‎‎‏‏‎‏‎‎‏‎‎‎‏‏‎‏‏‎‎‎‏‎‏‏‎‏‏‎‏‏‏‎‎‎Choose a profile picture‎‏‎‎‏‎"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‎‏‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‏‏‎‏‏‎‏‎‏‎‎‎‎‎‎‏‏‏‎‏‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎Default user icon‎‏‎‎‏‎"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‏‎‎‎‏‎‏‎‎‎‎‎‎‎‎‏‏‏‎‏‎‏‏‏‎‎‎‏‎‏‎‏‎‏‎‎‎Physical keyboard‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 27cd0ab..8821f42 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -661,6 +661,8 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Calidad del aire"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info de reparto"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Control de la casa"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
+    <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Elige una foto de perfil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ícono de usuario predeterminado"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string>
diff --git a/packages/SettingsLib/res/values-es/arrays.xml b/packages/SettingsLib/res/values-es/arrays.xml
index 0677864..4924407 100644
--- a/packages/SettingsLib/res/values-es/arrays.xml
+++ b/packages/SettingsLib/res/values-es/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Usar preferencia del sistema (predeterminado)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Usar preferencia del sistema (predeterminado)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Usar preferencia del sistema (predeterminado)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index d221c8d..cea4835 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Tiempo"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Calidad del aire"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info. de emisión"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Domótica"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"De un vistazo"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Elige una imagen de perfil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Icono de usuario predeterminado"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string>
diff --git a/packages/SettingsLib/res/values-et/arrays.xml b/packages/SettingsLib/res/values-et/arrays.xml
index d986ecf..0402ac2 100644
--- a/packages/SettingsLib/res/values-et/arrays.xml
+++ b/packages/SettingsLib/res/values-et/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Süsteemi valiku kasutamine (vaikeseade)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Heli: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Heli: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Süsteemi valiku kasutamine (vaikeseade)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Heli: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Heli: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Süsteemi valiku kasutamine (vaikeseade)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index f32ae23..13fd319 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Ilm"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Õhukvaliteet"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Osatäitjate teave"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kodu juhtimine"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Ülevaade"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Valige profiilipilt"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Vaikekasutajaikoon"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Füüsiline klaviatuur"</string>
diff --git a/packages/SettingsLib/res/values-eu/arrays.xml b/packages/SettingsLib/res/values-eu/arrays.xml
index d166e1b..9c12e95 100644
--- a/packages/SettingsLib/res/values-eu/arrays.xml
+++ b/packages/SettingsLib/res/values-eu/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Erabili sistema-hautapena (lehenetsia)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audioa"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audioa"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Erabili sistema-hautapena (lehenetsia)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audioa"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audioa"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Erabili sistema-hautapena (lehenetsia)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index bcb57e8..3c96b6f 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Eguraldia"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Airearen kalitatea"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Igorpenari buruzko informazioa"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Etxeko gailuak kontrolatzeko aukerak"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Aukeratu profileko argazki bat"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Erabiltzaile lehenetsiaren ikonoa"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Teklatu fisikoa"</string>
diff --git a/packages/SettingsLib/res/values-fa/arrays.xml b/packages/SettingsLib/res/values-fa/arrays.xml
index b7761dd..41410cb 100644
--- a/packages/SettingsLib/res/values-fa/arrays.xml
+++ b/packages/SettingsLib/res/values-fa/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"استفاده از انتخاب سیستم (پیش‌فرض)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"استفاده از انتخاب سیستم (پیش‌فرض)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"استفاده از انتخاب سیستم (پیش‌فرض)"</item>
     <item msgid="8003118270854840095">"۴۴٫۱ کیلوهرتز"</item>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 6abe873..5190e0f 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"آب‌وهوا"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"کیفیت هوا"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"اطلاعات پخش محتوا"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"کنترل لوازم خانگی"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"انتخاب عکس نمایه"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"نماد کاربر پیش‌فرض"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"صفحه‌کلید فیزیکی"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 2f3436e..c50a2e0 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Sää"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Ilmanlaatu"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Striimaustiedot"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kodin ohjaus"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Valitse profiilikuva"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Oletuskäyttäjäkuvake"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/arrays.xml b/packages/SettingsLib/res/values-fr-rCA/arrays.xml
index 12acbb6..808c3f2 100644
--- a/packages/SettingsLib/res/values-fr-rCA/arrays.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Utiliser sélect. du système (par défaut)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Utiliser sélect. du système (par défaut)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Utiliser sélect. du système (par défaut)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 28b3cd9..b8b4659 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Météo"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualité de l\'air"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info diffusion"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Domotique"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Aperçu"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Choisir une photo de profil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Icône d\'utilisateur par défaut"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Clavier physique"</string>
diff --git a/packages/SettingsLib/res/values-fr/arrays.xml b/packages/SettingsLib/res/values-fr/arrays.xml
index 80ac7e4..92546da 100644
--- a/packages/SettingsLib/res/values-fr/arrays.xml
+++ b/packages/SettingsLib/res/values-fr/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Utiliser la sélection du système (par défaut)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Utiliser la sélection du système (par défaut)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Utiliser la sélection du système (par défaut)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index da1497a..4a2d01e 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Météo"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualité de l\'air"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Infos distribution"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Domotique"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Choisissez une photo de profil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Icône de l\'utilisateur par défaut"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Clavier physique"</string>
diff --git a/packages/SettingsLib/res/values-gl/arrays.xml b/packages/SettingsLib/res/values-gl/arrays.xml
index b6cf48e..f663120 100644
--- a/packages/SettingsLib/res/values-gl/arrays.xml
+++ b/packages/SettingsLib/res/values-gl/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Usar selección do sistema (predeterminado)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Usa a selección do sistema (predeterminado)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Usar selección do sistema (predeterminado)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 7822749..93f9fcf 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -662,6 +662,7 @@
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Datos da emisión"</string>
     <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
     <skip />
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Espazo intelixente"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Escolle unha imaxe do perfil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Icona do usuario predeterminado"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string>
diff --git a/packages/SettingsLib/res/values-gu/arrays.xml b/packages/SettingsLib/res/values-gu/arrays.xml
index 7e668e7..e527d81 100644
--- a/packages/SettingsLib/res/values-gu/arrays.xml
+++ b/packages/SettingsLib/res/values-gu/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"સિસ્ટમ પસંદગીનો ઉપયોગ કરો (ડિફૉલ્ટ)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ઑડિયો"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ઑડિયો"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"સિસ્ટમ પસંદગીનો ઉપયોગ કરો (ડિફૉલ્ટ)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ઑડિયો"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ઑડિયો"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"સિસ્ટમ પસંદગીનો ઉપયોગ કરો (ડિફૉલ્ટ)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index cebb1fe..0e19125 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"હવામાન"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"હવાની ક્વૉલિટી"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"કાસ્ટ વિશેની માહિતી"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ઘરેલુ સાધન નિયંત્રણો"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"પ્રોફાઇલ ફોટો પસંદ કરો"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ડિફૉલ્ટ વપરાશકર્તાનું આઇકન"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"ભૌતિક કીબોર્ડ"</string>
diff --git a/packages/SettingsLib/res/values-hi/arrays.xml b/packages/SettingsLib/res/values-hi/arrays.xml
index 13da75b..9b8d83e 100644
--- a/packages/SettingsLib/res/values-hi/arrays.xml
+++ b/packages/SettingsLib/res/values-hi/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ऑडियो"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ऑडियो"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"सिस्टम से चुने जाने का इस्तेमाल करें (डिफ़ॉल्ट)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ऑडियो"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ऑडियो"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 417d2b2..ed0a771 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"मौसम"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"हवा की क्वालिटी"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"कास्टिंग की जानकारी"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"होम कंट्रोल"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"स्मार्टस्पेस"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"प्रोफ़ाइल फ़ोटो चुनें"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"उपयोगकर्ता के लिए डिफ़ॉल्ट आइकॉन"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"फ़िज़िकल कीबोर्ड"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 267000f..af26040 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Vremenska prognoza"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kvaliteta zraka"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Inform. o emitiranju"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Upravlj. kuć. uređ."</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Odabir profilne slike"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ikona zadanog korisnika"</string>
diff --git a/packages/SettingsLib/res/values-hu/arrays.xml b/packages/SettingsLib/res/values-hu/arrays.xml
index a5f37ea..409d600 100644
--- a/packages/SettingsLib/res/values-hu/arrays.xml
+++ b/packages/SettingsLib/res/values-hu/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Rendszerérték (alapértelmezett)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Hang: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Hang: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Rendszerérték (alapértelmezett)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Hang: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Hang: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Rendszerérték (alapértelmezett)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 69ddad8..3db43e7 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Időjárás"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Levegőminőség"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Átküldési információ"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Otthonvezérlés"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Profilkép választása"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Alapértelmezett felhasználó ikonja"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fizikai billentyűzet"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index d8efbd3..8971fce 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Եղանակ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Օդի որակը"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Հեռարձակման տվյալներ"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Տան կարգավորումներ"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Պրոֆիլի նկար ընտրեք"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Օգտատիրոջ կանխադրված պատկերակ"</string>
diff --git a/packages/SettingsLib/res/values-in/arrays.xml b/packages/SettingsLib/res/values-in/arrays.xml
index 5b0ad98..9527417 100644
--- a/packages/SettingsLib/res/values-in/arrays.xml
+++ b/packages/SettingsLib/res/values-in/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Gunakan Pilihan Sistem (Default)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Gunakan Pilihan Sistem (Default)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Gunakan Pilihan Sistem (Default)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 029ad8b..6185fd3 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Cuaca"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kualitas Udara"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info Transmisi"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kontrol Rumah"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Pilih foto profil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ikon pengguna default"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Keyboard fisik"</string>
diff --git a/packages/SettingsLib/res/values-is/arrays.xml b/packages/SettingsLib/res/values-is/arrays.xml
index 9d481f8..0b5b978 100644
--- a/packages/SettingsLib/res/values-is/arrays.xml
+++ b/packages/SettingsLib/res/values-is/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Nota val kerfisins (sjálfgefið)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> hljóð"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> hljóð"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Nota val kerfisins (sjálfgefið)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> hljóð"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> hljóð"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Nota val kerfisins (sjálfgefið)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index ccf06b0..e718786 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Veður"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Loftgæði"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Útsendingaruppl."</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Heimastýringar"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Veldu prófílmynd"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Tákn sjálfgefins notanda"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Vélbúnaðarlyklaborð"</string>
diff --git a/packages/SettingsLib/res/values-it/arrays.xml b/packages/SettingsLib/res/values-it/arrays.xml
index 62450da3..ae1e515 100644
--- a/packages/SettingsLib/res/values-it/arrays.xml
+++ b/packages/SettingsLib/res/values-it/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Usa selezione di sistema (predefinita)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Usa selezione di sistema (predefinita)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Usa selezione di sistema (predefinita)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 3811152..2e9b202 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -117,7 +117,7 @@
     <string name="bluetooth_profile_opp" msgid="6692618568149493430">"Trasferimento file"</string>
     <string name="bluetooth_profile_hid" msgid="2969922922664315866">"Dispositivo di input"</string>
     <string name="bluetooth_profile_pan" msgid="1006235139308318188">"Accesso a Internet"</string>
-    <string name="bluetooth_profile_pbap" msgid="4262303387989406171">"Condivis. contatti e cronologia chiamate"</string>
+    <string name="bluetooth_profile_pbap" msgid="4262303387989406171">"Condivisione contatti e cronologia chiamate"</string>
     <string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"Usa per condivisione di contatti e cronologia chiamate"</string>
     <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"Condivisione connessione Internet"</string>
     <string name="bluetooth_profile_map" msgid="8907204701162107271">"SMS"</string>
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Meteo"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualità dell\'aria"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info sul cast"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Controlli della casa"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Scegli un\'immagine del profilo"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Icona dell\'utente predefinito"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Tastiera fisica"</string>
diff --git a/packages/SettingsLib/res/values-iw/arrays.xml b/packages/SettingsLib/res/values-iw/arrays.xml
index 49f3fcf..5d72aff 100644
--- a/packages/SettingsLib/res/values-iw/arrays.xml
+++ b/packages/SettingsLib/res/values-iw/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"שימוש בבחירת המערכת (ברירת המחדל)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"אודיו <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"אודיו <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"שימוש בבחירת המערכת (ברירת המחדל)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"אודיו <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"אודיו <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"שימוש בבחירת המערכת (ברירת המחדל)"</item>
     <item msgid="8003118270854840095">"44.1 קילו-הרץ"</item>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 3421fb5..7e9fd89 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"איכות האוויר"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"פרטי ההעברה"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"בית חכם"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"בחירה של תמונת פרופיל"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"סמל המשתמש שמוגדר כברירת מחדל"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"מקלדת פיזית"</string>
diff --git a/packages/SettingsLib/res/values-ja/arrays.xml b/packages/SettingsLib/res/values-ja/arrays.xml
index d73cc43..775e31c 100644
--- a/packages/SettingsLib/res/values-ja/arrays.xml
+++ b/packages/SettingsLib/res/values-ja/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"システムの選択(デフォルト)を使用"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> オーディオ"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> オーディオ"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"システムの選択(デフォルト)を使用"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> オーディオ"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> オーディオ"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"システムの選択(デフォルト)を使用"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index e7d2cf8..a3311f5 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"天気"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"大気質"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"キャスト情報"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"スマートホーム"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"プロフィール写真の選択"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"デフォルト ユーザー アイコン"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"物理キーボード"</string>
diff --git a/packages/SettingsLib/res/values-ka/arrays.xml b/packages/SettingsLib/res/values-ka/arrays.xml
index c0d6f97..f3545b6 100644
--- a/packages/SettingsLib/res/values-ka/arrays.xml
+++ b/packages/SettingsLib/res/values-ka/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"სისტემის არჩეულის გამოყენება (ნაგულისხმევი)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> აუდიო"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> აუდიო"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"სისტემის არჩეულის გამოყენება (ნაგულისხმევი)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> აუდიო"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> აუდიო"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"სისტემის არჩეულის გამოყენება (ნაგულისხმევი)"</item>
     <item msgid="8003118270854840095">"44,1 კჰც"</item>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 09f5471..b3e4402 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"ჰაერის ხარისხი"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ტრანსლირების ინფო"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"სახლის მართვა"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"ჭკვიანი სივრცე"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"აირჩიეთ პროფილის სურათი"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"მომხმარებლის ნაგულისხმევი ხატულა"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"ფიზიკური კლავიატურა"</string>
diff --git a/packages/SettingsLib/res/values-kk/arrays.xml b/packages/SettingsLib/res/values-kk/arrays.xml
index fc998e7..9971f86 100644
--- a/packages/SettingsLib/res/values-kk/arrays.xml
+++ b/packages/SettingsLib/res/values-kk/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Жүйенің таңдағанын алу (әдепкі)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудиокодегі"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудиокодегі"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"L34C"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Жүйенің таңдағанын алу (әдепкі)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудиокодегі"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудиокодегі"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"L34C"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Жүйенің таңдағанын алу (әдепкі)"</item>
     <item msgid="8003118270854840095">"44,1 кГц"</item>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 3281303..5ecba9b 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Ауа райы"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Ауа сапасы"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Трансляция ақпараты"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Үйді басқару элементтері"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Профиль суретін таңдау"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Әдепкі пайдаланушы белгішесі"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Пернетақта"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 87c1aa2..275bcc1 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"អាកាសធាតុ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"គុណភាព​ខ្យល់"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ព័ត៌មានអំពីការបញ្ជូន"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ការគ្រប់គ្រង​ផ្ទះ"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"ជ្រើសរើស​រូបភាព​កម្រង​ព័ត៌មាន"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"រូបអ្នកប្រើប្រាស់លំនាំដើម"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index ab437e8..7e9ff12 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"ಹವಾಮಾನ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"ವಾಯು ಗುಣಮಟ್ಟ"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ಬಿತ್ತರಿಸಿದ ಮಾಹಿತಿ"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ಹೋಮ್ ನಿಯಂತ್ರಣಗಳು"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"ಪ್ರೊಫೈಲ್ ಚಿತ್ರವನ್ನು ಆಯ್ಕೆ ಮಾಡಿ"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ಡೀಫಾಲ್ಟ್ ಬಳಕೆದಾರರ ಐಕಾನ್"</string>
diff --git a/packages/SettingsLib/res/values-ko/arrays.xml b/packages/SettingsLib/res/values-ko/arrays.xml
index 7138113..16b840b 100644
--- a/packages/SettingsLib/res/values-ko/arrays.xml
+++ b/packages/SettingsLib/res/values-ko/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"시스템 설정 사용(기본)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 오디오"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 오디오"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"시스템 설정 사용(기본)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 오디오"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 오디오"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"시스템 설정 사용(기본)"</item>
     <item msgid="8003118270854840095">"44.1kHz"</item>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 2b8ab75..fa96ad78 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -385,7 +385,7 @@
     <string name="force_msaa" msgid="4081288296137775550">"4x MSAA 강제 사용"</string>
     <string name="force_msaa_summary" msgid="9070437493586769500">"OpenGL ES 2.0 앱에서 4x MSAA 사용"</string>
     <string name="show_non_rect_clip" msgid="7499758654867881817">"사각형이 아닌 클립 작업 디버그"</string>
-    <string name="track_frame_time" msgid="522674651937771106">"프로필 HWUI 렌더링"</string>
+    <string name="track_frame_time" msgid="522674651937771106">"HWUI 렌더링 프로파일"</string>
     <string name="enable_gpu_debug_layers" msgid="4986675516188740397">"GPU 디버그 레이어 사용 설정"</string>
     <string name="enable_gpu_debug_layers_summary" msgid="4921521407377170481">"디버그 앱에 GPU 디버그 레이어 로드 허용"</string>
     <string name="enable_verbose_vendor_logging" msgid="1196698788267682072">"상세 공급업체 로깅 사용 설정"</string>
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"날씨"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"대기 상태"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"전송 정보"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"홈 컨트롤"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"프로필 사진 선택하기"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"기본 사용자 아이콘"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"물리적 키보드"</string>
diff --git a/packages/SettingsLib/res/values-ky/arrays.xml b/packages/SettingsLib/res/values-ky/arrays.xml
index 40271f7..700aae1 100644
--- a/packages/SettingsLib/res/values-ky/arrays.xml
+++ b/packages/SettingsLib/res/values-ky/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"карта13"</item>
     <item msgid="8147982633566548515">"карта14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Система тандаганды колдонуу (демейки)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Система тандаганды колдонуу (демейки)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Система тандаганды колдонуу (демейки)"</item>
     <item msgid="8003118270854840095">"44,1 кГц"</item>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index e993e3f..9ebd47b 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Аба ырайы"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Абанын сапаты"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Тышкы экранга чыгаруу маалыматы"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Үйдү көзөмөлдөө"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Профилдин сүрөтүн тандоо"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Демейки колдонуучунун сүрөтчөсү"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Аппараттык баскычтоп"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index d295932..11d6d43 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"ສະພາບອາກາດ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"ຄຸນນະພາບ​ອາກາດ"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ຂໍ້ມູນການສົ່ງສັນຍານ"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ການຄວບຄຸມເຮືອນ"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"ເລືອກຮູບໂປຣໄຟລ໌"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ໄອຄອນຜູ້ໃຊ້ເລີ່ມຕົ້ນ"</string>
diff --git a/packages/SettingsLib/res/values-lt/arrays.xml b/packages/SettingsLib/res/values-lt/arrays.xml
index 946f69c..c0aafdc 100644
--- a/packages/SettingsLib/res/values-lt/arrays.xml
+++ b/packages/SettingsLib/res/values-lt/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Naudoti sistemos pasirink. (numatytasis)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> garsas"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> garsas"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Naudoti sistemos pasirink. (numatytasis)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> garsas"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> garsas"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Naudoti sistemos pasirink. (numatytasis)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index e38889b..e016956 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Oro kokybė"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Perdav. informacija"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Namų sist. valdikl."</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Pasirinkite profilio nuotrauką"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Numatytojo naudotojo piktograma"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fizinė klaviatūra"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index d899ada..ffd41635 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Laikapstākļi"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Gaisa kvalitāte"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Apraides informācija"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Mājas kontrolierīces"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Profila attēla izvēle"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Noklusējuma lietotāja ikona"</string>
diff --git a/packages/SettingsLib/res/values-mk/arrays.xml b/packages/SettingsLib/res/values-mk/arrays.xml
index 9c46f21..41427c1 100644
--- a/packages/SettingsLib/res/values-mk/arrays.xml
+++ b/packages/SettingsLib/res/values-mk/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Користи избор на системот (стандардно)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Користи избор на системот (стандардно)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Користи избор на системот (стандардно)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index c8033e3..26a87e3 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Квалитет на воздух"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Инфо за улогите"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Контроли за домот"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Изберете профилна слика"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Икона за стандарден корисник"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Физичка тастатура"</string>
diff --git a/packages/SettingsLib/res/values-ml/arrays.xml b/packages/SettingsLib/res/values-ml/arrays.xml
index 4715e2a..98e3bd6 100644
--- a/packages/SettingsLib/res/values-ml/arrays.xml
+++ b/packages/SettingsLib/res/values-ml/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"സിസ്റ്റം സെലക്ഷൻ ഉപയോഗിക്കൂ ‌(ഡിഫോൾട്ട്)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ഓഡിയോ"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ഓഡിയോ"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"സിസ്റ്റം സെലക്ഷൻ ഉപയോഗിക്കൂ ‌(ഡിഫോൾട്ട്)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ഓഡിയോ"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ഓഡിയോ"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"സിസ്റ്റം സെലക്ഷൻ ഉപയോഗിക്കൂ ‌(ഡിഫോൾട്ട്)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 089f6af..5d9f799 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"വായു നിലവാരം"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"കാസ്റ്റ് വിവരങ്ങൾ"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ഹോം കൺട്രോളുകൾ"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"പ്രൊഫൈൽ ചിത്രം തിരഞ്ഞെടുക്കുക"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ഡിഫോൾട്ട് ഉപയോക്തൃ ഐക്കൺ"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"ഫിസിക്കൽ കീബോർഡ്"</string>
diff --git a/packages/SettingsLib/res/values-mn/arrays.xml b/packages/SettingsLib/res/values-mn/arrays.xml
index 63fa53b..f3c10d7 100644
--- a/packages/SettingsLib/res/values-mn/arrays.xml
+++ b/packages/SettingsLib/res/values-mn/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Системийн сонголтыг ашиглах (Өгөгдмөл)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Системийн сонголтыг ашиглах (Өгөгдмөл)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Системийн сонголтыг ашиглах (Өгөгдмөл)"</item>
     <item msgid="8003118270854840095">"44.1 кГц"</item>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index cc35053..df6fa89 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -447,8 +447,8 @@
     <string name="daltonizer_mode_deuteranomaly" msgid="3507284319584683963">"Дьютераномаль (улаан-ногоон)"</string>
     <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномаль (улаан-ногоон)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомаль (цэнхэр-шар)"</string>
-    <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Өнгө тохируулах"</string>
-    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Өнгөний засвар нь таныг дараахыг хийхийг хүсэх үед хэрэгтэй байж болно:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Өнгөнүүдийг илүү нарийвчилж харах&lt;/li&gt; &lt;li&gt;&amp;nbsp;Төвлөрөхийн тулд өнгөнүүдийг хасах&lt;/li&gt; &lt;/ol&gt;"</string>
+    <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Өнгө тохируулга"</string>
+    <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Өнгө тохируулга нь таныг дараахыг хийхийг хүсэх үед хэрэгтэй байж болно:&lt;br/&gt; &lt;ol&gt; &lt;li&gt;&amp;nbsp;Өнгөнүүдийг илүү нарийвчилж харах&lt;/li&gt; &lt;li&gt;&amp;nbsp;Төвлөрөхийн тулд өнгөнүүдийг хасах&lt;/li&gt; &lt;/ol&gt;"</string>
     <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Давхарласан <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
     <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ойролцоогоор <xliff:g id="TIME_REMAINING">%1$s</xliff:g> үлдсэн"</string>
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Агаарын чанар"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Дамжуулах мэдээлэл"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Гэрийн удирдлага"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Профайл зураг сонгох"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Өгөгдмөл хэрэглэгчийн дүрс тэмдэг"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Биет гар"</string>
diff --git a/packages/SettingsLib/res/values-mr/arrays.xml b/packages/SettingsLib/res/values-mr/arrays.xml
index a54f990..c37baaa2 100644
--- a/packages/SettingsLib/res/values-mr/arrays.xml
+++ b/packages/SettingsLib/res/values-mr/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"सिस्टीम निवड वापरा (डीफॉल्ट)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ऑडिओ"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ऑडिओ"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"सिस्टम निवड वापरा (डीफॉल्ट)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ऑडिओ"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ऑडिओ"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"सिस्टम निवड वापरा (डीफॉल्ट)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 1907d00..4597549 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"हवेची गुणवत्ता"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"कास्टसंबंधित माहिती"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"होम कंट्रोल"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"स्मार्टस्पेस"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"प्रोफाइल फोटो निवडा"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"डीफॉल्ट वापरकर्ता आयकन"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"वास्तविक कीबोर्ड"</string>
diff --git a/packages/SettingsLib/res/values-ms/arrays.xml b/packages/SettingsLib/res/values-ms/arrays.xml
index b26ed23..b19f038 100644
--- a/packages/SettingsLib/res/values-ms/arrays.xml
+++ b/packages/SettingsLib/res/values-ms/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Gunakan Pilihan Sistem (Lalai)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Gunakan Pilihan Sistem (Lalai)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Gunakan Pilihan Sistem (Lalai)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index b3929c9..1471040 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Cuaca"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kualiti Udara"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Maklumat Pelakon"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kawalan Rumah"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Pilih gambar profil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ikon pengguna lalai"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Papan kekunci fizikal"</string>
diff --git a/packages/SettingsLib/res/values-my/arrays.xml b/packages/SettingsLib/res/values-my/arrays.xml
index ed95dfe..3398c5b 100644
--- a/packages/SettingsLib/res/values-my/arrays.xml
+++ b/packages/SettingsLib/res/values-my/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"စနစ်ရွေးချယ်မှုကို အသုံးပြုပါ (မူရင်း)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> အသံ"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> အသံ"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"စနစ်ရွေးချယ်မှုကို အသုံးပြုပါ (မူရင်း)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> အသံ"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> အသံ"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"စနစ်ရွေးချယ်မှုကို အသုံးပြုပါ (မူရင်း)"</item>
     <item msgid="8003118270854840095">"၄၄.၁ kHz"</item>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index f7a33a9..ee5601f 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"မိုးလေဝသ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"လေထုအရည်အသွေး"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ကာစ် အချက်အလက်"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"အိမ်သတ်မှတ်ချက်များ"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"ပရိုဖိုင်ပုံ ရွေးပါ"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"မူရင်းအသုံးပြုသူ သင်္ကေတ"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"ပကတိ ကီးဘုတ်"</string>
diff --git a/packages/SettingsLib/res/values-nb/arrays.xml b/packages/SettingsLib/res/values-nb/arrays.xml
index 317c2db..7e65fa0 100644
--- a/packages/SettingsLib/res/values-nb/arrays.xml
+++ b/packages/SettingsLib/res/values-nb/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Bruk systemvalg (standard)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-lyd"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-lyd"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Bruk systemvalg (standard)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-lyd"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-lyd"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Bruk systemvalg (standard)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 9b99631..a8fd256 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Vær"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luftkvalitet"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Castinformasjon"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Hjemkontroller"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Velg et profilbilde"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Standard brukerikon"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fysisk tastatur"</string>
diff --git a/packages/SettingsLib/res/values-ne/arrays.xml b/packages/SettingsLib/res/values-ne/arrays.xml
index b3c3ee2..ac1f187 100644
--- a/packages/SettingsLib/res/values-ne/arrays.xml
+++ b/packages/SettingsLib/res/values-ne/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"सिस्टमको छनौट प्रयोग गरियोस् (डिफल्ट)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> अडियो"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> अडियो"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"सिस्टमको छनौट प्रयोग गरियोस् (डिफल्ट)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> अडियो"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> अडियो"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"सिस्टमको छनौट प्रयोग गरियोस् (डिफल्ट)"</item>
     <item msgid="8003118270854840095">"४४.१ kHz"</item>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 1534cba..17b81b2 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"मौसम"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"वायुको गुणस्तर"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"कास्टसम्बन्धी जानकारी"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"घरायसी उपकरणका नियन्त्रण"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"प्रोफाइल फोटो छान्नुहोस्"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"प्रयोगकर्ताको डिफल्ट आइकन"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"भौतिक किबोर्ड"</string>
diff --git a/packages/SettingsLib/res/values-nl/arrays.xml b/packages/SettingsLib/res/values-nl/arrays.xml
index e809452..7c90eab 100644
--- a/packages/SettingsLib/res/values-nl/arrays.xml
+++ b/packages/SettingsLib/res/values-nl/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Systeemselectie gebruiken (standaard)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Gebruik systeemselectie (standaard)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Systeemselectie gebruiken (standaard)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index ba11b46..1251ed9 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luchtkwaliteit"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Castinformatie"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Bediening voor in huis"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Kies een profielfoto"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Standaard gebruikersicoon"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fysiek toetsenbord"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 09fa59d..715f1eb 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -116,12 +116,12 @@
     <string name="bluetooth_profile_headset" msgid="5395952236133499331">"ଫୋନ୍‌ କଲ୍‌‌ଗୁଡ଼ିକ"</string>
     <string name="bluetooth_profile_opp" msgid="6692618568149493430">"ଫାଇଲ୍‌ ଟ୍ରାନ୍ସଫର୍‌"</string>
     <string name="bluetooth_profile_hid" msgid="2969922922664315866">"ଇନ୍‌ପୁଟ୍‌ ଡିଭାଇସ୍"</string>
-    <string name="bluetooth_profile_pan" msgid="1006235139308318188">"ଇଣ୍ଟର୍‌ନେଟ୍‌ ଆକ୍ସେସ୍"</string>
+    <string name="bluetooth_profile_pan" msgid="1006235139308318188">"ଇଣ୍ଟରନେଟ ଆକ୍ସେସ"</string>
     <string name="bluetooth_profile_pbap" msgid="4262303387989406171">"କଣ୍ଟାକ୍ଟ ଏବଂ କଲ ଇତିହାସ ସେୟାରିଂ"</string>
     <string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"କଣ୍ଟାକ୍ଟ ଏବଂ କଲ ଇତିହାସ ସେୟାରିଂ ପାଇଁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"ଇଣ୍ଟର୍‌ନେଟ୍‌ ସଂଯୋଗ ଶେୟାରିଙ୍ଗ"</string>
     <string name="bluetooth_profile_map" msgid="8907204701162107271">"ଟେକ୍ସଟ୍ ମେସେଜ୍"</string>
-    <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM ଆକ୍ସେସ୍‌"</string>
+    <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM ଆକ୍ସେସ"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ଅଡିଓ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ଅଡିଓ"</string>
     <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ଶ୍ରବଣ ଯନ୍ତ୍ର"</string>
@@ -156,7 +156,7 @@
     <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ଦ୍ୱାରା ପେୟାରିଙ୍ଗ ପାଇଁ ପ୍ରତ୍ୟାଖ୍ୟାନ କରିଦିଆଗଲା।"</string>
     <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"କମ୍ପ୍ୟୁଟର୍"</string>
     <string name="bluetooth_talkback_headset" msgid="3406852564400882682">"ହେଡ୍‌ସେଟ୍‌"</string>
-    <string name="bluetooth_talkback_phone" msgid="868393783858123880">"ଫୋନ୍‌"</string>
+    <string name="bluetooth_talkback_phone" msgid="868393783858123880">"ଫୋନ"</string>
     <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"ଇମେଜିଙ୍ଗ"</string>
     <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"ହେଡ୍‌ଫୋନ୍‌"</string>
     <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"ଇନ୍‌ପୁଟ୍‌ ଉପକରଣ"</string>
@@ -246,7 +246,7 @@
     <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"ଛଅ ଡିଜିଟ୍ କୋଡ୍ ବ୍ୟବହାର କରି ନୂଆ ଡିଭାଇସଗୁଡ଼ିକୁ ପେୟାର୍ କରନ୍ତୁ"</string>
     <string name="adb_paired_devices_title" msgid="5268997341526217362">"ପେୟାର୍ ହୋଇଥିବା ଡିଭାଇସଗୁଡ଼ିକ"</string>
     <string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"ବର୍ତ୍ତମାନ ସଂଯୁକ୍ତ ଅଛି"</string>
-    <string name="adb_wireless_device_details_title" msgid="7129369670526565786">"ଡିଭାଇସ୍ ବିବରଣୀ"</string>
+    <string name="adb_wireless_device_details_title" msgid="7129369670526565786">"ଡିଭାଇସର ବିବରଣୀ"</string>
     <string name="adb_device_forget" msgid="193072400783068417">"ଭୁଲିଯାଆନ୍ତୁ"</string>
     <string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"ଡିଭାଇସ୍ ଫିଙ୍ଗରପ୍ରିଣ୍ଟ: <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string>
     <string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"ସଂଯୋଗ ବିଫଳ ହେଲା"</string>
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"ପାଣିପାଗ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"ବାୟୁର ଗୁଣବତ୍ତା"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"କାଷ୍ଟ ସୂଚନା"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ହୋମ କଣ୍ଟ୍ରୋଲ"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"ଏକ ପ୍ରୋଫାଇଲ ଛବି ବାଛନ୍ତୁ"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ଡିଫଲ୍ଟ ଉପଯୋଗକର୍ତ୍ତା ଆଇକନ"</string>
diff --git a/packages/SettingsLib/res/values-pa/arrays.xml b/packages/SettingsLib/res/values-pa/arrays.xml
index a3fae22..0fd5c56 100644
--- a/packages/SettingsLib/res/values-pa/arrays.xml
+++ b/packages/SettingsLib/res/values-pa/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"ਸਿਸਟਮ ਚੋਣ ਦੀ ਵਰਤੋਂ ਕਰੋ (ਪੂਰਵ-ਨਿਰਧਾਰਿਤ)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ਆਡੀਓ"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ਆਡੀਓ"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"ਸਿਸਟਮ ਚੋਣ ਦੀ ਵਰਤੋਂ ਕਰੋ (ਪੂਰਵ-ਨਿਰਧਾਰਿਤ)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ਆਡੀਓ"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ਆਡੀਓ"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"ਸਿਸਟਮ ਚੋਣ ਦੀ ਵਰਤੋਂ ਕਰੋ (ਪੂਰਵ-ਨਿਰਧਾਰਿਤ)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 9df0fef..51bf22d 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"ਮੌਸਮ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"ਹਵਾ ਦੀ ਕੁਆਲਿਟੀ"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ਕਾਸਟ ਸੰਬੰਧੀ ਜਾਣਕਾਰੀ"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ਹੋਮ ਕੰਟਰੋਲ"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"ਕੋਈ ਪ੍ਰੋਫਾਈਲ ਤਸਵੀਰ ਚੁਣੋ"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਵਰਤੋਂਕਾਰ ਪ੍ਰਤੀਕ"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"ਭੌਤਿਕ ਕੀ-ਬੋਰਡ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index f4599fd..64cbe78 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Pogoda"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Jakość powietrza"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Obsada"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Sterowanie domem"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Wybierz zdjęcie profilowe"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ikona domyślnego użytkownika"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/arrays.xml b/packages/SettingsLib/res/values-pt-rBR/arrays.xml
index 1883ef3..f218fab 100644
--- a/packages/SettingsLib/res/values-pt-rBR/arrays.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Usar seleção do sistema (padrão)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Usar seleção do sistema (padrão)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Usar seleção do sistema (padrão)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 12507c5..531bc63 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -208,7 +208,7 @@
     <string name="tts_engine_settings_title" msgid="7849477533103566291">"Configurações para <xliff:g id="TTS_ENGINE_NAME">%s</xliff:g>"</string>
     <string name="tts_engine_settings_button" msgid="477155276199968948">"Iniciar configurações do mecanismo"</string>
     <string name="tts_engine_preference_section_title" msgid="3861562305498624904">"Mecanismo preferencial"</string>
-    <string name="tts_general_section_title" msgid="8919671529502364567">"Gerais"</string>
+    <string name="tts_general_section_title" msgid="8919671529502364567">"Geral"</string>
     <string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"Redefinir o tom de voz"</string>
     <string name="tts_reset_speech_pitch_summary" msgid="6822904157021406449">"Redefinir o tom de voz para o padrão."</string>
   <string-array name="tts_rate_entries">
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Clima"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualidade ­do ar"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info. de transmissão"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Automação residencial"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Escolher a foto do perfil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ícone de usuário padrão"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/arrays.xml b/packages/SettingsLib/res/values-pt-rPT/arrays.xml
index 985bd51..e323455 100644
--- a/packages/SettingsLib/res/values-pt-rPT/arrays.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Utilizar seleção do sistema (predefinido)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Utilizar seleção do sistema (predefinido)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Utilizar seleção do sistema (predefinido)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 4988136..ed3f642 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualidade do ar"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info. de transmissão"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Ctr. domésticos"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Espaço inteligente"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Escolha uma imagem do perfil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ícone do utilizador predefinido"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string>
diff --git a/packages/SettingsLib/res/values-pt/arrays.xml b/packages/SettingsLib/res/values-pt/arrays.xml
index 1883ef3..f218fab 100644
--- a/packages/SettingsLib/res/values-pt/arrays.xml
+++ b/packages/SettingsLib/res/values-pt/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Usar seleção do sistema (padrão)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Usar seleção do sistema (padrão)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Usar seleção do sistema (padrão)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 12507c5..531bc63 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -208,7 +208,7 @@
     <string name="tts_engine_settings_title" msgid="7849477533103566291">"Configurações para <xliff:g id="TTS_ENGINE_NAME">%s</xliff:g>"</string>
     <string name="tts_engine_settings_button" msgid="477155276199968948">"Iniciar configurações do mecanismo"</string>
     <string name="tts_engine_preference_section_title" msgid="3861562305498624904">"Mecanismo preferencial"</string>
-    <string name="tts_general_section_title" msgid="8919671529502364567">"Gerais"</string>
+    <string name="tts_general_section_title" msgid="8919671529502364567">"Geral"</string>
     <string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"Redefinir o tom de voz"</string>
     <string name="tts_reset_speech_pitch_summary" msgid="6822904157021406449">"Redefinir o tom de voz para o padrão."</string>
   <string-array name="tts_rate_entries">
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Clima"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualidade ­do ar"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info. de transmissão"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Automação residencial"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Escolher a foto do perfil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ícone de usuário padrão"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string>
diff --git a/packages/SettingsLib/res/values-ro/arrays.xml b/packages/SettingsLib/res/values-ro/arrays.xml
index f1e9986..34b0ac9 100644
--- a/packages/SettingsLib/res/values-ro/arrays.xml
+++ b/packages/SettingsLib/res/values-ro/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Folosiți selectarea sistemului (prestabilit)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Folosiți selectarea sistemului (prestabilit)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Folosiți selectarea sistemului (prestabilit)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index f49fcdd..8f87a7c 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Meteo"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Calitatea aerului"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Informații artiști"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Controlul locuinței"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Alegeți o fotografie de profil"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Pictograma prestabilită a utilizatorului"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Tastatură fizică"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 38930a5..e2f05dc 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Погода"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Качество воздуха"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Данные о трансляции"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Автоматизация дома"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Выберите фото профиля"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Значок пользователя по умолчанию"</string>
diff --git a/packages/SettingsLib/res/values-si/arrays.xml b/packages/SettingsLib/res/values-si/arrays.xml
index 8386c1a..eaacfb8 100644
--- a/packages/SettingsLib/res/values-si/arrays.xml
+++ b/packages/SettingsLib/res/values-si/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"පද්ධති තේරීම භාවිත කරන්න (පෙරනිමි)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ශ්‍රව්‍යය"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ශ්‍රව්‍යය"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"පද්ධති තේරීම භාවිත කරන්න (පෙරනිමි)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ශ්‍රව්‍යය"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ශ්‍රව්‍යය"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"පද්ධති තේරීම භාවිත කරන්න (පෙරනිමි)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index fa0296f..1d52e0b 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"කාලගුණය"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"වායු ගුණත්වය"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"විකාශ තතු"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"නිවෙස් පාලන"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"ස්මාර්ට් අවකාශය"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"පැතිකඩ පින්තූරයක් තේරීම"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"පෙරනිමි පරිශීලක නිරූපකය"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"භෞතික යතුරු පුවරුව"</string>
diff --git a/packages/SettingsLib/res/values-sk/arrays.xml b/packages/SettingsLib/res/values-sk/arrays.xml
index 370b23f..bbfe969 100644
--- a/packages/SettingsLib/res/values-sk/arrays.xml
+++ b/packages/SettingsLib/res/values-sk/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Použiť voľbu systému (predvolené)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Zvuk: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Zvuk: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Použiť voľbu systému (predvolené)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Zvuk: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Zvuk: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Použiť voľbu systému (predvolené)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index a7c69c8..cf501cd 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kvalita vzduchu"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Informácie o prenose"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Ovládanie domácnosti"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Výber profilovej fotky"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Predvolená ikona používateľa"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fyzická klávesnica"</string>
diff --git a/packages/SettingsLib/res/values-sl/arrays.xml b/packages/SettingsLib/res/values-sl/arrays.xml
index 6e33e38..b2003e5 100644
--- a/packages/SettingsLib/res/values-sl/arrays.xml
+++ b/packages/SettingsLib/res/values-sl/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Uporabi sistemsko izbiro (privzeto)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Zvok <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Zvok <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Uporabi sistemsko izbiro (privzeto)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Zvok <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Zvok <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Uporabi sistemsko izbiro (privzeto)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 7334cea..bc89f5a 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kakovost zraka"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"O zasedbi"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Nadzor doma"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Hitri pregled"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Izbira profilne slike"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Privzeta ikona uporabnika"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fizična tipkovnica"</string>
diff --git a/packages/SettingsLib/res/values-sq/arrays.xml b/packages/SettingsLib/res/values-sq/arrays.xml
index 8a6d853..ed86380 100644
--- a/packages/SettingsLib/res/values-sq/arrays.xml
+++ b/packages/SettingsLib/res/values-sq/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Audioja e <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Audioja e <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Audioja e <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Audioja e <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 59359cdf..e7af25d 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Moti"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Cilësia e ajrit"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Të dhënat e aktorëve"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kontrollet e shtëpisë"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Zgjidh një fotografi profili"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ikona e parazgjedhur e përdoruesit"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Tastiera fizike"</string>
diff --git a/packages/SettingsLib/res/values-sr/arrays.xml b/packages/SettingsLib/res/values-sr/arrays.xml
index 7e198bf..a95e47b 100644
--- a/packages/SettingsLib/res/values-sr/arrays.xml
+++ b/packages/SettingsLib/res/values-sr/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Користи избор система (подразумевано)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Користи избор система (подразумевано)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Користи избор система (подразумевано)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index eee3f89..a02d674 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Време"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Квалитет ваздуха"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Подаци о пребацивању"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Управљање домом"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"SmartSpace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Одаберите слику профила"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Подразумевана икона корисника"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Физичка тастатура"</string>
diff --git a/packages/SettingsLib/res/values-sv/arrays.xml b/packages/SettingsLib/res/values-sv/arrays.xml
index f99a85b..c63465c 100644
--- a/packages/SettingsLib/res/values-sv/arrays.xml
+++ b/packages/SettingsLib/res/values-sv/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Använd systemval (standardinställning)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-ljud"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-ljud"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Använd systemval (standardinställning)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-ljud"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-ljud"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Använd systemval (standardinställning)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 1a8815c..e135d1b 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Väder"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luftkvalitet"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info om rollistan"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Hemstyrning"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Välj en profilbild"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Ikon för standardanvändare"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fysiskt tangentbord"</string>
diff --git a/packages/SettingsLib/res/values-sw/arrays.xml b/packages/SettingsLib/res/values-sw/arrays.xml
index dab4279..53dc6e5 100644
--- a/packages/SettingsLib/res/values-sw/arrays.xml
+++ b/packages/SettingsLib/res/values-sw/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"ramani ya 13"</item>
     <item msgid="8147982633566548515">"ramani ya 14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Tumia Uteuzi wa Mfumo (Chaguomsingi)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Sauti ya <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Sauti ya <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Tumia Uteuzi wa Mfumo (Chaguomsingi)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Sauti ya <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Sauti ya <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Tumia Uteuzi wa Mfumo (Chaguomsingi)"</item>
     <item msgid="8003118270854840095">"kHz 44.1"</item>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 5f1d141..a900064 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Hali ya Hewa"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Ubora wa Hewa"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Maelezo ya Wahusika"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Udhibiti wa Vifaa Nyumbani"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Chagua picha ya wasifu"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Aikoni chaguomsingi ya mtumiaji"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Kibodi halisi"</string>
diff --git a/packages/SettingsLib/res/values-ta/arrays.xml b/packages/SettingsLib/res/values-ta/arrays.xml
index a0f1fa6..957bd61 100644
--- a/packages/SettingsLib/res/values-ta/arrays.xml
+++ b/packages/SettingsLib/res/values-ta/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"சாதனத் தேர்வைப் பயன்படுத்து (இயல்பு)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ஆடியோ"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ஆடியோ"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"சாதனத் தேர்வைப் பயன்படுத்து (இயல்பு)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ஆடியோ"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ஆடியோ"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"சாதனத் தேர்வைப் பயன்படுத்து (இயல்பு)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index b8c37b0..645278c 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"வானிலை"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"காற்றின் தரம்"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"அலைபரப்புத் தகவல்"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ஹோம் கன்ட்ரோல்கள்"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"சுயவிவரப் படத்தைத் தேர்வுசெய்யுங்கள்"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"இயல்புநிலைப் பயனர் ஐகான்"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"கீபோர்டு"</string>
diff --git a/packages/SettingsLib/res/values-te/arrays.xml b/packages/SettingsLib/res/values-te/arrays.xml
index 18a2758..d4361e5 100644
--- a/packages/SettingsLib/res/values-te/arrays.xml
+++ b/packages/SettingsLib/res/values-te/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"సిస్టమ్ ఎంపికను ఉపయోగించండి (ఆటోమేటిక్)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ఆడియో"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ఆడియో"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"సిస్టమ్ ఎంపికను ఉపయోగించండి (ఆటోమేటిక్)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ఆడియో"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ఆడియో"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"సిస్టమ్ ఎంపికను ఉపయోగించండి (ఆటోమేటిక్)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 504c827..de3f390 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"గాలి క్వాలిటీ"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"కాస్ట్ సమాచారం"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"హోమ్ కంట్రోల్స్"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"స్మార్ట్‌స్పేస్"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"ప్రొఫైల్ ఫోటోను ఎంచుకోండి"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ఆటోమేటిక్ సెట్టింగ్ యూజర్ చిహ్నం"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"భౌతిక కీబోర్డ్"</string>
diff --git a/packages/SettingsLib/res/values-th/arrays.xml b/packages/SettingsLib/res/values-th/arrays.xml
index 04a5f4d..782e95e 100644
--- a/packages/SettingsLib/res/values-th/arrays.xml
+++ b/packages/SettingsLib/res/values-th/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"ใช้การเลือกของระบบ (ค่าเริ่มต้น)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"เสียง <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"เสียง <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"ใช้การเลือกของระบบ (ค่าเริ่มต้น)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"เสียง <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"เสียง <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"ใช้การเลือกของระบบ (ค่าเริ่มต้น)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index f33630d..dfb87a8 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"สภาพอากาศ"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"คุณภาพอากาศ"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ข้อมูลแคสต์"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ระบบควบคุมอุปกรณ์ในบ้าน"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"เลือกรูปโปรไฟล์"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ไอคอนผู้ใช้เริ่มต้น"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"แป้นพิมพ์จริง"</string>
diff --git a/packages/SettingsLib/res/values-tl/arrays.xml b/packages/SettingsLib/res/values-tl/arrays.xml
index 59cb1f3..19d3423 100644
--- a/packages/SettingsLib/res/values-tl/arrays.xml
+++ b/packages/SettingsLib/res/values-tl/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Gamitin ang Pagpili ng System (Default)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> na audio"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> na audio"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Gamitin ang Pagpili ng System (Default)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> na audio"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> na audio"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Gamitin ang Pagpili ng System (Default)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index f95f0e50..fdaece1 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Lagay ng Panahon"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kalidad ng Hangin"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Impormasyon ng Cast"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Home Controls"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Pumili ng larawan sa profile"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Icon ng default na user"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Pisikal na keyboard"</string>
diff --git a/packages/SettingsLib/res/values-tr/arrays.xml b/packages/SettingsLib/res/values-tr/arrays.xml
index 5ed35fa..37891ae 100644
--- a/packages/SettingsLib/res/values-tr/arrays.xml
+++ b/packages/SettingsLib/res/values-tr/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Sistem Seçimini Kullan (Varsayılan)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ses"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ses"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Sistem Seçimini Kullan (Varsayılan)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ses"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ses"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Sistem Seçimini Kullan (Varsayılan)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 2fa2bd1..a2eb0e2 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Hava durumu"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Hava Kalitesi"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Yayın Bilgisi"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Ev Kontrolleri"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Profil fotoğrafı seçin"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Varsayılan kullanıcı simgesi"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Fiziksel klavye"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 9be0855..63d7118 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Погода"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Якість повітря"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Акторський склад"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Автоматизація дому"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Виберіть зображення профілю"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Значок користувача за умовчанням"</string>
diff --git a/packages/SettingsLib/res/values-ur/arrays.xml b/packages/SettingsLib/res/values-ur/arrays.xml
index d097458..db9941e 100644
--- a/packages/SettingsLib/res/values-ur/arrays.xml
+++ b/packages/SettingsLib/res/values-ur/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"سسٹم انتخاب کا استعمال کریں (ڈیفالٹ)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> آڈیو"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> آڈیو"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"سسٹم انتخاب کا استعمال کریں (ڈیفالٹ)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> آڈیو"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> آڈیو"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"سسٹم انتخاب کا استعمال کریں (ڈیفالٹ)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 8eb9a11..2de47ec 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -661,6 +661,7 @@
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"ہوا کا معیار"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"کاسٹ کرنے کی معلومات"</string>
     <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ہوم کنٹرولز"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"پروفائل کی تصویر منتخب کریں"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"ڈیفالٹ صارف کا آئیکن"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"فزیکل کی بورڈ"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index a5c8683..707a54e 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -660,7 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Ob-havo"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Havo sifati"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Translatsiya axboroti"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Uy boshqaruvi"</string>
+    <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
     <skip />
     <string name="avatar_picker_title" msgid="8492884172713170652">"Profil rasmini tanlash"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Foydalanuvchining standart belgisi"</string>
diff --git a/packages/SettingsLib/res/values-vi/arrays.xml b/packages/SettingsLib/res/values-vi/arrays.xml
index 31867e2..ee599d6 100644
--- a/packages/SettingsLib/res/values-vi/arrays.xml
+++ b/packages/SettingsLib/res/values-vi/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Sử dụng lựa chọn của hệ thống (Mặc định)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"Âm thanh <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="2908219194098827570">"Âm thanh <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Sử dụng lựa chọn của hệ thống (Mặc định)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"Âm thanh <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+    <item msgid="3517061573669307965">"Âm thanh <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Sử dụng lựa chọn của hệ thống (Mặc định)"</item>
     <item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 45a0465..82eb63d 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Thời tiết"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Chất lượng không khí"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Thông tin về dàn nghệ sĩ"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Điều khiển nhà"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Chọn một ảnh hồ sơ"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Biểu tượng người dùng mặc định"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Bàn phím thực"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/arrays.xml b/packages/SettingsLib/res/values-zh-rCN/arrays.xml
index 973d7d0..2a85d31 100644
--- a/packages/SettingsLib/res/values-zh-rCN/arrays.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"使用系统选择(默认)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音频"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音频"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"使用系统选择(默认)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音频"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音频"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"使用系统选择(默认)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 2bc8a30..9f8007b 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"天气"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"空气质量"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"投放信息"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"家居控制"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"SmartSpace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"选择个人资料照片"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"默认用户图标"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"实体键盘"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/arrays.xml b/packages/SettingsLib/res/values-zh-rHK/arrays.xml
index 87f3825..a84f0e2 100644
--- a/packages/SettingsLib/res/values-zh-rHK/arrays.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"使用系統選擇 (預設)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音訊"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音訊"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"使用系統選擇 (預設)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音訊"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音訊"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"使用系統選擇 (預設)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 114bc64..cc80ebc 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"天氣"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"空氣質素"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"投放資料"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"智能家居"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"選擇個人檔案相片"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"預設使用者圖示"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"實體鍵盤"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/arrays.xml b/packages/SettingsLib/res/values-zh-rTW/arrays.xml
index 529287f..66aaa56b 100644
--- a/packages/SettingsLib/res/values-zh-rTW/arrays.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"map13"</item>
     <item msgid="8147982633566548515">"map14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"系統自動選擇 (預設)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音訊"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音訊"</item>
+    <item msgid="3825367753087348007">"LDAC"</item>
+    <item msgid="328951785723550863">"LC3"</item>
+    <item msgid="506175145534048710">"Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"系統自動選擇 (預設)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音訊"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音訊"</item>
+    <item msgid="2553206901068987657">"LDAC"</item>
+    <item msgid="3940992993241040716">"LC3"</item>
+    <item msgid="7940970833006181407">"Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"系統自動選擇 (預設)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index a7ae213..46aaef8 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"天氣"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"空氣品質"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"演出者資訊"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"居家控制系統"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"智慧空間"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"選擇個人資料相片"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"預設使用者圖示"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"實體鍵盤"</string>
diff --git a/packages/SettingsLib/res/values-zu/arrays.xml b/packages/SettingsLib/res/values-zu/arrays.xml
index 59ead86..0494f1c 100644
--- a/packages/SettingsLib/res/values-zu/arrays.xml
+++ b/packages/SettingsLib/res/values-zu/arrays.xml
@@ -85,10 +85,26 @@
     <item msgid="7073042887003102964">"Imephu13"</item>
     <item msgid="8147982633566548515">"Imephu14"</item>
   </string-array>
-    <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
-    <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
-    <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+  <string-array name="bluetooth_a2dp_codec_titles">
+    <item msgid="2494959071796102843">"Sebenzisa ukukhetha kwesistimu (Okuzenzakalelayo)"</item>
+    <item msgid="4055460186095649420">"SBC"</item>
+    <item msgid="720249083677397051">"I-AAC"</item>
+    <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> umsindo"</item>
+    <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> umsindo"</item>
+    <item msgid="3825367753087348007">"I-LDAC"</item>
+    <item msgid="328951785723550863">"I-LC3"</item>
+    <item msgid="506175145534048710">"I-Opus"</item>
+  </string-array>
+  <string-array name="bluetooth_a2dp_codec_summaries">
+    <item msgid="8868109554557331312">"Sebenzisa ukukhetha kwesistimu (Okuzenzakalelayo)"</item>
+    <item msgid="9024885861221697796">"SBC"</item>
+    <item msgid="4688890470703790013">"I-AAC"</item>
+    <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> umsindo"</item>
+    <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> umsindo"</item>
+    <item msgid="2553206901068987657">"I-LDAC"</item>
+    <item msgid="3940992993241040716">"I-LC3"</item>
+    <item msgid="7940970833006181407">"I-Opus"</item>
+  </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
     <item msgid="926809261293414607">"Sebenzisa ukukhetha kwesistimu (Okuzenzakalelayo)"</item>
     <item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index e037d12..e788357 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -660,8 +660,8 @@
     <string name="dream_complication_title_weather" msgid="598609151677172783">"Isimo sezulu"</string>
     <string name="dream_complication_title_aqi" msgid="4587552608957834110">"Ikhwalithi Yomoya"</string>
     <string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Ulwazi Lokusakaza"</string>
-    <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
-    <skip />
+    <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Izilawuli Zasekhaya"</string>
+    <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"I-Smartspace"</string>
     <string name="avatar_picker_title" msgid="8492884172713170652">"Khetha isithombe sephrofayela"</string>
     <string name="default_user_icon_description" msgid="6554047177298972638">"Isithonjana somsebenzisi sokuzenzakalelayo"</string>
     <string name="physical_keyboard_title" msgid="4811935435315835220">"Ikhibhodi ephathekayo"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index b879223..06d7bb4 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -488,15 +488,15 @@
 
     <!-- Default speech rate choices -->
     <string-array name="tts_rate_entries">
-        <item>Very slow</item>
-        <item>Slow</item>
-        <item>Normal</item>
-        <item>Fast</item>
-        <item>Faster</item>
-        <item>Very fast</item>
-        <item>Rapid</item>
-        <item>Very rapid</item>
-        <item>Fastest</item>
+        <item>60%</item>
+        <item>80%</item>
+        <item>100%</item>
+        <item>150%</item>
+        <item>200%</item>
+        <item>250%</item>
+        <item>300%</item>
+        <item>350%</item>
+        <item>400%</item>
     </string-array>
     <!-- Do not translate. -->
     <string-array name="tts_rate_values">
@@ -962,6 +962,9 @@
     <!-- UI debug setting: enable freeform window support summary [CHAR LIMIT=150] -->
     <string name="enable_freeform_support_summary">Enable support for experimental freeform windows.</string>
 
+    <!-- UI debug setting: enable desktop mode [CHAR LIMIT=25] -->
+    <string name="desktop_mode">Desktop mode</string>
+
     <!-- Local (desktop) backup password menu title [CHAR LIMIT=25] -->
     <string name="local_backup_password_title">Desktop backup password</string>
     <!-- Summary text of the "local backup password" setting when the user has not supplied a password -->
@@ -1122,7 +1125,7 @@
     <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
     <string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> left until full</string>
     <!-- [CHAR_LIMIT=80] Label for battery level chart when charge been limited -->
-    <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging temporarily limited</string>
+    <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging is paused</string>
 
     <!-- Battery Info screen. Value for a status item.  Used for diagnostic info screens, precise translation isn't needed -->
     <string name="battery_info_status_unknown">Unknown</string>
@@ -1135,7 +1138,7 @@
     <!-- [CHAR_LIMIT=20] Battery use screen.  Battery status shown in chart label when charging wirelessly.  -->
     <string name="battery_info_status_charging_wireless">Charging wirelessly</string>
     <!-- [CHAR_LIMIT=20] Battery use screen.  Battery status shown in chart label when the device is dock charging.  -->
-    <string name="battery_info_status_charging_dock">Charging Dock</string>
+    <string name="battery_info_status_charging_dock">Charging</string>
     <!-- Battery Info screen. Value for a status item.  Used for diagnostic info screens, precise translation isn't needed -->
     <string name="battery_info_status_discharging">Not charging</string>
     <!-- Battery Info screen. Value for a status item. A state which device is connected with any charger(e.g. USB, Adapter or Wireless) but not charging yet. Used for diagnostic info screens, precise translation isn't needed -->
@@ -1598,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/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 7fbd100..cd3242a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -297,6 +297,9 @@
                     mCachedDevices.remove(i);
                 }
             }
+
+            // To clear the SetMemberPair flag when the Bluetooth is turning off.
+            mOngoingSetMemberPair = null;
         }
     }
 
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/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index d9262cc..766c036 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -528,7 +528,7 @@
     class RouterManagerCallback implements MediaRouter2Manager.Callback {
 
         @Override
-        public void onRoutesAdded(List<MediaRoute2Info> routes) {
+        public void onRoutesUpdated() {
             refreshDevices();
         }
 
@@ -540,16 +540,6 @@
         }
 
         @Override
-        public void onRoutesChanged(List<MediaRoute2Info> routes) {
-            refreshDevices();
-        }
-
-        @Override
-        public void onRoutesRemoved(List<MediaRoute2Info> routes) {
-            refreshDevices();
-        }
-
-        @Override
         public void onTransferred(RoutingSessionInfo oldSession, RoutingSessionInfo newSession) {
             if (DEBUG) {
                 Log.d(TAG, "onTransferred() oldSession : " + oldSession.getName()
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/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index ee7b7d6..f4af6e8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -112,7 +112,7 @@
         final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
         assertThat(mediaDevice).isNull();
 
-        mInfoMediaManager.mMediaRouterCallback.onRoutesAdded(routes);
+        mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
 
         final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
         assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -135,7 +135,7 @@
         assertThat(mediaDevice).isNull();
 
         mInfoMediaManager.mPackageName = "";
-        mInfoMediaManager.mMediaRouterCallback.onRoutesAdded(routes);
+        mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
 
         final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
         assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -199,7 +199,7 @@
         final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
         assertThat(mediaDevice).isNull();
 
-        mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes);
+        mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
 
         final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
         assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -222,7 +222,7 @@
         assertThat(mediaDevice).isNull();
 
         mInfoMediaManager.mPackageName = "";
-        mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes);
+        mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
 
         final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
         assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -263,7 +263,7 @@
         final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
         assertThat(mediaDevice).isNull();
 
-        mInfoMediaManager.mMediaRouterCallback.onRoutesRemoved(routes);
+        mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
 
         final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
         assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -286,7 +286,7 @@
         assertThat(mediaDevice).isNull();
 
         mInfoMediaManager.mPackageName = "";
-        mInfoMediaManager.mMediaRouterCallback.onRoutesRemoved(routes);
+        mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
 
         final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
         assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
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/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 42b992f..5aae1ce 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -182,6 +182,7 @@
         Settings.Secure.PEOPLE_STRIP,
         Settings.Secure.MEDIA_CONTROLS_RESUME,
         Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
+        Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
         Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
         Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
         Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
@@ -212,6 +213,8 @@
         Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
         Settings.Secure.WEAR_TALKBACK_ENABLED,
         Settings.Secure.HBM_SETTING_KEY,
-        Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED
+        Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED,
+        Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_TRIGGER_HINTS_ENABLED,
+        Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_KEYBOARD_SHIFT_ENABLED,
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 76209da..f0915f8 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -337,9 +337,6 @@
         VALIDATORS.put(Global.Wearable.CHARGING_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.BEDTIME_MODE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
-                Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_MAX_RESET_COUNT,
-                NON_NEGATIVE_INTEGER_VALIDATOR);
-        VALIDATORS.put(
                 Global.Wearable.EARLY_UPDATES_STATUS,
                 new DiscreteValueValidator(
                         new String[] {
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 14b5855..67ea024 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -277,6 +277,7 @@
         VALIDATORS.put(Secure.PEOPLE_STRIP, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.MEDIA_CONTROLS_RESUME, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.MEDIA_CONTROLS_RECOMMENDATION, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.MEDIA_CONTROLS_LOCK_SCREEN, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
                 new InclusiveIntegerRangeValidator(
                         Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
@@ -346,5 +347,9 @@
         VALIDATORS.put(Secure.WEAR_TALKBACK_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.HBM_SETTING_KEY, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Secure.ACCESSIBILITY_SOFTWARE_CURSOR_TRIGGER_HINTS_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Secure.ACCESSIBILITY_SOFTWARE_CURSOR_KEYBOARD_SHIFT_ENABLED, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index c3b645e..a2ffcf3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1828,6 +1828,12 @@
         dumpSetting(s, p,
                 Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED,
                 SecureSettingsProto.Accessibility.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED);
+        dumpSetting(s, p,
+                Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_TRIGGER_HINTS_ENABLED,
+                SecureSettingsProto.Accessibility.SoftwareCursorSettings.TRIGGER_HINTS_ENABLED);
+        dumpSetting(s, p,
+                Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_KEYBOARD_SHIFT_ENABLED,
+                SecureSettingsProto.Accessibility.SoftwareCursorSettings.KEYBOARD_SHIFT_ENABLED);
         p.end(accessibilityToken);
 
         final long adaptiveSleepToken = p.start(SecureSettingsProto.ADAPTIVE_SLEEP);
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/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 874e570..ba7a9bc 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -98,6 +98,7 @@
                     Settings.System.VOLUME_VOICE, // deprecated since API 2?
                     Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
                     Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
+                    Settings.System.DESKTOP_MODE, // developer setting for internal prototyping
                     Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities
                     Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities
                     Settings.System.SCREEN_BRIGHTNESS_FLOAT,
@@ -656,7 +657,6 @@
                     Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
                     Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
                     Settings.Global.Wearable.BEDTIME_MODE,
-                    Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_MAX_RESET_COUNT,
                     Settings.Global.Wearable.EARLY_UPDATES_STATUS);
 
     private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
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/Android.bp b/packages/SystemUI/Android.bp
index 634df39..a25f567 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -238,6 +238,9 @@
         "com.android.systemui",
     ],
     plugins: ["dagger2-compiler"],
+    lint: {
+        test: true,
+    },
 }
 
 // Opt-in config for optimizing the SystemUI target using R8.
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/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index cc7d23e..dc2c6356 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -165,6 +165,8 @@
          * @param includeFadeIn true if the animator should also fade in the view and child views.
          * @param fadeInInterpolator the interpolator to use when fading in the view. Unused if
          *     [includeFadeIn] is false.
+         * @param onAnimationEnd an optional runnable that will be run once the animation
+         *    finishes successfully. Will not be run if the animation is cancelled.
          */
         @JvmOverloads
         fun animateAddition(
@@ -174,7 +176,8 @@
             duration: Long = DEFAULT_DURATION,
             includeMargins: Boolean = false,
             includeFadeIn: Boolean = false,
-            fadeInInterpolator: Interpolator = DEFAULT_FADE_IN_INTERPOLATOR
+            fadeInInterpolator: Interpolator = DEFAULT_FADE_IN_INTERPOLATOR,
+            onAnimationEnd: Runnable? = null,
         ): Boolean {
             if (
                 occupiesSpace(
@@ -193,7 +196,8 @@
                     origin,
                     interpolator,
                     duration,
-                    ignorePreviousValues = !includeMargins
+                    ignorePreviousValues = !includeMargins,
+                    onAnimationEnd,
                 )
             addListener(rootView, listener, recursive = true)
 
@@ -246,14 +250,16 @@
             origin: Hotspot,
             interpolator: Interpolator,
             duration: Long,
-            ignorePreviousValues: Boolean
+            ignorePreviousValues: Boolean,
+            onAnimationEnd: Runnable? = null,
         ): View.OnLayoutChangeListener {
             return createListener(
                 interpolator,
                 duration,
                 ephemeral = true,
                 origin = origin,
-                ignorePreviousValues = ignorePreviousValues
+                ignorePreviousValues = ignorePreviousValues,
+                onAnimationEnd,
             )
         }
 
@@ -272,7 +278,8 @@
             duration: Long,
             ephemeral: Boolean,
             origin: Hotspot? = null,
-            ignorePreviousValues: Boolean = false
+            ignorePreviousValues: Boolean = false,
+            onAnimationEnd: Runnable? = null,
         ): View.OnLayoutChangeListener {
             return object : View.OnLayoutChangeListener {
                 override fun onLayoutChange(
@@ -340,7 +347,8 @@
                             endValues,
                             interpolator,
                             duration,
-                            ephemeral
+                            ephemeral,
+                            onAnimationEnd,
                         )
                     }
                 }
@@ -903,7 +911,8 @@
             endValues: Map<Bound, Int>,
             interpolator: Interpolator,
             duration: Long,
-            ephemeral: Boolean
+            ephemeral: Boolean,
+            onAnimationEnd: Runnable? = null,
         ) {
             val propertyValuesHolders =
                 buildList {
@@ -941,6 +950,9 @@
                             // listener.
                             recursivelyRemoveListener(view)
                         }
+                        if (!cancelled) {
+                            onAnimationEnd?.run()
+                        }
                     }
 
                     override fun onAnimationCancel(animation: Animator?) {
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt
new file mode 100644
index 0000000..925fae0e
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.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.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+@Suppress("UnstableApiUsage")
+class BindServiceViaContextDetector : Detector(), SourceCodeScanner {
+
+    override fun getApplicableMethodNames(): List<String> {
+        return listOf("bindService", "bindServiceAsUser", "unbindService")
+    }
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+        if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) {
+            context.report(
+                    ISSUE,
+                    method,
+                    context.getNameLocation(node),
+                    "Binding or unbinding services are synchronous calls, please make " +
+                            "sure you're on a @Background Executor."
+            )
+        }
+    }
+
+    companion object {
+        @JvmField
+        val ISSUE: Issue =
+            Issue.create(
+                id = "BindServiceViaContextDetector",
+                briefDescription = "Service bound/unbound via Context, please make sure " +
+                        "you're on a background thread.",
+                explanation =
+                "Binding or unbinding services are synchronous calls to ActivityManager, " +
+                        "they usually take multiple milliseconds to complete and will make" +
+                        "the caller drop frames. Make sure you're on a @Background Executor.",
+                category = Category.PERFORMANCE,
+                priority = 8,
+                severity = Severity.WARNING,
+                implementation =
+                Implementation(BindServiceViaContextDetector::class.java, Scope.JAVA_FILE_SCOPE)
+            )
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt
new file mode 100644
index 0000000..a629eee
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+@Suppress("UnstableApiUsage")
+class GetMainLooperViaContextDetector : Detector(), SourceCodeScanner {
+
+    override fun getApplicableMethodNames(): List<String> {
+        return listOf("getMainThreadHandler", "getMainLooper", "getMainExecutor")
+    }
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+        if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) {
+            context.report(
+                    ISSUE,
+                    method,
+                    context.getNameLocation(node),
+                    "Please inject a @Main Executor instead."
+            )
+        }
+    }
+
+    companion object {
+        @JvmField
+        val ISSUE: Issue =
+                Issue.create(
+                        id = "GetMainLooperViaContextDetector",
+                        briefDescription = "Please use idiomatic SystemUI executors, injecting " +
+                                "them via Dagger.",
+                        explanation = "Injecting the @Main Executor is preferred in order to make" +
+                                "dependencies explicit and increase testability. It's much " +
+                                "easier to pass a FakeExecutor on your test ctor than to " +
+                                "deal with loopers in unit tests.",
+                        category = Category.LINT,
+                        priority = 8,
+                        severity = Severity.WARNING,
+                        implementation = Implementation(GetMainLooperViaContextDetector::class.java,
+                                Scope.JAVA_FILE_SCOPE)
+                )
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
new file mode 100644
index 0000000..b72d03d
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+class RegisterReceiverViaContextDetector : Detector(), SourceCodeScanner {
+
+    override fun getApplicableMethodNames(): List<String> {
+        return listOf("registerReceiver", "registerReceiverAsUser", "registerReceiverForAllUsers")
+    }
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+        if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) {
+            context.report(
+                    ISSUE,
+                    method,
+                    context.getNameLocation(node),
+                    "BroadcastReceivers should be registered via BroadcastDispatcher."
+            )
+        }
+    }
+
+    companion object {
+        @JvmField
+        val ISSUE: Issue =
+            Issue.create(
+                    id = "RegisterReceiverViaContextDetector",
+                    briefDescription = "Broadcast registrations via Context are blocking " +
+                            "calls. Please use BroadcastDispatcher.",
+                    explanation =
+                    "Context#registerReceiver is a blocking call to the system server, " +
+                            "making it very likely that you'll drop a frame. Please use " +
+                            "BroadcastDispatcher instead (or move this call to a " +
+                            "@Background Executor.)",
+                    category = Category.PERFORMANCE,
+                    priority = 8,
+                    severity = Severity.WARNING,
+                    implementation = Implementation(RegisterReceiverViaContextDetector::class.java,
+                            Scope.JAVA_FILE_SCOPE)
+            )
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
new file mode 100644
index 0000000..a584894
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
@@ -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.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiField
+import org.jetbrains.uast.UReferenceExpression
+
+@Suppress("UnstableApiUsage")
+class SoftwareBitmapDetector : Detector(), SourceCodeScanner {
+
+    override fun getApplicableReferenceNames(): List<String> {
+        return mutableListOf("ALPHA_8", "RGB_565", "ARGB_8888", "RGBA_F16", "RGBA_1010102")
+    }
+
+    override fun visitReference(
+            context: JavaContext,
+            reference: UReferenceExpression,
+            referenced: PsiElement
+    ) {
+
+        val evaluator = context.evaluator
+        if (evaluator.isMemberInClass(referenced as? PsiField, "android.graphics.Bitmap.Config")) {
+            context.report(
+                    ISSUE,
+                    referenced,
+                    context.getNameLocation(referenced),
+                    "Usage of Config.HARDWARE is highly encouraged."
+            )
+        }
+    }
+
+    companion object {
+        @JvmField
+        val ISSUE: Issue =
+            Issue.create(
+                id = "SoftwareBitmapDetector",
+                briefDescription = "Software bitmap detected. Please use Config.HARDWARE instead.",
+                explanation =
+                "Software bitmaps occupy twice as much memory, when compared to Config.HARDWARE. " +
+                        "In case you need to manipulate the pixels, please consider to either use" +
+                        "a shader (encouraged), or a short lived software bitmap.",
+                category = Category.PERFORMANCE,
+                priority = 8,
+                severity = Severity.WARNING,
+                implementation = Implementation(SoftwareBitmapDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE)
+            )
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 397a110..c7c73d3 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -27,7 +27,13 @@
 class SystemUIIssueRegistry : IssueRegistry() {
 
     override val issues: List<Issue>
-        get() = listOf(BroadcastSentViaContextDetector.ISSUE)
+        get() = listOf(
+                BindServiceViaContextDetector.ISSUE,
+                BroadcastSentViaContextDetector.ISSUE,
+                GetMainLooperViaContextDetector.ISSUE,
+                RegisterReceiverViaContextDetector.ISSUE,
+                SoftwareBitmapDetector.ISSUE,
+        )
 
     override val api: Int
         get() = CURRENT_API
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt
new file mode 100644
index 0000000..bf685f7
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class BindServiceViaContextDetectorTest : LintDetectorTest() {
+
+    override fun getDetector(): Detector = BindServiceViaContextDetector()
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+    override fun getIssues(): List<Issue> = listOf(
+            BindServiceViaContextDetector.ISSUE)
+
+    private val explanation = "Binding or unbinding services are synchronous calls"
+
+    @Test
+    fun testBindService() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.Context;
+
+                    public class TestClass1 {
+                        public void bind(Context context) {
+                          Intent intent = new Intent(Intent.ACTION_VIEW);
+                          context.bindService(intent, null, 0);
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(BindServiceViaContextDetector.ISSUE)
+                .run()
+                .expectWarningCount(1)
+                .expectContains(explanation)
+    }
+
+    @Test
+    fun testBindServiceAsUser() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.Context;
+                    import android.os.UserHandle;
+
+                    public class TestClass1 {
+                        public void bind(Context context) {
+                          Intent intent = new Intent(Intent.ACTION_VIEW);
+                          context.bindServiceAsUser(intent, null, 0, UserHandle.ALL);
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(BindServiceViaContextDetector.ISSUE)
+                .run()
+                .expectWarningCount(1)
+                .expectContains(explanation)
+    }
+
+    @Test
+    fun testUnbindService() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.Context;
+                    import android.content.ServiceConnection;
+
+                    public class TestClass1 {
+                        public void unbind(Context context, ServiceConnection connection) {
+                          context.unbindService(connection);
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(BindServiceViaContextDetector.ISSUE)
+                .run()
+                .expectWarningCount(1)
+                .expectContains(explanation)
+    }
+
+    private val contextStub: TestFile = java(
+            """
+        package android.content;
+        import android.os.UserHandle;
+
+        public class Context {
+            public void bindService(Intent intent) {};
+            public void bindServiceAsUser(Intent intent, ServiceConnection connection, int flags,
+                                          UserHandle userHandle) {};
+            public void unbindService(ServiceConnection connection) {};
+        }
+        """
+    )
+
+    private val serviceConnectionStub: TestFile = java(
+            """
+        package android.content;
+
+        public class ServiceConnection {}
+        """
+    )
+
+    private val userHandleStub: TestFile = java(
+            """
+        package android.os;
+
+        public enum UserHandle {
+            ALL
+        }
+        """
+    )
+
+    private val stubs = arrayOf(contextStub, serviceConnectionStub, userHandleStub)
+}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt
new file mode 100644
index 0000000..ec761cd
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class GetMainLooperViaContextDetectorTest : LintDetectorTest() {
+
+    override fun getDetector(): Detector = GetMainLooperViaContextDetector()
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+    override fun getIssues(): List<Issue> = listOf(GetMainLooperViaContextDetector.ISSUE)
+
+    private val explanation = "Please inject a @Main Executor instead."
+
+    @Test
+    fun testGetMainThreadHandler() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.Context;
+                    import android.os.Handler;
+
+                    public class TestClass1 {
+                        public void test(Context context) {
+                          Handler mainThreadHandler = context.getMainThreadHandler();
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(GetMainLooperViaContextDetector.ISSUE)
+                .run()
+                .expectWarningCount(1)
+                .expectContains(explanation)
+    }
+
+    @Test
+    fun testGetMainLooper() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.Context;
+                    import android.os.Looper;
+
+                    public class TestClass1 {
+                        public void test(Context context) {
+                          Looper mainLooper = context.getMainLooper();
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(GetMainLooperViaContextDetector.ISSUE)
+                .run()
+                .expectWarningCount(1)
+                .expectContains(explanation)
+    }
+
+    @Test
+    fun testGetMainExecutor() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.Context;
+                    import java.util.concurrent.Executor;
+
+                    public class TestClass1 {
+                        public void test(Context context) {
+                          Executor mainExecutor = context.getMainExecutor();
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(GetMainLooperViaContextDetector.ISSUE)
+                .run()
+                .expectWarningCount(1)
+                .expectContains(explanation)
+    }
+
+    private val contextStub: TestFile = java(
+            """
+        package android.content;
+        import android.os.Handler;import android.os.Looper;import java.util.concurrent.Executor;
+
+        public class Context {
+            public Looper getMainLooper() { return null; };
+            public Executor getMainExecutor() { return null; };
+            public Handler getMainThreadHandler() { return null; };
+        }
+        """
+    )
+
+    private val looperStub: TestFile = java(
+            """
+        package android.os;
+
+        public class Looper {}
+        """
+    )
+
+    private val handlerStub: TestFile = java(
+            """
+        package android.os;
+
+        public class Handler {}
+        """
+    )
+
+    private val stubs = arrayOf(contextStub, looperStub, handlerStub)
+}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
new file mode 100644
index 0000000..76c0519
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class RegisterReceiverViaContextDetectorTest : LintDetectorTest() {
+
+    override fun getDetector(): Detector = RegisterReceiverViaContextDetector()
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+    override fun getIssues(): List<Issue> = listOf(
+            RegisterReceiverViaContextDetector.ISSUE)
+
+    private val explanation = "BroadcastReceivers should be registered via BroadcastDispatcher."
+
+    @Test
+    fun testRegisterReceiver() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.BroadcastReceiver;
+                    import android.content.Context;
+                    import android.content.IntentFilter;
+
+                    public class TestClass1 {
+                        public void bind(Context context, BroadcastReceiver receiver,
+                            IntentFilter filter) {
+                          context.registerReceiver(receiver, filter, 0);
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(RegisterReceiverViaContextDetector.ISSUE)
+                .run()
+                .expectWarningCount(1)
+                .expectContains(explanation)
+    }
+
+    @Test
+    fun testRegisterReceiverAsUser() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.BroadcastReceiver;
+                    import android.content.Context;
+                    import android.content.IntentFilter;
+                    import android.os.Handler;
+                    import android.os.UserHandle;
+
+                    public class TestClass1 {
+                        public void bind(Context context, BroadcastReceiver receiver,
+                            IntentFilter filter, Handler handler) {
+                          context.registerReceiverAsUser(receiver, UserHandle.ALL, filter,
+                            "permission", handler);
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(RegisterReceiverViaContextDetector.ISSUE)
+                .run()
+                .expectWarningCount(1)
+                .expectContains(explanation)
+    }
+
+    @Test
+    fun testRegisterReceiverForAllUsers() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    package test.pkg;
+                    import android.content.BroadcastReceiver;
+                    import android.content.Context;
+                    import android.content.IntentFilter;
+                    import android.os.Handler;
+                    import android.os.UserHandle;
+
+                    public class TestClass1 {
+                        public void bind(Context context, BroadcastReceiver receiver,
+                            IntentFilter filter, Handler handler) {
+                          context.registerReceiverForAllUsers(receiver, filter, "permission",
+                            handler);
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(RegisterReceiverViaContextDetector.ISSUE)
+                .run()
+                .expectWarningCount(1)
+                .expectContains(explanation)
+    }
+
+    private val contextStub: TestFile = java(
+            """
+        package android.content;
+        import android.os.Handler;
+        import android.os.UserHandle;
+
+        public class Context {
+            public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+                int flags) {};
+            public void registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+                IntentFilter filter, String broadcastPermission, Handler scheduler) {};
+            public void registerReceiverForAllUsers(BroadcastReceiver receiver, IntentFilter filter,
+                String broadcastPermission, Handler scheduler) {};
+        }
+        """
+    )
+
+    private val broadcastReceiverStub: TestFile = java(
+            """
+        package android.content;
+
+        public class BroadcastReceiver {}
+        """
+    )
+
+    private val intentFilterStub: TestFile = java(
+            """
+        package android.content;
+
+        public class IntentFilter {}
+        """
+    )
+
+    private val handlerStub: TestFile = java(
+            """
+        package android.os;
+
+        public class Handler {}
+        """
+    )
+
+    private val userHandleStub: TestFile = java(
+            """
+        package android.os;
+
+        public enum UserHandle {
+            ALL
+        }
+        """
+    )
+
+    private val stubs = arrayOf(contextStub, broadcastReceiverStub, intentFilterStub, handlerStub,
+            userHandleStub)
+}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
new file mode 100644
index 0000000..890f2b8
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+@Suppress("UnstableApiUsage")
+class SoftwareBitmapDetectorTest : LintDetectorTest() {
+
+    override fun getDetector(): Detector = SoftwareBitmapDetector()
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+    override fun getIssues(): List<Issue> = listOf(SoftwareBitmapDetector.ISSUE)
+
+    private val explanation = "Usage of Config.HARDWARE is highly encouraged."
+
+    @Test
+    fun testSoftwareBitmap() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    import android.graphics.Bitmap;
+
+                    public class TestClass1 {
+                        public void test() {
+                          Bitmap.createBitmap(300, 300, Bitmap.Config.RGB_565);
+                          Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(SoftwareBitmapDetector.ISSUE)
+                .run()
+                .expectWarningCount(2)
+                .expectContains(explanation)
+    }
+
+    @Test
+    fun testHardwareBitmap() {
+        lint().files(
+                TestFiles.java(
+                        """
+                    import android.graphics.Bitmap;
+
+                    public class TestClass1 {
+                        public void test() {
+                          Bitmap.createBitmap(300, 300, Bitmap.Config.HARDWARE);
+                        }
+                    }
+                """
+                ).indented(),
+                *stubs)
+                .issues(SoftwareBitmapDetector.ISSUE)
+                .run()
+                .expectWarningCount(0)
+    }
+
+    private val bitmapStub: TestFile = java(
+            """
+        package android.graphics;
+
+        public class Bitmap {
+            public enum Config {
+                ARGB_8888,
+                RGB_565,
+                HARDWARE
+            }
+            public static Bitmap createBitmap(int width, int height, Config config) {
+                return null;
+            }
+        }
+        """
+    )
+
+    private val stubs = arrayOf(bitmapStub)
+}
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index 40218de..325ede6 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -30,6 +30,7 @@
     ],
 
     static_libs: [
+        "SystemUI-core",
         "SystemUIComposeCore",
 
         "androidx.compose.runtime_runtime",
diff --git a/packages/SystemUI/compose/features/AndroidManifest.xml b/packages/SystemUI/compose/features/AndroidManifest.xml
index 0aea99d..eada40e 100644
--- a/packages/SystemUI/compose/features/AndroidManifest.xml
+++ b/packages/SystemUI/compose/features/AndroidManifest.xml
@@ -16,7 +16,38 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+
     package="com.android.systemui.compose.features">
-
-
+    <application
+        android:name="android.app.Application"
+        android:appComponentFactory="androidx.core.app.AppComponentFactory"
+        tools:replace="android:name,android:appComponentFactory">
+        <!-- Disable providers from SystemUI -->
+        <provider android:name="com.android.systemui.keyguard.KeyguardSliceProvider"
+            android:authorities="com.android.systemui.test.keyguard.disabled"
+            android:enabled="false"
+            tools:replace="android:authorities"
+            tools:node="remove" />
+        <provider android:name="com.google.android.systemui.keyguard.KeyguardSliceProviderGoogle"
+            android:authorities="com.android.systemui.test.keyguard.disabled"
+            android:enabled="false"
+            tools:replace="android:authorities"
+            tools:node="remove" />
+        <provider android:name="com.android.keyguard.clock.ClockOptionsProvider"
+            android:authorities="com.android.systemui.test.keyguard.clock.disabled"
+            android:enabled="false"
+            tools:replace="android:authorities"
+            tools:node="remove" />
+        <provider android:name="com.android.systemui.people.PeopleProvider"
+            android:authorities="com.android.systemui.test.people.disabled"
+            android:enabled="false"
+            tools:replace="android:authorities"
+            tools:node="remove" />
+        <provider android:name="androidx.core.content.FileProvider"
+            android:authorities="com.android.systemui.test.fileprovider.disabled"
+            android:enabled="false"
+            tools:replace="android:authorities"
+            tools:node="remove"/>
+    </application>
 </manifest>
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
new file mode 100644
index 0000000..2bf1937
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.people.ui.compose
+
+import android.annotation.StringRes
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Divider
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import kotlinx.coroutines.flow.collect
+
+/**
+ * Compose the screen associated to a [PeopleViewModel].
+ *
+ * @param viewModel the [PeopleViewModel] that should be composed.
+ * @param onResult the callback called with the result of this screen. Callers should usually finish
+ * the Activity/Fragment/View hosting this Composable once a result is available.
+ */
+@Composable
+fun PeopleScreen(
+    viewModel: PeopleViewModel,
+    onResult: (PeopleViewModel.Result) -> Unit,
+) {
+    val priorityTiles by viewModel.priorityTiles.collectAsState()
+    val recentTiles by viewModel.recentTiles.collectAsState()
+
+    // Make sure to refresh the tiles/conversations when the lifecycle is resumed, so that it
+    // updates them when going back to the Activity after leaving it.
+    val lifecycleOwner = LocalLifecycleOwner.current
+    LaunchedEffect(lifecycleOwner, viewModel) {
+        lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+            viewModel.onTileRefreshRequested()
+        }
+    }
+
+    // Call [onResult] this activity when the ViewModel tells us so.
+    LaunchedEffect(viewModel.result) {
+        viewModel.result.collect { result ->
+            if (result != null) {
+                viewModel.clearResult()
+                onResult(result)
+            }
+        }
+    }
+
+    // Make sure to use the Android colors and not the default Material3 colors to have the exact
+    // same colors as the View implementation.
+    val androidColors = LocalAndroidColorScheme.current
+    Surface(
+        color = androidColors.colorBackground,
+        contentColor = androidColors.textColorPrimary,
+        modifier = Modifier.fillMaxSize(),
+    ) {
+        if (priorityTiles.isNotEmpty() || recentTiles.isNotEmpty()) {
+            PeopleScreenWithConversations(priorityTiles, recentTiles, viewModel::onTileClicked)
+        } else {
+            PeopleScreenEmpty(viewModel::onUserJourneyCancelled)
+        }
+    }
+}
+
+@Composable
+private fun PeopleScreenWithConversations(
+    priorityTiles: List<PeopleTileViewModel>,
+    recentTiles: List<PeopleTileViewModel>,
+    onTileClicked: (PeopleTileViewModel) -> Unit,
+) {
+    Column {
+        Column(
+            Modifier.fillMaxWidth().padding(PeopleSpacePadding),
+            horizontalAlignment = Alignment.CenterHorizontally,
+        ) {
+            Text(
+                stringResource(R.string.select_conversation_title),
+                style = MaterialTheme.typography.headlineSmall,
+                textAlign = TextAlign.Center,
+            )
+
+            Spacer(Modifier.height(24.dp))
+
+            Text(
+                stringResource(R.string.select_conversation_text),
+                Modifier.padding(horizontal = 24.dp),
+                style = MaterialTheme.typography.bodyLarge,
+                textAlign = TextAlign.Center,
+            )
+        }
+
+        LazyColumn(
+            Modifier.fillMaxWidth(),
+            contentPadding =
+                PaddingValues(
+                    top = 16.dp,
+                    bottom = PeopleSpacePadding,
+                    start = 8.dp,
+                    end = 8.dp,
+                )
+        ) {
+            ConversationList(R.string.priority_conversations, priorityTiles, onTileClicked)
+            item { Spacer(Modifier.height(35.dp)) }
+            ConversationList(R.string.recent_conversations, recentTiles, onTileClicked)
+        }
+    }
+}
+
+private fun LazyListScope.ConversationList(
+    @StringRes headerTextResource: Int,
+    tiles: List<PeopleTileViewModel>,
+    onTileClicked: (PeopleTileViewModel) -> Unit
+) {
+    item {
+        Text(
+            stringResource(headerTextResource),
+            Modifier.padding(start = 16.dp),
+            style = MaterialTheme.typography.labelLarge,
+            color = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
+        )
+
+        Spacer(Modifier.height(10.dp))
+    }
+
+    tiles.forEachIndexed { index, tile ->
+        if (index > 0) {
+            item {
+                Divider(
+                    color = LocalAndroidColorScheme.current.colorBackground,
+                    thickness = 2.dp,
+                )
+            }
+        }
+
+        item(tile.key.toString()) {
+            Tile(
+                tile,
+                onTileClicked,
+                withTopCornerRadius = index == 0,
+                withBottomCornerRadius = index == tiles.lastIndex,
+            )
+        }
+    }
+}
+
+@Composable
+private fun Tile(
+    tile: PeopleTileViewModel,
+    onTileClicked: (PeopleTileViewModel) -> Unit,
+    withTopCornerRadius: Boolean,
+    withBottomCornerRadius: Boolean,
+) {
+    val androidColors = LocalAndroidColorScheme.current
+    val cornerRadius = dimensionResource(R.dimen.people_space_widget_radius)
+    val topCornerRadius = if (withTopCornerRadius) cornerRadius else 0.dp
+    val bottomCornerRadius = if (withBottomCornerRadius) cornerRadius else 0.dp
+
+    Surface(
+        color = androidColors.colorSurface,
+        contentColor = androidColors.textColorPrimary,
+        shape =
+            RoundedCornerShape(
+                topStart = topCornerRadius,
+                topEnd = topCornerRadius,
+                bottomStart = bottomCornerRadius,
+                bottomEnd = bottomCornerRadius,
+            ),
+    ) {
+        Row(
+            Modifier.fillMaxWidth().clickable { onTileClicked(tile) }.padding(12.dp),
+            verticalAlignment = Alignment.CenterVertically,
+        ) {
+            Image(
+                tile.icon.asImageBitmap(),
+                // TODO(b/238993727): Add a content description.
+                contentDescription = null,
+                Modifier.size(dimensionResource(R.dimen.avatar_size_for_medium)),
+            )
+
+            Text(
+                tile.username ?: "",
+                Modifier.padding(horizontal = 16.dp),
+                style = MaterialTheme.typography.titleLarge,
+            )
+        }
+    }
+}
+
+/** The padding applied to the PeopleSpace screen. */
+internal val PeopleSpacePadding = 24.dp
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt
new file mode 100644
index 0000000..5c9358f
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.people.ui.compose
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import com.android.systemui.R
+import com.android.systemui.compose.theme.LocalAndroidColorScheme
+
+@Composable
+internal fun PeopleScreenEmpty(
+    onGotItClicked: () -> Unit,
+) {
+    Column(
+        Modifier.fillMaxSize().padding(PeopleSpacePadding),
+        horizontalAlignment = Alignment.CenterHorizontally,
+    ) {
+        Text(
+            stringResource(R.string.select_conversation_title),
+            style = MaterialTheme.typography.headlineSmall,
+            textAlign = TextAlign.Center,
+        )
+
+        Spacer(Modifier.height(50.dp))
+
+        Text(
+            stringResource(R.string.no_conversations_text),
+            style = MaterialTheme.typography.bodyLarge,
+            textAlign = TextAlign.Center,
+        )
+
+        Spacer(Modifier.weight(1f))
+        ExampleTile()
+        Spacer(Modifier.weight(1f))
+
+        val androidColors = LocalAndroidColorScheme.current
+        Button(
+            onGotItClicked,
+            Modifier.fillMaxWidth().defaultMinSize(minHeight = 56.dp),
+            colors =
+                ButtonDefaults.buttonColors(
+                    containerColor = androidColors.colorAccentPrimary,
+                    contentColor = androidColors.textColorOnAccent,
+                )
+        ) { Text(stringResource(R.string.got_it)) }
+    }
+}
+
+@Composable
+private fun ExampleTile() {
+    val androidColors = LocalAndroidColorScheme.current
+    Surface(
+        shape = RoundedCornerShape(28.dp),
+        color = androidColors.colorSurface,
+        contentColor = androidColors.textColorPrimary,
+    ) {
+        Row(
+            Modifier.padding(vertical = 20.dp, horizontal = 16.dp),
+            verticalAlignment = Alignment.CenterVertically,
+        ) {
+            Column(horizontalAlignment = Alignment.CenterHorizontally) {
+                // TODO(b/238993727): Add a content description.
+                Image(
+                    painterResource(R.drawable.ic_avatar_with_badge),
+                    contentDescription = null,
+                    Modifier.size(40.dp),
+                )
+                Spacer(Modifier.height(2.dp))
+                Text(
+                    stringResource(R.string.empty_user_name),
+                    style = MaterialTheme.typography.labelMedium,
+                    maxLines = 1,
+                    overflow = TextOverflow.Ellipsis,
+                )
+            }
+
+            Spacer(Modifier.width(24.dp))
+
+            Text(
+                stringResource(R.string.empty_status),
+                style = MaterialTheme.typography.labelMedium,
+                maxLines = 1,
+                overflow = TextOverflow.Ellipsis,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/gallery/Android.bp b/packages/SystemUI/compose/gallery/Android.bp
index 40504dc..b0f5cc1 100644
--- a/packages/SystemUI/compose/gallery/Android.bp
+++ b/packages/SystemUI/compose/gallery/Android.bp
@@ -27,6 +27,7 @@
 
     srcs: [
         "src/**/*.kt",
+        ":SystemUI-tests-utils",
     ],
 
     resource_dirs: [
@@ -45,6 +46,14 @@
         "androidx.navigation_navigation-compose",
 
         "androidx.appcompat_appcompat",
+
+        // TODO(b/240431193): Remove the dependencies and depend on
+        // SystemUI-test-utils directly.
+        "androidx.test.runner",
+        "mockito-target-extended-minus-junit4",
+        "testables",
+        "truth-prebuilt",
+        "androidx.test.uiautomator",
     ],
 
     kotlincflags: ["-Xjvm-default=all"],
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt
index c341867..bb98fb3 100644
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt
@@ -13,7 +13,7 @@
 import androidx.compose.runtime.CompositionLocalProvider
 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.platform.LocalContext
@@ -33,6 +33,28 @@
     val AndroidColors = ChildScreen("android_colors") { AndroidColorsScreen() }
     val ExampleFeature = ChildScreen("example_feature") { ExampleFeatureScreen() }
 
+    val PeopleEmpty =
+        ChildScreen("people_empty") { navController ->
+            EmptyPeopleScreen(onResult = { navController.popBackStack() })
+        }
+    val PeopleFew =
+        ChildScreen("people_few") { navController ->
+            FewPeopleScreen(onResult = { navController.popBackStack() })
+        }
+    val PeopleFull =
+        ChildScreen("people_full") { navController ->
+            FullPeopleScreen(onResult = { navController.popBackStack() })
+        }
+    val People =
+        ParentScreen(
+            "people",
+            mapOf(
+                "Empty" to PeopleEmpty,
+                "Few" to PeopleFew,
+                "Full" to PeopleFull,
+            )
+        )
+
     val Home =
         ParentScreen(
             "home",
@@ -41,20 +63,21 @@
                 "Material colors" to MaterialColors,
                 "Android colors" to AndroidColors,
                 "Example feature" to ExampleFeature,
+                "People" to People,
             )
         )
 }
 
 /** The main content of the app, that shows [GalleryAppScreens.Home] by default. */
 @Composable
-private fun MainContent() {
+private fun MainContent(onControlToggleRequested: () -> Unit) {
     Box(Modifier.fillMaxSize()) {
         val navController = rememberNavController()
         NavHost(
             navController = navController,
             startDestination = GalleryAppScreens.Home.identifier,
         ) {
-            screen(GalleryAppScreens.Home, navController)
+            screen(GalleryAppScreens.Home, navController, onControlToggleRequested)
         }
     }
 }
@@ -69,7 +92,7 @@
     onChangeTheme: () -> Unit,
 ) {
     val systemFontScale = LocalDensity.current.fontScale
-    var fontScale: FontScale by remember {
+    var fontScale: FontScale by rememberSaveable {
         mutableStateOf(
             FontScale.values().firstOrNull { it.scale == systemFontScale } ?: FontScale.Normal
         )
@@ -87,7 +110,7 @@
     }
 
     val systemLayoutDirection = LocalLayoutDirection.current
-    var layoutDirection by remember { mutableStateOf(systemLayoutDirection) }
+    var layoutDirection by rememberSaveable { mutableStateOf(systemLayoutDirection) }
     val onChangeLayoutDirection = {
         layoutDirection =
             when (layoutDirection) {
@@ -105,19 +128,24 @@
                 Modifier.fillMaxSize(),
                 color = MaterialTheme.colorScheme.background,
             ) {
-                Column(Modifier.fillMaxSize().systemBarsPadding().padding(16.dp)) {
-                    ConfigurationControls(
-                        theme,
-                        fontScale,
-                        layoutDirection,
-                        onChangeTheme,
-                        onChangeLayoutDirection,
-                        onChangeFontScale,
-                    )
+                Column(Modifier.fillMaxSize().systemBarsPadding()) {
+                    var showControls by rememberSaveable { mutableStateOf(true) }
 
-                    Spacer(Modifier.height(4.dp))
+                    if (showControls) {
+                        ConfigurationControls(
+                            theme,
+                            fontScale,
+                            layoutDirection,
+                            onChangeTheme,
+                            onChangeLayoutDirection,
+                            onChangeFontScale,
+                            Modifier.padding(horizontal = 16.dp),
+                        )
 
-                    MainContent()
+                        Spacer(Modifier.height(4.dp))
+                    }
+
+                    MainContent(onControlToggleRequested = { showControls = !showControls })
                 }
             }
         }
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/PeopleScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/PeopleScreen.kt
new file mode 100644
index 0000000..2f0df77
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/PeopleScreen.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.compose.gallery
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+import com.android.systemui.people.emptyPeopleSpaceViewModel
+import com.android.systemui.people.fewPeopleSpaceViewModel
+import com.android.systemui.people.fullPeopleSpaceViewModel
+import com.android.systemui.people.ui.compose.PeopleScreen
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+
+@Composable
+fun EmptyPeopleScreen(onResult: (PeopleViewModel.Result) -> Unit) {
+    val context = LocalContext.current.applicationContext
+    val viewModel = emptyPeopleSpaceViewModel(context)
+    PeopleScreen(viewModel, onResult)
+}
+
+@Composable
+fun FewPeopleScreen(onResult: (PeopleViewModel.Result) -> Unit) {
+    val context = LocalContext.current.applicationContext
+    val viewModel = fewPeopleSpaceViewModel(context)
+    PeopleScreen(viewModel, onResult)
+}
+
+@Composable
+fun FullPeopleScreen(onResult: (PeopleViewModel.Result) -> Unit) {
+    val context = LocalContext.current.applicationContext
+    val viewModel = fullPeopleSpaceViewModel(context)
+    PeopleScreen(viewModel, onResult)
+}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt
index 467dac04..d7d0d72 100644
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt
@@ -52,17 +52,29 @@
 ) : Screen(identifier)
 
 /** Create the navigation graph for [screen]. */
-fun NavGraphBuilder.screen(screen: Screen, navController: NavController) {
+fun NavGraphBuilder.screen(
+    screen: Screen,
+    navController: NavController,
+    onControlToggleRequested: () -> Unit,
+) {
     when (screen) {
         is ChildScreen -> composable(screen.identifier) { screen.content(navController) }
         is ParentScreen -> {
             val menuRoute = "${screen.identifier}_menu"
             navigation(startDestination = menuRoute, route = screen.identifier) {
                 // The menu to navigate to one of the children screens.
-                composable(menuRoute) { ScreenMenu(screen, navController) }
+                composable(menuRoute) {
+                    ScreenMenu(screen, navController, onControlToggleRequested)
+                }
 
                 // The content of the child screens.
-                screen.children.forEach { (_, child) -> screen(child, navController) }
+                screen.children.forEach { (_, child) ->
+                    screen(
+                        child,
+                        navController,
+                        onControlToggleRequested,
+                    )
+                }
             }
         }
     }
@@ -72,8 +84,27 @@
 private fun ScreenMenu(
     screen: ParentScreen,
     navController: NavController,
+    onControlToggleRequested: () -> Unit,
 ) {
-    LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
+    LazyColumn(
+        Modifier.padding(horizontal = 16.dp),
+        verticalArrangement = Arrangement.spacedBy(8.dp),
+    ) {
+        item {
+            Surface(
+                Modifier.fillMaxWidth(),
+                color = MaterialTheme.colorScheme.tertiaryContainer,
+                shape = CircleShape,
+            ) {
+                Column(
+                    Modifier.clickable(onClick = onControlToggleRequested).padding(16.dp),
+                    horizontalAlignment = Alignment.CenterHorizontally,
+                ) {
+                    Text("Toggle controls")
+                }
+            }
+        }
+
         screen.children.forEach { (name, child) ->
             item {
                 Surface(
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/people/Fakes.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/people/Fakes.kt
new file mode 100644
index 0000000..0966c32
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/people/Fakes.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.people
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.drawable.Icon
+import androidx.core.graphics.drawable.toIcon
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.people.data.model.PeopleTileModel
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import com.android.systemui.people.widget.PeopleTileKey
+
+/** A [PeopleViewModel] that does not have any conversations. */
+fun emptyPeopleSpaceViewModel(@Application context: Context): PeopleViewModel {
+    return fakePeopleSpaceViewModel(context, emptyList(), emptyList())
+}
+
+/** A [PeopleViewModel] that has a few conversations. */
+fun fewPeopleSpaceViewModel(@Application context: Context): PeopleViewModel {
+    return fakePeopleSpaceViewModel(
+        context,
+        priorityTiles =
+            listOf(
+                fakeTile(context, id = "0", Color.RED, "Priority"),
+                fakeTile(context, id = "1", Color.BLUE, "Priority NewStory", hasNewStory = true),
+            ),
+        recentTiles =
+            listOf(
+                fakeTile(context, id = "2", Color.GREEN, "Recent Important", isImportant = true),
+                fakeTile(context, id = "3", Color.CYAN, "Recent DndBlocking", isDndBlocking = true),
+            ),
+    )
+}
+
+/** A [PeopleViewModel] that has a lot of conversations. */
+fun fullPeopleSpaceViewModel(@Application context: Context): PeopleViewModel {
+    return fakePeopleSpaceViewModel(
+        context,
+        priorityTiles =
+            listOf(
+                fakeTile(context, id = "0", Color.RED, "Priority"),
+                fakeTile(context, id = "1", Color.BLUE, "Priority NewStory", hasNewStory = true),
+                fakeTile(context, id = "2", Color.GREEN, "Priority Important", isImportant = true),
+                fakeTile(
+                    context,
+                    id = "3",
+                    Color.CYAN,
+                    "Priority DndBlocking",
+                    isDndBlocking = true,
+                ),
+                fakeTile(
+                    context,
+                    id = "4",
+                    Color.MAGENTA,
+                    "Priority NewStory Important",
+                    hasNewStory = true,
+                    isImportant = true,
+                ),
+            ),
+        recentTiles =
+            listOf(
+                fakeTile(
+                    context,
+                    id = "5",
+                    Color.RED,
+                    "Recent NewStory DndBlocking",
+                    hasNewStory = true,
+                    isDndBlocking = true,
+                ),
+                fakeTile(
+                    context,
+                    id = "6",
+                    Color.BLUE,
+                    "Recent Important DndBlocking",
+                    isImportant = true,
+                    isDndBlocking = true,
+                ),
+                fakeTile(
+                    context,
+                    id = "7",
+                    Color.GREEN,
+                    "Recent NewStory Important DndBlocking",
+                    hasNewStory = true,
+                    isImportant = true,
+                    isDndBlocking = true,
+                ),
+                fakeTile(context, id = "8", Color.CYAN, "Recent"),
+                fakeTile(context, id = "9", Color.MAGENTA, "Recent"),
+            ),
+    )
+}
+
+private fun fakePeopleSpaceViewModel(
+    @Application context: Context,
+    priorityTiles: List<PeopleTileModel>,
+    recentTiles: List<PeopleTileModel>,
+): PeopleViewModel {
+    return PeopleViewModel(
+        context,
+        FakePeopleTileRepository(priorityTiles, recentTiles),
+        FakePeopleWidgetRepository(),
+    )
+}
+
+private fun fakeTile(
+    @Application context: Context,
+    id: String,
+    iconColor: Int,
+    username: String,
+    hasNewStory: Boolean = false,
+    isImportant: Boolean = false,
+    isDndBlocking: Boolean = false
+): PeopleTileModel {
+    return PeopleTileModel(
+        PeopleTileKey(id, /* userId= */ 0, /* packageName */ ""),
+        username,
+        fakeUserIcon(context, iconColor),
+        hasNewStory,
+        isImportant,
+        isDndBlocking,
+    )
+}
+
+private fun fakeUserIcon(@Application context: Context, color: Int): Icon {
+    val size = context.resources.getDimensionPixelSize(R.dimen.avatar_size_for_medium)
+    val bitmap =
+        Bitmap.createBitmap(
+            size,
+            size,
+            Bitmap.Config.ARGB_8888,
+        )
+    val canvas = Canvas(bitmap)
+    val paint = Paint().apply { this.color = color }
+    val radius = size / 2f
+    canvas.drawCircle(/* cx= */ radius, /* cy= */ radius, /* radius= */ radius, paint)
+    return bitmap.toIcon()
+}
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-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index babe924..8fa2204 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -37,8 +37,8 @@
     <!-- When the lock screen is showing and the phone plugged in, and the battery is not fully charged, say that it's wirelessly charging. [CHAR LIMIT=50]  -->
     <string name="keyguard_plugged_in_wireless"><xliff:g id="percentage" example="20%">%s</xliff:g> • Charging wirelessly</string>
 
-    <!-- When the lock screen is showing and the phone plugged in, and the battery is not fully charged, say that it's dock charging. [CHAR LIMIT=50]  -->
-    <string name="keyguard_plugged_in_dock"><xliff:g id="percentage" example="20%">%s</xliff:g> • Charging Dock</string>
+    <!-- When the lock screen is showing and the phone plugged in, and the battery is not fully charged, say that it's charging. [CHAR LIMIT=50]  -->
+    <string name="keyguard_plugged_in_dock"><xliff:g id="percentage" example="20%">%s</xliff:g> • Charging</string>
 
     <!-- When the lock screen is showing and the phone plugged in, and the battery
          is not fully charged, say that it's charging.  -->
@@ -53,7 +53,7 @@
     <string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string>
 
     <!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that charging is temporarily limited.  -->
-    <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging temporarily limited</string>
+    <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging is paused to protect battery</string>
 
     <!-- On the keyguard screen, when pattern lock is disabled, only tell them to press menu to unlock.  This is shown in small font at the bottom. -->
     <string name="keyguard_instructions_when_pattern_disabled">Press Menu to unlock.</string>
diff --git a/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml
new file mode 100644
index 0000000..4da47af
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.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.

+-->

+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

+    <item>

+        <shape android:shape="rectangle">

+            <solid android:color="@color/accessibility_magnifier_bg" />

+            <corners android:radius="24dp" />

+            <stroke

+                android:color="@color/accessibility_magnifier_bg_stroke"

+                android:width="1dp" />

+        </shape>

+    </item>

+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_btn_bg.xml b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_btn_bg.xml
new file mode 100644
index 0000000..5c9dd56
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_btn_bg.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.

+-->

+<ripple xmlns:android="http://schemas.android.com/apk/res/android"

+android:color="?android:attr/colorControlHighlight">

+<item android:id="@android:id/mask">

+    <shape android:shape="oval">

+        <solid android:color="@color/accessibility_magnifier_bg" />

+        <size

+            android:width="56dp"

+            android:height="56dp"/>

+        <corners android:radius="2dp"/>

+    </shape>

+</item>

+</ripple>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml b/packages/SystemUI/res/drawable/accessibility_magnifier_btn_bg.xml
similarity index 64%
rename from libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml
rename to packages/SystemUI/res/drawable/accessibility_magnifier_btn_bg.xml
index 1938f45..f633b3e 100644
--- a/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml
+++ b/packages/SystemUI/res/drawable/accessibility_magnifier_btn_bg.xml
@@ -1,21 +1,27 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2020 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="rectangle">
-    <corners android:radius="@dimen/pip_menu_button_radius" />
-    <solid android:color="@color/tv_pip_menu_icon_bg" />
-</shape>
\ No newline at end of file
+<?xml version="1.0" encoding="utf-8"?>

+<!--

+    Copyright (C) 2022 The Android Open Source Project

+

+    Licensed under the Apache License, Version 2.0 (the "License");

+    you may not use this file except in compliance with the License.

+    You may obtain a copy of the License at

+

+         http://www.apache.org/licenses/LICENSE-2.0

+

+    Unless required by applicable law or agreed to in writing, software

+    distributed under the License is distributed on an "AS IS" BASIS,

+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+    See the License for the specific language governing permissions and

+    limitations under the License.

+-->

+<shape xmlns:android="http://schemas.android.com/apk/res/android"

+    android:shape="oval">

+    <solid android:color="@color/accessibility_magnifier_bg" />

+    <size

+        android:width="56dp"

+        android:height="56dp"/>

+    <corners android:radius="2dp"/>

+    <stroke

+        android:color="@color/accessibility_magnifier_bg_stroke"

+        android:width="1dp" />

+ </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
deleted file mode 100644
index 5084ca4..0000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0.975"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.659" android:translateY="15.75"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="167" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="167" android:startOffset="0" android:valueFrom="2.5" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="83" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0.975" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="417" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml
deleted file mode 100644
index c4f8181..0000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0.975"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.75" android:translateY="15.75" android:pivotX="19.341" android:pivotY="24.25" android:scaleX="0.5" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="167" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="167" android:startOffset="0" android:valueFrom="2.5" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="83" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0.975" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="233" android:startOffset="0" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="267" android:startOffset="233" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="0.5" android:valueTo="0.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0.5" android:valueTo="0.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="333" android:startOffset="167" android:valueFrom="0.5" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="333" android:startOffset="167" android:valueFrom="0.5" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0" android:valueTo="0.5" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="683" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
deleted file mode 100644
index c05a8d5..0000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0" android:strokeAlpha="0" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="0"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.659" android:translateY="15.75"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeWidth" android:duration="233" android:startOffset="67" android:valueFrom="0" android:valueTo="2.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="167" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="267" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.341,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="350" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml
deleted file mode 100644
index 1694429..0000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_2_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="20.75" android:translateY="15.75"><path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group><group android:name="_R_G_L_0_G" android:translateX="37.357" android:translateY="43.25" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#d3e3fd" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " android:valueTo="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="143" android:startOffset="107" android:valueFrom="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.331,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="140" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="50" android:startOffset="140" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " android:valueTo="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="107" android:valueFrom="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="250" android:startOffset="0" android:valueFrom="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.189,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="95" android:startOffset="0" android:valueFrom="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " android:valueTo="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="24" android:startOffset="95" android:valueFrom="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueTo="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="81" android:startOffset="119" android:valueFrom="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.261,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="233" android:startOffset="200" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.123,0 0.23,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="120" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="20" android:startOffset="120" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_close.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_close.xml
new file mode 100644
index 0000000..a44a484
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_menu_close.xml
@@ -0,0 +1,29 @@
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="32"
+    android:viewportHeight="32">
+  <path
+      android:pathData="M7.3334,24.6667L24.6674,7.3334M7.3334,7.3334L24.6674,24.6667"
+      android:strokeLineJoin="round"
+      android:strokeWidth="1.5"
+      android:fillColor="#00000000"
+      android:strokeColor="#000000"
+      android:fillType="evenOdd"
+      android:strokeLineCap="round"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml
new file mode 100644
index 0000000..1ab0d4d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml
@@ -0,0 +1,25 @@
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:width="24dp"
+    android:height="24dp">
+    <path
+        android:pathData="M3 21L21 21C22.1 21 23 20.1 23 19L23 5C23 3.9 22.1 3 21 3L3 3C1.9 3 1 3.9 1 5L1 19C1 20.1 1.9 21 3 21ZM3 5L21 5L21 19L3 19L3 5ZM4 15L16.75 15L16.75 6L4 6L4 15Z"
+        android:fillType="evenOdd"
+        android:fillColor="#000000" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml
new file mode 100644
index 0000000..6fc89a6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml
@@ -0,0 +1,25 @@
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:width="24dp"
+    android:height="24dp">
+    <path
+        android:pathData="M3 21L21 21C22.1 21 23 20.1 23 19L23 5C23 3.9 22.1 3 21 3L3 3C1.9 3 1 3.9 1 5L1 19C1 20.1 1.9 21 3 21ZM3 5L21 5L21 19L3 19L3 5ZM4 12.75L13.75 12.75L13.75 6L4 6L4 12.75Z"
+        android:fillType="evenOdd"
+        android:fillColor="#000000" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml
new file mode 100644
index 0000000..fd73574
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml
@@ -0,0 +1,25 @@
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:width="24dp"
+    android:height="24dp">
+    <path
+        android:pathData="M3 21L21 21C22.1 21 23 20.1 23 19L23 5C23 3.9 22.1 3 21 3L3 3C1.9 3 1 3.9 1 5L1 19C1 20.1 1.9 21 3 21ZM3 5L21 5L21 19L3 19L3 5ZM4 10.5L8.5 10.5L8.5 6L4 6L4 10.5Z"
+        android:fillType="evenOdd"
+        android:fillColor="#000000" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_move_magnification.xml b/packages/SystemUI/res/drawable/ic_move_magnification.xml
index 96db365..1bff559 100644
--- a/packages/SystemUI/res/drawable/ic_move_magnification.xml
+++ b/packages/SystemUI/res/drawable/ic_move_magnification.xml
@@ -23,18 +23,21 @@
             <size
                 android:height="@dimen/magnification_drag_view_size"
                 android:width="@dimen/magnification_drag_view_size"/>
+            <corners android:topLeftRadius="12dp"/>
+
         </shape>
     </item>
     <item
         android:gravity="center">
+
         <vector xmlns:android="http://schemas.android.com/apk/res/android"
-            android:width="30dp"
-            android:height="30dp"
             android:viewportWidth="24"
-            android:viewportHeight="24">
+            android:viewportHeight="24"
+            android:width="24dp"
+            android:height="24dp">
             <path
-                android:pathData="M18.19,12.44l-3.24,-1.62c1.29,-1 2.12,-2.56 2.12,-4.32c0,-3.03 -2.47,-5.5 -5.5,-5.5s-5.5,2.47 -5.5,5.5c0,2.13 1.22,3.98 3,4.89v3.26c-2.11,-0.45 -2.01,-0.44 -2.26,-0.44c-0.53,0 -1.03,0.21 -1.41,0.59L4,16.22l5.09,5.09C9.52,21.75 10.12,22 10.74,22h6.3c0.98,0 1.81,-0.7 1.97,-1.67l0.8,-4.71C20.03,14.32 19.38,13.04 18.19,12.44zM17.84,15.29L17.04,20h-6.3c-0.09,0 -0.17,-0.04 -0.24,-0.1l-3.68,-3.68l4.25,0.89V6.5c0,-0.28 0.22,-0.5 0.5,-0.5c0.28,0 0.5,0.22 0.5,0.5v6h1.76l3.46,1.73C17.69,14.43 17.91,14.86 17.84,15.29zM8.07,6.5c0,-1.93 1.57,-3.5 3.5,-3.5s3.5,1.57 3.5,3.5c0,0.95 -0.38,1.81 -1,2.44V6.5c0,-1.38 -1.12,-2.5 -2.5,-2.5c-1.38,0 -2.5,1.12 -2.5,2.5v2.44C8.45,8.31 8.07,7.45 8.07,6.5z"
-                android:fillColor="#FFFFFF"/>
+                android:pathData="M13.2217 21.7734C12.8857 22.1094 12.288 22.712 12 23C12 23 11.1143 22.1094 10.7783 21.7734L8.33494 19.3301L9.55662 18.1084L12 20.5518L14.4434 18.1084L15.665 19.3301L13.2217 21.7734ZM19.3301 15.665L18.1084 14.4433L20.5518 12L18.1084 9.5566L19.3301 8.33492L21.7735 10.7783C22.1094 11.1142 22.4241 11.4241 23 12C22.4241 12.5759 22.3963 12.5988 21.7735 13.2217L19.3301 15.665ZM14.4434 14.4433C13.7714 15.1153 12.957 15.4512 12 15.4512C11.043 15.4512 10.2285 15.1153 9.55662 14.4433C8.88469 13.7714 8.54873 12.957 8.54873 12C8.54873 11.043 8.88469 10.2285 9.55662 9.5566C10.2285 8.88468 11.043 8.54871 12 8.54871C12.957 8.54871 13.7714 8.88467 14.4434 9.5566C15.1153 10.2285 15.4512 11.043 15.4512 12C15.4512 12.957 15.1153 13.7714 14.4434 14.4433ZM4.66988 15.665L2.22651 13.2217C1.89055 12.8857 1.28791 12.288 1 12C1.28791 11.712 1.89055 11.1143 2.22651 10.7783L4.66988 8.33492L5.89157 9.5566L3.4482 12L5.89157 14.4433L4.66988 15.665ZM14.4434 5.89155L12 3.44818L9.55662 5.89155L8.33494 4.66987L10.7783 2.2265C11.1389 1.86592 11.2963 1.70369 12 1C12.5758 1.57585 12.8857 1.89053 13.2217 2.2265L15.665 4.66986L14.4434 5.89155Z"
+                android:fillColor="#ffffff" />
         </vector>
     </item>
 </layer-list>
diff --git a/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml b/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml
index 36a1224..c7434f5 100644
--- a/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml
+++ b/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml
@@ -13,35 +13,19 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <item>
-        <shape android:shape="rectangle">
-            <solid android:color="@color/magnification_switch_button_color" />
-            <size
-                android:width="48dp"
-                android:height="48dp" />
-        </shape>
-    </item>
-
-    <item
-        android:gravity="center">
-        <vector
-            android:width="36dp"
-            android:height="36dp"
-            android:viewportWidth="24"
-            android:viewportHeight="24">
-            <group>
-                <clip-path
-                    android:pathData="M0,0h24v24h-24z"/>
-                <path
-                    android:pathData="M11,6.05V8.05H14.59L8,14.64V11.05H6V18.05H13V16.05H9.41L16,9.46V13.05H18V6.05H11Z"
-                    android:fillColor="#ffffff"/>
-                <path
-                    android:pathData="M20,4.05V20.05H4V4.05H20ZM22,2.05H2V22.05H22V2.05Z"
-                    android:fillColor="#ffffff"/>
-            </group>
-        </vector>
-    </item>
-
-</layer-list>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="36dp"
+    android:height="36dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <group>
+        <clip-path
+            android:pathData="M0,0h24v24h-24z"/>
+        <path
+            android:pathData="M11,6.05V8.05H14.59L8,14.64V11.05H6V18.05H13V16.05H9.41L16,9.46V13.05H18V6.05H11Z"
+            android:fillColor="#000000"/>
+        <path
+            android:pathData="M20,4.05V20.05H4V4.05H20ZM22,2.05H2V22.05H22V2.05Z"
+            android:fillColor="#000000"/>
+    </group>
+</vector>
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index e1b294f..d633803 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -49,11 +49,11 @@
 
     <FrameLayout
         android:id="@+id/biometric_icon_frame"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_horizontal">
 
-        <ImageView
+        <com.airbnb.lottie.LottieAnimationView
             android:id="@+id/biometric_icon"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml b/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
index ce53e27..01ea31f 100644
--- a/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
@@ -17,7 +17,7 @@
 <com.android.systemui.biometrics.AuthBiometricFingerprintView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/contents"
-    android:layout_width="match_parent"
+    android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:orientation="vertical">
 
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
index 5f4e310..8ab3e45 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -20,10 +20,11 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:fontFamily="@*android:string/config_clockFontFamily"
-    android:includeFontPadding="false"
     android:textColor="@android:color/white"
     android:format12Hour="@string/dream_time_complication_12_hr_time_format"
     android:format24Hour="@string/dream_time_complication_24_hr_time_format"
     android:shadowColor="@color/keyguard_shadow_color"
     android:shadowRadius="?attr/shadowRadius"
+    android:fontFeatureSettings="pnum, lnum"
+    android:letterSpacing="0.02"
     android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"/>
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/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
new file mode 100644
index 0000000..6d8847c
--- /dev/null
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/magnifier_panel_view"
+    android:layout_width="@dimen/magnification_max_size"
+    android:layout_height="match_parent"
+    android:background="@drawable/accessibility_magnification_setting_view_bg"
+    android:orientation="vertical">
+    <LinearLayout
+        android:layout_width="@dimen/magnification_max_size"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <TextView
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/accessibility_magnifier_size"
+            android:textAppearance="?android:attr/textAppearanceListItem"
+            android:textColor="?android:attr/textColorAlertDialogListItem"
+            android:focusable="true"
+            android:layout_gravity="center_vertical|left"
+            android:layout_marginStart="20dp"/>
+
+        <Button
+            android:id="@+id/magnifier_edit_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/accessibility_magnifier_edit"
+            android:textAppearance="?android:attr/textAppearanceListItem"
+            android:textColor="?android:attr/textColorAlertDialogListItem"
+            android:focusable="true"
+            android:layout_gravity="right"
+            android:layout_marginEnd="20dp"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="@dimen/magnification_max_size"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <ImageButton
+            android:id="@+id/magnifier_small_button"
+            android:layout_width="0dp"
+            android:layout_height="56dp"
+            android:scaleType="center"
+            android:layout_weight="1"
+            android:layout_marginStart="12dp"/>
+
+        <ImageButton
+            android:id="@+id/magnifier_medium_button"
+            android:layout_width="0dp"
+            android:layout_height="56dp"
+            android:scaleType="center"
+            android:layout_weight="1"/>
+
+        <ImageButton
+            android:id="@+id/magnifier_large_button"
+            android:layout_width="0dp"
+            android:layout_height="56dp"
+            android:scaleType="center"
+            android:layout_weight="1"/>
+
+        <ImageButton
+            android:id="@+id/magnifier_full_button"
+            android:layout_width="0dp"
+            android:layout_height="56dp"
+            android:scaleType="center"
+            android:layout_weight="1"
+            android:layout_marginEnd="12dp"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:paddingTop="8dp"
+        android:paddingEnd="20dp"
+        android:paddingStart="20dp"
+        android:focusable="true">
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:background="?android:attr/selectableItemBackground"
+            android:ellipsize="marquee"
+            android:gravity="center_vertical"
+            android:minHeight="?android:attr/listPreferredItemHeightSmall"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:singleLine="true"
+                android:text="@string/accessibility_allow_diagonal_scrolling"
+                android:textAppearance="?android:attr/textAppearanceListItem"
+                android:textColor="?android:attr/textColorAlertDialogListItem" />
+        </LinearLayout>
+
+        <Switch
+            android:id="@+id/magnifier_horizontal_lock_switch"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="right|center"
+            android:theme="@android:style/Theme.DeviceDefault.DayNight"/>
+    </LinearLayout>
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/accessibility_magnification_zoom"
+        android:textAppearance="?android:attr/textAppearanceListItem"
+        android:textColor="?android:attr/textColorAlertDialogListItem"
+        android:focusable="true"
+        android:layout_marginStart="20dp"
+        android:paddingTop="2dp"
+        android:paddingBottom="10dp"/>
+
+    <SeekBar
+        android:id="@+id/magnifier_zoom_seekbar"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:progress="0"
+        android:max="6"
+        android:layout_marginEnd="20dp"
+        android:theme="@android:style/Theme.DeviceDefault.DayNight"/>
+
+
+    <Button
+        android:id="@+id/magnifier_close_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/accessibility_magnification_close"
+        android:textAppearance="?android:attr/textAppearanceListItem"
+        android:textColor="?android:attr/textColorAlertDialogListItem"
+        android:focusable="true"
+        android:layout_gravity="center_horizontal"
+        android:paddingBottom="24dp"/>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/window_magnifier_view.xml b/packages/SystemUI/res/layout/window_magnifier_view.xml
index 7c755e5..0bff47c 100644
--- a/packages/SystemUI/res/layout/window_magnifier_view.xml
+++ b/packages/SystemUI/res/layout/window_magnifier_view.xml
@@ -16,33 +16,25 @@
   -->
 
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-             android:layout_width="wrap_content"
-             android:layout_height="wrap_content"
-             android:screenReaderFocusable="true">
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+
     <View
+        android:id="@+id/magnification_inner_border"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_margin="@dimen/magnification_outer_border_margin"
-        android:importantForAccessibility="no"
-        android:background="@android:color/black"/>
-
-    <View
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_margin="@dimen/magnification_inner_border_margin"
-        android:importantForAccessibility="no"
         android:background="@color/magnification_border_color"/>
 
     <RelativeLayout
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:importantForAccessibility="noHideDescendants">
+        android:layout_height="match_parent">
 
         <View
             android:id="@+id/left_handle"
             android:layout_width="@dimen/magnification_border_drag_size"
             android:layout_height="match_parent"
-            android:layout_above="@+id/bottom_handle"/>
+            android:layout_alignParentStart="true"/>
 
         <View
             android:id="@+id/top_handle"
@@ -54,7 +46,6 @@
             android:id="@+id/right_handle"
             android:layout_width="@dimen/magnification_border_drag_size"
             android:layout_height="match_parent"
-            android:layout_above="@+id/bottom_handle"
             android:layout_alignParentEnd="true"/>
 
         <View
@@ -68,7 +59,6 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_margin="@dimen/magnification_mirror_surface_margin"/>
-
     </RelativeLayout>
 
     <ImageView
@@ -79,7 +69,17 @@
         android:layout_gravity="right|bottom"
         android:padding="@dimen/magnifier_drag_handle_padding"
         android:scaleType="center"
-        android:importantForAccessibility="no"
         android:src="@drawable/ic_move_magnification"/>
 
-</FrameLayout>
\ No newline at end of file
+    <ImageView
+        android:id="@+id/close_button"
+        android:layout_width="40dp"
+        android:layout_height="40dp"
+        android:layout_margin="30dp"
+        android:padding="@dimen/magnification_switch_button_padding"
+        android:layout_gravity="right|bottom"
+        android:scaleType="center"
+        android:visibility="gone"
+        android:background="@drawable/accessibility_magnifier_btn_bg"
+        android:src="@drawable/ic_magnification_menu_close" />
+</FrameLayout>
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_fingerprint_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_fingerprint_lottie.json
new file mode 100644
index 0000000..cc68a83
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_fingerprint_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":25,"w":80,"h":80,"nm":"error_to_fingerprint","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-1.636],[4.642,0.681],[2.07,1.453],[-0.001,1.636],[-2.621,1.341],[-4.909,0.563],[-8.182,-1.636]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.299],"y":[1]},"o":{"x":[0.543],"y":[0]},"t":5,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":5,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.95,40,0],"ix":2,"l":2},"a":{"a":0,"k":[30,30,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-7.5],[1.2,-7.5],[1.2,7.5],[-1.2,7.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.002,32.488],"ix":2},"a":{"a":0,"k":[0.002,7.488],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.659,0.6],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.6,0.92],"y":[1,1.096]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[100,110]},{"t":10,"s":[100,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top!","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-1.25],[1.2,-1.25],[1.2,1.25],[-1.2,1.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30,38.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.6,0.6],"y":[1,1]},"o":{"x":[0.853,0.853],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.92,0.92],"y":[1.06,1.06]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[110,110]},{"t":10,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom!","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":25,"st":-30,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[97.5,97.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[2.5]},{"t":10,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":10,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":14,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_success_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_success_lottie.json
new file mode 100644
index 0000000..c5ed827
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_success_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":80,"h":80,"nm":"RearFPS_error_to_success","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[28,47,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-10.556,-9.889],[7.444,6.555],[34.597,-20.486]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":10,"op":910,"st":10,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.95,40,0],"ix":2,"l":2},"a":{"a":0,"k":[30,30,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.721,-7.982],[1.721,-7.982],[1.721,7.5],[-1.721,7.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.002,32.488],"ix":2},"a":{"a":0,"k":[0.002,7.488],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.659,0.6],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.6,0.92],"y":[1,1.096]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[100,110]},{"t":10,"s":[100,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top!","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.681,-1.25],[1.681,-1.25],[1.681,2.213],[-1.681,2.213]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30,38.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.6,0.6],"y":[1,1]},"o":{"x":[0.853,0.853],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.92,0.92],"y":[1.06,1.06]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[110,110]},{"t":10,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom!","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":86,"st":-30,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[93.5,93.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":10,"op":21,"st":10,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[93.5,93.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[4]},{"t":10,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":10,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_unlock_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_unlock_lottie.json
new file mode 100644
index 0000000..aaf7e58
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_unlock_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":41,"w":80,"h":80,"nm":"error_to_unlock","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.091,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[1.4,1.4,0]},"t":10,"s":[50,50,100]},{"t":30,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":14,"s":[{"i":[[0,0],[0,0],[-3.452,0],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[3.452,0],[0,0],[0,0]],"v":[[-6.217,12.558],[-6.234,6.669],[0.016,0.558],[6.266,6.669],[6.283,12.558]],"c":false}]},{"t":30,"s":[{"i":[[0,0],[0,0],[3.292,0.021],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[-3.393,-0.022],[0,0],[0,0]],"v":[[18.568,12.573],[18.552,6.684],[12.516,0.553],[6.266,6.669],[6.283,12.558]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-0.307,0.352],[-0.601,0],[0,0],[0,-1.104],[0,0]],"o":[[0,0],[0,-0.503],[0.367,-0.42],[0,0],[1.104,0],[0,0],[0,0]],"v":[[-11.2,14.15],[-11.198,6.146],[-10.705,4.831],[-9.198,4.146],[9.302,4.146],[11.302,6.146],[11.3,14.07]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-0.736,0],[0,-0.741],[0,0],[0.243,0],[0.066,0.191],[0,0],[0.179,0],[0,-0.276],[-0.162,-0.273],[-0.755,0.357],[0,0]],"o":[[-1.273,-0.008],[0,-0.741],[0.736,0],[0,0],[0,0.276],[-0.181,0],[0,0],[-0.066,-0.191],[-0.243,0],[-0.002,0.139],[0.109,0.182],[0.727,-0.402],[0,0]],"v":[[0.082,3.187],[-1.235,1.986],[0.055,0.642],[1.346,1.986],[1.346,2.026],[0.905,2.527],[0.498,2.212],[0.35,1.794],[-0.057,1.479],[-0.733,1.951],[-0.58,2.686],[0.619,3.071],[1.351,2]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0.446,-0.367],[0.481,0],[0,0],[0,1.104],[0,0]],"o":[[0,0],[0,0.623],[-0.345,0.284],[0,0],[-1.104,0],[0,0],[0,0]],"v":[[11.302,-10.469],[11.302,-2.469],[10.57,-0.923],[9.302,-0.469],[-9.198,-0.469],[-11.198,-2.469],[-11.198,-10.469]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":10,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.95,40,0],"ix":2,"l":2},"a":{"a":0,"k":[30,30,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-7.5],[1.2,-7.5],[1.2,7.5],[-1.2,7.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.002,32.488],"ix":2},"a":{"a":0,"k":[0.002,7.488],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.659,0.6],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.6,0.92],"y":[1,1.096]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[100,110]},{"t":10,"s":[100,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top!","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-1.25],[1.2,-1.25],[1.2,1.25],[-1.2,1.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30,38.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.6,0.6],"y":[1,1]},"o":{"x":[0.853,0.853],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.92,0.92],"y":[1.06,1.06]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[110,110]},{"t":10,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom!","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":86,"st":-30,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[97.5,97.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[2.5]},{"t":10,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":10,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":14,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_error_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_error_lottie.json
new file mode 100644
index 0000000..78bccba
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_error_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":80,"h":80,"nm":"fingerprint_to_error","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-1.636],[4.642,0.681],[2.07,1.453],[-0.001,1.636],[-2.621,1.341],[-4.909,0.563],[-8.182,-1.636]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":5,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.95,40,0],"ix":2,"l":2},"a":{"a":0,"k":[30,30,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-7.5],[1.2,-7.5],[1.2,7.5],[-1.2,7.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019610882,0.721568644047,0.709803938866,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.002,32.488],"ix":2},"a":{"a":0,"k":[0.002,7.488],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.4,0.08],"y":[0,0.096]},"t":10,"s":[100,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.341,0.4],"y":[0,0]},"t":16,"s":[100,110]},{"t":20,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top!","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-1.25],[1.2,-1.25],[1.2,1.25],[-1.2,1.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019610882,0.721568644047,0.709803938866,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30,38.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.08,0.08],"y":[0.06,0.06]},"t":10,"s":[0,0]},{"i":{"x":[0.147,0.147],"y":[1,1]},"o":{"x":[0.4,0.4],"y":[0,0]},"t":16,"s":[110,110]},{"t":20,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom!","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":21,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":9,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[97.5,97.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":9,"s":[100]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":9,"s":[100]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[0]},{"t":18,"s":[2.5]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":4,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_success_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_success_lottie.json
new file mode 100644
index 0000000..3eb95ef
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_success_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":80,"h":80,"nm":"RearFPS_fingerprint_to_success","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[28,47,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-10.556,-9.889],[7.444,6.555],[34.597,-20.486]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":10,"op":910,"st":10,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[93.5,93.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":10,"op":21,"st":10,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.091,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-0.386],[4.642,1.931],[2.07,2.703],[-0.001,2.886],[-2.621,2.591],[-4.909,1.813],[-8.182,-0.386]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":20,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_unlock_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_unlock_lottie.json
new file mode 100644
index 0000000..313c6c5
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_unlock_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":31,"w":80,"h":80,"nm":"fingerprint_to_unlock","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":2,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.107,46,0],"ix":2,"l":2},"a":{"a":0,"k":[2.75,2.75,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.43,0.43,0.2],"y":[1,1,1]},"o":{"x":[0.001,0.001,0.001],"y":[0,0,0]},"t":7.199,"s":[141.866,141.866,100]},{"t":15,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7.199,"s":[0]},{"t":8.400390625,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[2.75,2.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.091,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.767},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false}]},{"i":{"x":0.833,"y":0.767},"o":{"x":0.167,"y":0.233},"t":5.715,"s":[{"i":[[0,0],[-1.323,1.591],[-2.674,0],[-1.207,-1.781],[0,0]],"o":[[0,0],[1.298,-1.562],[2.657,0],[1.206,1.781],[0,0]],"v":[[-7.87,7.358],[-5.804,2.36],[0.009,-0.261],[5.845,2.706],[7.905,7.358]],"c":false}]},{"i":{"x":0.261,"y":1},"o":{"x":0.167,"y":0.233},"t":7.143,"s":[{"i":[[0,0],[-0.549,1.21],[-2.975,0],[-0.74,-2.398],[0,0]],"o":[[0,0],[0.796,-2.263],[2.964,0],[0.258,0.927],[0,0]],"v":[[-7.231,9.37],[-5.97,4.027],[0.012,0.056],[6.008,4.239],[7.277,9.37]],"c":false}]},{"i":{"x":0.23,"y":1},"o":{"x":0.123,"y":0},"t":12,"s":[{"i":[[0,0],[0,0],[-3.452,0],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[3.452,0],[0,0],[0,0]],"v":[[-6.217,12.558],[-6.234,6.669],[0.016,0.558],[6.266,6.669],[6.283,12.558]],"c":false}]},{"t":26,"s":[{"i":[[0,0],[0,0],[3.292,0.021],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[-3.393,-0.022],[0,0],[0,0]],"v":[[18.568,12.573],[18.552,6.684],[12.516,0.553],[6.266,6.669],[6.283,12.558]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.189,"y":1},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false}]},{"t":15,"s":[{"i":[[0,0],[0,0],[-0.307,0.352],[-0.601,0],[0,0],[0,-1.104],[0,0]],"o":[[0,0],[0,-0.503],[0.367,-0.42],[0,0],[1.104,0],[0,0],[0,0]],"v":[[-11.2,14.15],[-11.198,6.146],[-10.705,4.831],[-9.198,4.146],[9.302,4.146],[11.302,6.146],[11.3,14.07]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.767},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.233},"t":6.428,"s":[{"i":[[0,0],[0,0],[-1.576,0],[0,-1.474],[0,0],[1.541,0.347],[0.142,0.379],[0,0],[0.383,0],[0,-0.549],[-0.256,-0.431],[-0.768,0.207],[0,0]],"o":[[-1.823,0.497],[0,-1.474],[1.576,0],[0,0],[0,0.549],[-0.378,-0.085],[0,0],[-0.142,-0.379],[-0.521,0],[-0.002,0.353],[0.171,0.288],[0.622,-0.344],[0,0]],"v":[[-0.41,3.841],[-2.717,1.917],[0.047,-0.756],[2.811,1.917],[2.811,1.996],[0.225,3.848],[0.995,2.366],[0.679,1.534],[-0.193,0.909],[-1.338,1.879],[-1.026,3.169],[0.445,3.702],[1.036,3.015]],"c":false}]},{"t":12.857421875,"s":[{"i":[[0,0],[0,0],[-0.736,0],[0,-0.741],[0,0],[0.243,0],[0.066,0.191],[0,0],[0.179,0],[0,-0.276],[-0.162,-0.273],[-0.755,0.357],[0,0]],"o":[[-1.273,-0.008],[0,-0.741],[0.736,0],[0,0],[0,0.276],[-0.181,0],[0,0],[-0.066,-0.191],[-0.243,0],[-0.002,0.139],[0.109,0.182],[0.727,-0.402],[0,0]],"v":[[0.082,3.187],[-1.235,1.986],[0.055,0.642],[1.346,1.986],[1.346,2.026],[0.905,2.527],[0.498,2.212],[0.35,1.794],[-0.057,1.479],[-0.733,1.951],[-0.58,2.686],[0.619,3.071],[1.351,2]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":8.4,"s":[100]},{"t":11.3984375,"s":[0]}],"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.767},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-1.636],[4.642,0.681],[2.07,1.453],[-0.001,1.636],[-2.621,1.341],[-4.909,0.563],[-8.182,-1.636]],"c":false}]},{"i":{"x":0.331,"y":1},"o":{"x":0.167,"y":0.233},"t":6.428,"s":[{"i":[[0,0],[0.313,-0.134],[0.554,-0.317],[0.535,0],[0.203,0.046],[0.175,0.919],[0.232,0.216]],"o":[[-0.249,0.232],[-0.196,0.557],[-0.424,0.245],[-0.216,0],[-1.03,-0.044],[-0.288,-0.132],[0,0]],"v":[[11.468,-8.353],[10.62,-1.716],[9.232,-0.353],[7.057,0.034],[-7.634,-0.037],[-10.453,-1.739],[-11.238,-8.347]],"c":false}]},{"t":15,"s":[{"i":[[0,0],[0,0],[0.446,-0.367],[0.481,0],[0,0],[0,1.104],[0,0]],"o":[[0,0],[0,0.623],[-0.345,0.284],[0,0],[-1.104,0],[0,0],[0,0]],"v":[[11.302,-10.469],[11.302,-2.469],[10.57,-0.923],[9.302,-0.469],[-9.198,-0.469],[-11.198,-2.469],[-11.198,-10.469]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[{"tm":30,"cm":"1","dr":0},{"tm":51,"cm":"350ms\r","dr":0},{"tm":69,"cm":"650ms\r","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 1eece4c..26bf103 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -166,8 +166,12 @@
 
     <!-- Window magnification colors -->
     <color name="magnification_border_color">#FF9900</color>
+    <color name="magnification_border_color_change">#0000FF</color>
     <color name="magnification_switch_button_color">#7F000000</color>
     <color name="magnification_drag_handle_color">#B3000000</color>
+    <color name="accessibility_magnifier_bg">#FCFCFC</color>
+    <color name="accessibility_magnifier_bg_stroke">#E0E0E0</color>
+    <color name="accessibility_magnifier_icon_color">#252525</color>
 
     <!-- Volume dialog colors -->
     <color name="volume_dialog_background_color">@android:color/transparent</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 786b6b8..ae30089 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -926,7 +926,8 @@
 
     <!-- Biometric Dialog values -->
     <dimen name="biometric_dialog_face_icon_size">64dp</dimen>
-    <dimen name="biometric_dialog_fingerprint_icon_size">80dp</dimen>
+    <dimen name="biometric_dialog_fingerprint_icon_width">80dp</dimen>
+    <dimen name="biometric_dialog_fingerprint_icon_height">80dp</dimen>
     <dimen name="biometric_dialog_button_negative_max_width">160dp</dimen>
     <dimen name="biometric_dialog_button_positive_max_width">136dp</dimen>
     <dimen name="biometric_dialog_corner_size">4dp</dimen>
@@ -1065,8 +1066,10 @@
     <dimen name="magnification_frame_move_long">25dp</dimen>
     <dimen name="magnification_drag_view_size">36dp</dimen>
     <dimen name="magnification_controls_size">90dp</dimen>
-    <dimen name="magnification_switch_button_size">48dp</dimen>
+    <dimen name="magnification_switch_button_size">56dp</dimen>
+    <dimen name="magnification_switch_button_padding">6dp</dimen>
     <dimen name="magnification_switch_button_margin">16dp</dimen>
+    <dimen name="magnification_close_button_padding">15dp</dimen>
     <dimen name="magnifier_left_right_controls_width">35dp</dimen>
     <dimen name="magnifier_left_right_controls_height">45dp</dimen>
     <dimen name="magnifier_up_down_controls_width">45dp</dimen>
@@ -1074,10 +1077,15 @@
     <!-- The extra padding to show the whole outer border -->
     <dimen name="magnifier_drag_handle_padding">3dp</dimen>
     <dimen name="magnification_max_frame_size">300dp</dimen>
+
     <!-- How far from the right edge of the screen you need to drag the window before the button
          repositions to the other side. -->
     <dimen name="magnification_button_reposition_threshold_from_edge">32dp</dimen>
 
+    <dimen name="magnification_drag_size">15dp</dimen>
+    <dimen name="magnification_max_size">360dp</dimen>
+    <dimen name="magnifier_panel_size">265dp</dimen>
+
     <!-- Home Controls -->
     <dimen name="controls_header_menu_size">48dp</dimen>
     <dimen name="controls_header_bottom_margin">24dp</dimen>
@@ -1171,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>
@@ -1453,7 +1460,7 @@
     <dimen name="dream_overlay_status_bar_extra_margin">16dp</dimen>
 
     <!-- Dream overlay complications related dimensions -->
-    <dimen name="dream_overlay_complication_clock_time_text_size">100sp</dimen>
+    <dimen name="dream_overlay_complication_clock_time_text_size">86sp</dimen>
     <dimen name="dream_overlay_complication_clock_subtitle_text_size">24sp</dimen>
     <dimen name="dream_overlay_complication_preview_text_size">36sp</dimen>
     <dimen name="dream_overlay_complication_preview_icon_padding">28dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 1bf3037..bfdb170 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -799,7 +799,7 @@
     <!-- Message shown when lock screen is tapped or face authentication fails. [CHAR LIMIT=60] -->
     <string name="keyguard_unlock">Swipe up to open</string>
 
-    <!-- Message shown when lock screen is unlocked (ie: by trust agent) and the user taps the empty space on the lock screen and UDFPS is supported. Provides extra instructions for how the user can enter their device [CHAR LIMIT=60] -->
+    <!-- Message shown when lock screen is unlocked (ie: by trust agent or face auth). Provides extra instructions for how the user can enter their device [CHAR LIMIT=60] -->
     <string name="keyguard_unlock_press">Press the unlock icon to open</string>
 
     <!-- Message shown when non-bypass face authentication succeeds. Provides extra instructions for how the user can enter their device [CHAR LIMIT=60] -->
@@ -813,6 +813,10 @@
     <!-- Message shown when non-bypass face authentication succeeds and UDFPS is supported. Provides extra instructions for how the user can enter their device [CHAR LIMIT=60] -->
     <string name="keyguard_face_successful_unlock_press_alt_3">Face recognized. Press the unlock icon to open.</string>
 
+    <!-- Message shown when non-bypass face authentication succeeds. [CHAR LIMIT=60] -->
+    <string name="keyguard_face_successful_unlock">Unlocked by face</string>
+    <!-- Message shown when non-bypass face authentication succeeds. [CHAR LIMIT=60] -->
+    <string name="keyguard_face_successful_unlock_alt1">Face recognized</string>
 
     <!-- Messages shown when users press outside of udfps region during -->
     <string-array name="udfps_accessibility_touch_hints">
@@ -880,7 +884,7 @@
     <string name="keyguard_indication_charging_time_slowly"><xliff:g id="percentage">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>
 
     <!-- Indication on the keyguard that is shown when the device is dock charging. [CHAR LIMIT=80]-->
-    <string name="keyguard_indication_charging_time_dock"><xliff:g id="percentage" example="20%">%2$s</xliff:g> • Charging Dock • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>
+    <string name="keyguard_indication_charging_time_dock"><xliff:g id="percentage" example="20%">%2$s</xliff:g> • Charging • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>
 
     <!-- Related to user switcher --><skip/>
 
@@ -2105,6 +2109,41 @@
     <!-- Click action label for magnification switch. [CHAR LIMIT=NONE] -->
     <string name="magnification_mode_switch_click_label">Switch</string>
 
+    <!-- Title of the magnification option button allow diagonal scrolling [CHAR LIMIT=NONE]-->
+    <string name="accessibility_allow_diagonal_scrolling">Allow diagonal scrolling</string>
+    <!-- Title of the magnification option button Resize [CHAR LIMIT=NONE]-->
+    <string name="accessibility_resize">Resize</string>
+    <!-- Title of the magnification option button Change type [CHAR LIMIT=NONE]-->
+    <string name="accessibility_change_magnification_type">Change magnification type</string>
+    <!-- Title of the magnification option button End resizing [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_end_resizing">End resizing</string>
+
+    <!-- Description of the window magnification Top handle [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_top_handle">Top handle</string>
+    <!-- Description of the window magnification Left handle [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_left_handle">Left handle</string>
+    <!-- Description of the window magnification Right handle [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_right_handle">Right handle</string>
+    <!-- Description of the window magnification Bottom handle [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_bottom_handle">Bottom handle</string>
+
+    <!-- Title of the window magnification panel option Magnifier size [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnifier_size">Magnifier size</string>
+    <!-- Title of the window magnification panel option Zoom [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_zoom">Zoom</string>
+    <!-- Click action label for magnification panel medium size [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_medium">Medium</string>
+    <!-- Click action label for magnification panel small size [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_small">Small</string>
+    <!-- Click action label for magnification panel large size [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_large">Large</string>
+    <!-- Click action label for magnification panel Close [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_close">Close</string>
+    <!-- Click action label for edit magnification size [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnifier_edit">Edit</string>
+    <!-- Click action label for magnification panel settings [CHAR LIMIT=NONE]-->
+    <string name="accessibility_magnification_magnifier_window_settings">Magnifier window settings</string>
+
     <!-- Accessibility floating menu strings -->
     <!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user they could customize or replace the floating button in Settings. [CHAR LIMIT=100] -->
     <string name="accessibility_floating_button_migration_tooltip">Tap to open accessibility features. Customize or replace this button in Settings.\n\n<annotation id="link">View settings</annotation></string>
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 8b5e3c1..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;
@@ -86,7 +87,6 @@
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
@@ -714,7 +714,7 @@
     private void handleFingerprintAuthFailed() {
         Assert.isMainThread();
         if (mHandler.hasCallbacks(mFpCancelNotReceived)) {
-            Log.d(TAG, "handleFingerprintAuthFailed()"
+            mLogger.d("handleFingerprintAuthFailed()"
                     + " triggered while waiting for cancellation, removing watchdog");
             mHandler.removeCallbacks(mFpCancelNotReceived);
         }
@@ -749,7 +749,7 @@
     private void handleFingerprintAuthenticated(int authUserId, boolean isStrongBiometric) {
         Trace.beginSection("KeyGuardUpdateMonitor#handlerFingerPrintAuthenticated");
         if (mHandler.hasCallbacks(mFpCancelNotReceived)) {
-            Log.d(TAG, "handleFingerprintAuthenticated()"
+            mLogger.d("handleFingerprintAuthenticated()"
                     + " triggered while waiting for cancellation, removing watchdog");
             mHandler.removeCallbacks(mFpCancelNotReceived);
         }
@@ -824,7 +824,7 @@
 
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
                 || msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED) {
-            Log.d(TAG, "Fingerprint retrying auth due to(" + msgId + ") -> " + errString);
+            mLogger.logRetryAfterFpError(msgId, errString);
             mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT);
         }
 
@@ -2019,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));
             }
         });
@@ -2565,8 +2566,8 @@
     }
 
     private boolean isOnlyFaceEnrolled() {
-        return isFaceAuthEnabledForUser(getCurrentUser())
-                && !isUnlockWithFingerprintPossible(getCurrentUser());
+        return isFaceEnrolled()
+                && !getCachedIsUnlockWithFingerprintPossible(sCurrentUser);
     }
 
     private void maybeLogListenerModelData(KeyguardListenModel model) {
@@ -2681,7 +2682,9 @@
         return isUnlockWithFacePossible(userId) || isUnlockWithFingerprintPossible(userId);
     }
 
-    private boolean isUnlockWithFingerprintPossible(int userId) {
+    @VisibleForTesting
+    boolean isUnlockWithFingerprintPossible(int userId) {
+        // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
         mIsUnlockWithFingerprintPossible.put(userId, mFpm != null && mFpm.isHardwareDetected()
                 && !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId));
         return mIsUnlockWithFingerprintPossible.get(userId);
@@ -2703,6 +2706,7 @@
      * If face hardware is available, user has enrolled and enabled auth via setting.
      */
     public boolean isFaceAuthEnabledForUser(int userId) {
+        // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
         updateFaceEnrolled(userId);
         return mIsFaceEnrolled;
     }
@@ -3402,7 +3406,7 @@
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_ASSISTANT_STACK_CHANGED,
                         info.visible));
             } catch (RemoteException e) {
-                Log.e(TAG, "unable to check task stack", e);
+                mLogger.logException(e, "unable to check task stack ");
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 99e0ce2..7a42803 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -271,7 +271,7 @@
      * like fingerprint authentication errors.
      *
      * @param message Message that indicates an error.
-     * @see KeyguardIndicationController.BaseKeyguardCallback#HIDE_DELAY_MS
+     * @see KeyguardIndicationController#DEFAULT_HIDE_DELAY_MS
      * @see KeyguardIndicationController#showTransientIndication(CharSequence)
      */
     public void onTrustAgentErrorMessage(CharSequence message) { }
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/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 035b7f0..6c452bd 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -45,7 +45,7 @@
 
     fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
 
-    fun v(@CompileTimeConstant msg: String) = log(msg, ERROR)
+    fun v(@CompileTimeConstant msg: String) = log(msg, VERBOSE)
 
     fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
 
@@ -198,6 +198,15 @@
                 { "Retrying face after HW unavailable, attempt $int1" })
     }
 
+    fun logRetryAfterFpError(msgId: Int, errString: String?) {
+        logBuffer.log(TAG, DEBUG, {
+            int1 = msgId
+            str1 = "$errString"
+        }, {
+            "Fingerprint retrying auth due to($int1) -> $str1"
+        })
+    }
+
     fun logRetryAfterFpHwUnavailable(retryCount: Int) {
         logBuffer.log(TAG, WARNING,
                 { int1 = retryCount },
@@ -270,12 +279,12 @@
                 { str1 = newTimeFormat },
                 { "handleTimeFormatUpdate timeFormat=$str1" })
     }
-
     fun logUdfpsPointerDown(sensorId: Int) {
         logBuffer.log(TAG, DEBUG,
                 { int1 = sensorId },
                 { "onUdfpsPointerDown, sensorId: $int1" })
     }
+
     fun logUdfpsPointerUp(sensorId: Int) {
         logBuffer.log(TAG, DEBUG,
                 { int1 = sensorId },
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardViewMediatorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardViewMediatorLogger.kt
new file mode 100644
index 0000000..f54bf02
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardViewMediatorLogger.kt
@@ -0,0 +1,690 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.logging
+
+import android.os.RemoteException
+import android.view.WindowManagerPolicyConstants
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.LogLevel.WTF
+import com.android.systemui.log.dagger.KeyguardViewMediatorLog
+import javax.inject.Inject
+
+private const val TAG = "KeyguardViewMediatorLog"
+
+@SysUISingleton
+class KeyguardViewMediatorLogger @Inject constructor(
+        @KeyguardViewMediatorLog private val logBuffer: LogBuffer,
+) {
+
+    fun logFailedLoadLockSound(soundPath: String) {
+        logBuffer.log(
+                TAG,
+                WARNING,
+                { str1 = soundPath },
+                { "failed to load lock sound from $str1" }
+        )
+    }
+
+    fun logFailedLoadUnlockSound(soundPath: String) {
+        logBuffer.log(
+                TAG,
+                WARNING,
+                { str1 = soundPath },
+                { "failed to load unlock sound from $str1" }
+        )
+    }
+
+    fun logFailedLoadTrustedSound(soundPath: String) {
+        logBuffer.log(
+                TAG,
+                WARNING,
+                { str1 = soundPath },
+                { "failed to load trusted sound from $str1" }
+        )
+    }
+
+    fun logOnSystemReady() {
+        logBuffer.log(TAG, DEBUG, "onSystemReady")
+    }
+
+    fun logOnStartedGoingToSleep(offReason: Int) {
+        val offReasonString = WindowManagerPolicyConstants.offReasonToString(offReason)
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                { str1 = offReasonString },
+                { "onStartedGoingToSleep($str1)" }
+        )
+    }
+
+    fun logPendingExitSecureCallbackCancelled() {
+        logBuffer.log(TAG, DEBUG, "pending exit secure callback cancelled")
+    }
+
+    fun logFailedOnKeyguardExitResultFalse(remoteException: RemoteException) {
+        logBuffer.log(
+                TAG,
+                WARNING,
+                "Failed to call onKeyguardExitResult(false)",
+                remoteException
+        )
+    }
+
+    fun logOnFinishedGoingToSleep(offReason: Int) {
+        val offReasonString = WindowManagerPolicyConstants.offReasonToString(offReason)
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                { str1 = offReasonString },
+                { "onFinishedGoingToSleep($str1)" }
+        )
+    }
+
+    fun logPinLockRequestedStartingKeyguard() {
+        logBuffer.log(TAG, INFO, "PIN lock requested, starting keyguard")
+    }
+
+    fun logUserSwitching(userId: Int) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                { int1 = userId },
+                { "onUserSwitching $int1" }
+        )
+    }
+
+    fun logOnUserSwitchComplete(userId: Int) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                { int1 = userId },
+                { "onUserSwitchComplete $int1" }
+        )
+    }
+
+    fun logOnSimStateChanged(subId: Int, slotId: Int, simState: String) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                {
+                    int1 = subId
+                    int2 = slotId
+                    str1 = simState
+                },
+                { "onSimStateChanged(subId=$int1, slotId=$int2, state=$str1)" }
+        )
+    }
+
+    fun logFailedToCallOnSimSecureStateChanged(remoteException: RemoteException) {
+        logBuffer.log(
+                TAG,
+                WARNING,
+                "Failed to call onSimSecureStateChanged",
+                remoteException
+        )
+    }
+
+    fun logIccAbsentIsNotShowing() {
+        logBuffer.log(TAG, DEBUG, "ICC_ABSENT isn't showing, we need to show the " +
+                "keyguard since the device isn't provisioned yet.")
+    }
+
+    fun logSimMovedToAbsent() {
+        logBuffer.log(TAG, DEBUG, "SIM moved to ABSENT when the " +
+                "previous state was locked. Reset the state.")
+    }
+
+    fun logIntentValueIccLocked() {
+        logBuffer.log(TAG, DEBUG, "INTENT_VALUE_ICC_LOCKED and keyguard isn't " +
+                "showing; need to show keyguard so user can enter sim pin")
+    }
+
+    fun logPermDisabledKeyguardNotShowing() {
+        logBuffer.log(TAG, DEBUG, "PERM_DISABLED and keyguard isn't showing.")
+    }
+
+    fun logPermDisabledResetStateLocked() {
+        logBuffer.log(TAG, DEBUG, "PERM_DISABLED, resetStateLocked to show permanently " +
+                "disabled message in lockscreen.")
+    }
+
+    fun logReadyResetState(showing: Boolean) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                { bool1 = showing },
+                { "READY, reset state? $bool1"}
+        )
+    }
+
+    fun logSimMovedToReady() {
+        logBuffer.log(TAG, DEBUG, "SIM moved to READY when the previously was locked. " +
+                "Reset the state.")
+    }
+
+    fun logUnspecifiedSimState(simState: Int) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                { int1 = simState },
+                { "Unspecific state: $int1" }
+        )
+    }
+
+    fun logOccludeLaunchAnimationCancelled(occluded: Boolean) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                { bool1 = occluded },
+                { "Occlude launch animation cancelled. Occluded state is now: $bool1"}
+        )
+    }
+
+    fun logActivityLaunchAnimatorLaunchContainerChanged() {
+        logBuffer.log(TAG, WTF, "Someone tried to change the launch container for the " +
+                "ActivityLaunchAnimator, which should never happen.")
+    }
+
+    fun logVerifyUnlock() {
+        logBuffer.log(TAG, DEBUG, "verifyUnlock")
+    }
+
+    fun logIgnoreUnlockDeviceNotProvisioned() {
+        logBuffer.log(TAG, DEBUG, "ignoring because device isn't provisioned")
+    }
+
+    fun logFailedToCallOnKeyguardExitResultFalse(remoteException: RemoteException) {
+        logBuffer.log(
+                TAG,
+                WARNING,
+                "Failed to call onKeyguardExitResult(false)",
+                remoteException
+        )
+    }
+
+    fun logVerifyUnlockCalledNotExternallyDisabled() {
+        logBuffer.log(TAG, WARNING, "verifyUnlock called when not externally disabled")
+    }
+
+    fun logSetOccluded(isOccluded: Boolean) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                { bool1 = isOccluded },
+                { "setOccluded($bool1)" }
+        )
+    }
+
+    fun logHandleSetOccluded(isOccluded: Boolean) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                { bool1 = isOccluded },
+                { "handleSetOccluded($bool1)" }
+        )
+    }
+
+    fun logIgnoreHandleShow() {
+        logBuffer.log(TAG, DEBUG, "ignoring handleShow because system is not ready.")
+    }
+
+    fun logHandleShow() {
+        logBuffer.log(TAG, DEBUG, "handleShow")
+    }
+
+    fun logHandleHide() {
+        logBuffer.log(TAG, DEBUG, "handleHide")
+    }
+
+    fun logSplitSystemUserQuitUnlocking() {
+        logBuffer.log(TAG, DEBUG, "Split system user, quit unlocking.")
+    }
+
+    fun logHandleStartKeyguardExitAnimation(startTime: Long, fadeoutDuration: Long) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                {
+                    long1 = startTime
+                    long2 = fadeoutDuration
+                },
+                { "handleStartKeyguardExitAnimation startTime=$long1 fadeoutDuration=$long2" }
+        )
+    }
+
+    fun logHandleVerifyUnlock() {
+        logBuffer.log(TAG, DEBUG, "handleVerifyUnlock")
+    }
+
+    fun logHandleNotifyStartedGoingToSleep() {
+        logBuffer.log(TAG, DEBUG, "handleNotifyStartedGoingToSleep")
+    }
+
+    fun logHandleNotifyFinishedGoingToSleep() {
+        logBuffer.log(TAG, DEBUG, "handleNotifyFinishedGoingToSleep")
+    }
+
+    fun logHandleNotifyWakingUp() {
+        logBuffer.log(TAG, DEBUG, "handleNotifyWakingUp")
+    }
+
+    fun logHandleReset() {
+        logBuffer.log(TAG, DEBUG, "handleReset")
+    }
+
+    fun logKeyguardDone() {
+        logBuffer.log(TAG, DEBUG, "keyguardDone")
+    }
+
+    fun logKeyguardDonePending() {
+        logBuffer.log(TAG, DEBUG, "keyguardDonePending")
+    }
+
+    fun logKeyguardGone() {
+        logBuffer.log(TAG, DEBUG, "keyguardGone")
+    }
+
+    fun logUnoccludeAnimationCancelled(isOccluded: Boolean) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                { bool1 = isOccluded },
+                { "Unocclude animation cancelled. Occluded state is now: $bool1" }
+        )
+    }
+
+    fun logShowLocked() {
+        logBuffer.log(TAG, DEBUG, "showLocked")
+    }
+
+    fun logHideLocked() {
+        logBuffer.log(TAG, DEBUG, "hideLocked")
+    }
+
+    fun logResetStateLocked() {
+        logBuffer.log(TAG, DEBUG, "resetStateLocked")
+    }
+
+    fun logNotifyStartedGoingToSleep() {
+        logBuffer.log(TAG, DEBUG, "notifyStartedGoingToSleep")
+    }
+
+    fun logNotifyFinishedGoingToSleep() {
+        logBuffer.log(TAG, DEBUG, "notifyFinishedGoingToSleep")
+    }
+
+    fun logNotifyStartedWakingUp() {
+        logBuffer.log(TAG, DEBUG, "notifyStartedWakingUp")
+    }
+
+    fun logDoKeyguardShowingLockScreen() {
+        logBuffer.log(TAG, DEBUG, "doKeyguard: showing the lock screen")
+    }
+
+    fun logDoKeyguardNotShowingLockScreenOff() {
+        logBuffer.log(TAG, DEBUG, "doKeyguard: not showing because lockscreen is off")
+    }
+
+    fun logDoKeyguardNotShowingDeviceNotProvisioned() {
+        logBuffer.log(TAG, DEBUG, "doKeyguard: not showing because device isn't " +
+                "provisioned and the sim is not locked or missing")
+    }
+
+    fun logDoKeyguardNotShowingAlreadyShowing() {
+        logBuffer.log(TAG, DEBUG, "doKeyguard: not showing because it is already showing")
+    }
+
+    fun logDoKeyguardNotShowingBootingCryptkeeper() {
+        logBuffer.log(TAG, DEBUG, "doKeyguard: not showing because booting to cryptkeeper")
+    }
+
+    fun logDoKeyguardNotShowingExternallyDisabled() {
+        logBuffer.log(TAG, DEBUG, "doKeyguard: not showing because externally disabled")
+    }
+
+    fun logFailedToCallOnDeviceProvisioned(remoteException: RemoteException) {
+        logBuffer.log(
+                TAG,
+                WARNING,
+                "Failed to call onDeviceProvisioned",
+                remoteException
+        )
+    }
+
+    fun logMaybeHandlePendingLockNotHandling() {
+        logBuffer.log(TAG, DEBUG, "#maybeHandlePendingLock: not handling because the " +
+                "screen off animation's isKeyguardShowDelayed() returned true. This should be " +
+                "handled soon by #onStartedWakingUp, or by the end actions of the " +
+                "screen off animation.")
+    }
+
+    fun logMaybeHandlePendingLockKeyguardGoingAway() {
+        logBuffer.log(TAG, DEBUG, "#maybeHandlePendingLock: not handling because the " +
+                "keyguard is going away. This should be handled shortly by " +
+                "StatusBar#finishKeyguardFadingAway.")
+    }
+
+    fun logMaybeHandlePendingLockHandling() {
+        logBuffer.log(TAG, DEBUG, "#maybeHandlePendingLock: handling pending lock; " +
+                "locking keyguard.")
+    }
+
+    fun logSetAlarmToTurnOffKeyguard(delayedShowingSequence: Int) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                { int1 = delayedShowingSequence },
+                { "setting alarm to turn off keyguard, seq = $int1" }
+        )
+    }
+
+    fun logOnStartedWakingUp(delayedShowingSequence: Int) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                { int1 = delayedShowingSequence },
+                { "onStartedWakingUp, seq = $int1" }
+        )
+    }
+
+    fun logSetKeyguardEnabled(enabled: Boolean) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                { bool1 = enabled },
+                { "setKeyguardEnabled($bool1)" }
+        )
+    }
+
+    fun logIgnoreVerifyUnlockRequest() {
+        logBuffer.log(TAG, DEBUG, "in process of verifyUnlock request, ignoring")
+    }
+
+    fun logRememberToReshowLater() {
+        logBuffer.log(TAG, DEBUG, "remembering to reshow, hiding keyguard, disabling " +
+                "status bar expansion")
+    }
+
+    fun logPreviouslyHiddenReshow() {
+        logBuffer.log(TAG, DEBUG, "previously hidden, reshowing, reenabling status " +
+                "bar expansion")
+    }
+
+    fun logOnKeyguardExitResultFalseResetting() {
+        logBuffer.log(TAG, DEBUG, "onKeyguardExitResult(false), resetting")
+    }
+
+    fun logWaitingUntilKeyguardVisibleIsFalse() {
+        logBuffer.log(TAG, DEBUG, "waiting until mWaitingUntilKeyguardVisible is false")
+    }
+
+    fun logDoneWaitingUntilKeyguardVisible() {
+        logBuffer.log(TAG, DEBUG, "done waiting for mWaitingUntilKeyguardVisible")
+    }
+
+    fun logUnoccludeAnimatorOnAnimationStart() {
+        logBuffer.log(TAG, DEBUG, "UnoccludeAnimator#onAnimationStart. " +
+                "Set occluded = false.")
+    }
+
+    fun logNoAppsProvidedToUnoccludeRunner() {
+        logBuffer.log(TAG, DEBUG, "No apps provided to unocclude runner; " +
+                "skipping animation and unoccluding.")
+    }
+
+    fun logReceivedDelayedKeyguardAction(sequence: Int, delayedShowingSequence: Int) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                {
+                    int1 = sequence
+                    int2 = delayedShowingSequence
+                },
+                {
+                    "received DELAYED_KEYGUARD_ACTION with seq = $int1 " +
+                            "mDelayedShowingSequence = $int2"
+                }
+        )
+    }
+
+    fun logTimeoutWhileActivityDrawn() {
+        logBuffer.log(TAG, WARNING, "Timeout while waiting for activity drawn")
+    }
+
+    fun logTryKeyguardDonePending(
+            keyguardDonePending: Boolean,
+            hideAnimationRun: Boolean,
+            hideAnimationRunning: Boolean
+    ) {
+        logBuffer.log(TAG, DEBUG,
+                {
+                    bool1 = keyguardDonePending
+                    bool2 = hideAnimationRun
+                    bool3 = hideAnimationRunning
+                },
+                { "tryKeyguardDone: pending - $bool1, animRan - $bool2 animRunning - $bool3" }
+        )
+    }
+
+    fun logTryKeyguardDonePreHideAnimation() {
+        logBuffer.log(TAG, DEBUG, "tryKeyguardDone: starting pre-hide animation")
+    }
+
+    fun logHandleKeyguardDone() {
+        logBuffer.log(TAG, DEBUG, "handleKeyguardDone")
+    }
+
+    fun logDeviceGoingToSleep() {
+        logBuffer.log(TAG, INFO, "Device is going to sleep, aborting keyguardDone")
+    }
+
+    fun logFailedToCallOnKeyguardExitResultTrue(remoteException: RemoteException) {
+        logBuffer.log(
+                TAG,
+                WARNING,
+                "Failed to call onKeyguardExitResult(true)",
+                remoteException
+        )
+    }
+
+    fun logHandleKeyguardDoneDrawing() {
+        logBuffer.log(TAG, DEBUG, "handleKeyguardDoneDrawing")
+    }
+
+    fun logHandleKeyguardDoneDrawingNotifyingKeyguardVisible() {
+        logBuffer.log(TAG, DEBUG, "handleKeyguardDoneDrawing: notifying " +
+                "mWaitingUntilKeyguardVisible")
+    }
+
+    fun logUpdateActivityLockScreenState(showing: Boolean, aodShowing: Boolean) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                {
+                    bool1 = showing
+                    bool2 = aodShowing
+                },
+                { "updateActivityLockScreenState($bool1, $bool2)" }
+        )
+    }
+
+    fun logFailedToCallSetLockScreenShown(remoteException: RemoteException) {
+        logBuffer.log(
+                TAG,
+                WARNING,
+                "Failed to call setLockScreenShown",
+                remoteException
+        )
+    }
+
+    fun logKeyguardGoingAway() {
+        logBuffer.log(TAG, DEBUG, "keyguardGoingAway")
+    }
+
+    fun logFailedToCallKeyguardGoingAway(keyguardFlag: Int, remoteException: RemoteException) {
+        logBuffer.log(
+                TAG,
+                ERROR,
+                { int1 = keyguardFlag },
+                { "Failed to call keyguardGoingAway($int1)" },
+                remoteException
+        )
+    }
+
+    fun logHideAnimationFinishedRunnable() {
+        logBuffer.log(TAG, WARNING, "mHideAnimationFinishedRunnable#run")
+    }
+
+    fun logFailedToCallOnAnimationFinished(remoteException: RemoteException) {
+        logBuffer.log(
+                TAG,
+                WARNING,
+                "Failed to call onAnimationFinished",
+                remoteException
+        )
+    }
+
+    fun logFailedToCallOnAnimationStart(remoteException: RemoteException) {
+        logBuffer.log(
+                TAG,
+                WARNING,
+                "Failed to call onAnimationStart",
+                remoteException
+        )
+    }
+
+    fun logOnKeyguardExitRemoteAnimationFinished() {
+        logBuffer.log(TAG, DEBUG, "onKeyguardExitRemoteAnimationFinished")
+    }
+
+    fun logSkipOnKeyguardExitRemoteAnimationFinished(
+            cancelled: Boolean,
+            surfaceBehindRemoteAnimationRunning: Boolean,
+            surfaceBehindRemoteAnimationRequested: Boolean
+    ) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                {
+                    bool1 = cancelled
+                    bool2 = surfaceBehindRemoteAnimationRunning
+                    bool3 = surfaceBehindRemoteAnimationRequested
+                },
+                {
+                    "skip onKeyguardExitRemoteAnimationFinished cancelled=$bool1 " +
+                            "surfaceAnimationRunning=$bool2 " +
+                            "surfaceAnimationRequested=$bool3"
+                }
+        )
+    }
+
+    fun logOnKeyguardExitRemoteAnimationFinishedHideKeyguardView() {
+        logBuffer.log(TAG, DEBUG, "onKeyguardExitRemoteAnimationFinished" +
+                "#hideKeyguardViewAfterRemoteAnimation")
+    }
+
+    fun logSkipHideKeyguardViewAfterRemoteAnimation(
+            dismissingFromSwipe: Boolean,
+            wasShowing: Boolean
+    ) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                {
+                    bool1 = dismissingFromSwipe
+                    bool2 = wasShowing
+                },
+                {
+                    "skip hideKeyguardViewAfterRemoteAnimation dismissFromSwipe=$bool1 " +
+                            "wasShowing=$bool2"
+                }
+        )
+    }
+
+    fun logCouldNotGetStatusBarManager() {
+        logBuffer.log(TAG, WARNING, "Could not get status bar manager")
+    }
+
+    fun logAdjustStatusBarLocked(
+            showing: Boolean,
+            occluded: Boolean,
+            secure: Boolean,
+            forceHideHomeRecentsButtons: Boolean,
+            flags: String
+    ) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                {
+                    bool1 = showing
+                    bool2 = occluded
+                    bool3 = secure
+                    bool4 = forceHideHomeRecentsButtons
+                    str3 = flags
+                },
+                {
+                    "adjustStatusBarLocked: mShowing=$bool1 mOccluded=$bool2 isSecure=$bool3 " +
+                            "force=$bool4 --> flags=0x$str3"
+                }
+        )
+    }
+
+    fun logFailedToCallOnShowingStateChanged(remoteException: RemoteException) {
+        logBuffer.log(
+                TAG,
+                WARNING,
+                "Failed to call onShowingStateChanged",
+                remoteException
+        )
+    }
+
+    fun logFailedToCallNotifyTrustedChangedLocked(remoteException: RemoteException) {
+        logBuffer.log(
+                TAG,
+                WARNING,
+                "Failed to call notifyTrustedChangedLocked",
+                remoteException
+        )
+    }
+
+    fun logFailedToCallIKeyguardStateCallback(remoteException: RemoteException) {
+        logBuffer.log(
+                TAG,
+                WARNING,
+                "Failed to call to IKeyguardStateCallback",
+                remoteException
+        )
+    }
+
+    fun logOccludeAnimatorOnAnimationStart() {
+        logBuffer.log(TAG, DEBUG, "OccludeAnimator#onAnimationStart. Set occluded = true.")
+    }
+
+    fun logOccludeAnimationCancelledByWm(isKeyguardOccluded: Boolean) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                { bool1 = isKeyguardOccluded },
+                { "Occlude animation cancelled by WM. Setting occluded state to: $bool1" }
+        )
+    }
+}
\ No newline at end of file
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/accessibility/MagnificationGestureDetector.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java
index 4b30ec3..c91082c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java
@@ -23,6 +23,7 @@
 import android.os.Handler;
 import android.view.Display;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.ViewConfiguration;
 
 /**
@@ -41,7 +42,7 @@
          *
          * @return {@code true} if this gesture is handled.
          */
-        boolean onSingleTap();
+        boolean onSingleTap(View view);
 
         /**
          * Called when the user is performing dragging gesture. It is started after the offset
@@ -52,7 +53,7 @@
          * @param offsetY The Y offset in screen coordinate.
          * @return {@code true} if this gesture is handled.
          */
-        boolean onDrag(float offsetX, float offsetY);
+        boolean onDrag(View view, float offsetX, float offsetY);
 
         /**
          * Notified when a tap occurs with the down {@link MotionEvent} that triggered it. This will
@@ -109,7 +110,7 @@
      * @param event The current motion event.
      * @return {@code True} if the {@link OnGestureListener} consumes the event, else false.
      */
-    boolean onTouch(MotionEvent event) {
+    boolean onTouch(View view, MotionEvent event) {
         final float rawX = event.getRawX();
         final float rawY = event.getRawY();
         boolean handled = false;
@@ -125,12 +126,12 @@
                 break;
             case MotionEvent.ACTION_MOVE:
                 stopSingleTapDetectionIfNeeded(rawX, rawY);
-                handled |= notifyDraggingGestureIfNeeded(rawX, rawY);
+                handled |= notifyDraggingGestureIfNeeded(view, rawX, rawY);
                 break;
             case MotionEvent.ACTION_UP:
                 stopSingleTapDetectionIfNeeded(rawX, rawY);
                 if (mDetectSingleTap) {
-                    handled |= mOnGestureListener.onSingleTap();
+                    handled |= mOnGestureListener.onSingleTap(view);
                 }
                 // Fall through
             case MotionEvent.ACTION_CANCEL:
@@ -163,7 +164,7 @@
         mDetectSingleTap = false;
     }
 
-    private boolean notifyDraggingGestureIfNeeded(float x, float y) {
+    private boolean notifyDraggingGestureIfNeeded(View view, float x, float y) {
         if (!mDraggingDetected) {
             return false;
         }
@@ -173,7 +174,7 @@
         final float offsetX = x - mPointerLocation.x;
         final float offsetY = y - mPointerLocation.y;
         mPointerLocation.set(x, y);
-        return mOnGestureListener.onDrag(offsetX, offsetY);
+        return mOnGestureListener.onDrag(view, offsetX, offsetY);
     }
 
     private void reset() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index dbd215d..59a5b15 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -213,18 +213,18 @@
         if (!mIsVisible) {
             return false;
         }
-        return mGestureDetector.onTouch(event);
+        return mGestureDetector.onTouch(v, event);
     }
 
     @Override
-    public boolean onSingleTap() {
+    public boolean onSingleTap(View v) {
         mSingleTapDetected = true;
         handleSingleTap();
         return true;
     }
 
     @Override
-    public boolean onDrag(float offsetX, float offsetY) {
+    public boolean onDrag(View v, float offsetX, float offsetY) {
         moveButton(offsetX, offsetY);
         return true;
     }
@@ -292,9 +292,12 @@
      * @param resetPosition if the button position needs be reset
      */
     private void showButton(int mode, boolean resetPosition) {
+        if (mode != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
+            return;
+        }
         if (mMagnificationMode != mode) {
             mMagnificationMode = mode;
-            mImageView.setImageResource(getIconResId(mode));
+            mImageView.setImageResource(getIconResId(mMagnificationMode));
         }
         if (!mIsVisible) {
             onConfigurationChanged(mContext.getResources().getConfiguration());
@@ -408,6 +411,7 @@
 
     private static ImageView createView(Context context) {
         ImageView imageView = new ImageView(context);
+        imageView.setScaleType(ImageView.ScaleType.CENTER);
         imageView.setClickable(true);
         imageView.setFocusable(true);
         imageView.setAlpha(0f);
@@ -415,10 +419,8 @@
     }
 
     @VisibleForTesting
-    static int getIconResId(int mode) {
-        return (mode == Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)
-                ? R.drawable.ic_open_in_new_window
-                : R.drawable.ic_open_in_new_fullscreen;
+    static int getIconResId(int mode) { // TODO(b/242233514): delete non used param
+        return R.drawable.ic_open_in_new_window;
     }
 
     private static LayoutParams createLayoutParams(Context context) {
@@ -461,4 +463,4 @@
                             new Rect(0, 0, mImageView.getWidth(), mImageView.getHeight())));
         });
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index d7fead1..f4701ed 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -233,6 +233,13 @@
     }
 
     @Override
+    public void onModeSwitch(int displayId, int newMode) {
+        if (mWindowMagnificationConnectionImpl != null) {
+            mWindowMagnificationConnectionImpl.onChangeMagnificationMode(displayId, newMode);
+        }
+    }
+
+    @Override
     public void requestWindowMagnificationConnection(boolean connect) {
         if (connect) {
             setWindowMagnificationConnection();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 1eedae6..813f4dd 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -19,8 +19,11 @@
 import static android.view.WindowInsets.Type.systemGestures;
 import static android.view.WindowManager.LayoutParams;
 
+import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
 
+import static java.lang.Math.abs;
+
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.annotation.NonNull;
@@ -41,6 +44,8 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.Log;
 import android.util.Range;
 import android.util.Size;
@@ -62,6 +67,7 @@
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
 import android.widget.FrameLayout;
+import android.widget.ImageView;
 
 import androidx.core.math.MathUtils;
 
@@ -93,6 +99,7 @@
     private static final Range<Float> A11Y_ACTION_SCALE_RANGE = new Range<>(2.0f, 8.0f);
     private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f;
     private static final float ANIMATION_BOUNCE_EFFECT_SCALE = 1.05f;
+    private static final float[] MAGNIFICATION_SCALE_OPTIONS = {1.0f, 1.4f, 1.8f, 2.5f};
 
     private final Context mContext;
     private final Resources mResources;
@@ -145,7 +152,8 @@
     // The root of the mirrored content
     private SurfaceControl mMirrorSurface;
 
-    private View mDragView;
+    private ImageView mDragView;
+    private ImageView mCloseView;
     private View mLeftDrag;
     private View mTopDrag;
     private View mRightDrag;
@@ -162,6 +170,7 @@
     private final Runnable mWindowInsetChangeRunnable;
     // MirrorView is the mirror window which displays the magnified content.
     private View mMirrorView;
+    private View mMirrorBorderView;
     private SurfaceView mMirrorSurfaceView;
     private int mMirrorSurfaceMargin;
     private int mBorderDragSize;
@@ -172,6 +181,7 @@
      * repositions to the other side.
      */
     private int mButtonRepositionThresholdFromEdge;
+
     // The boundary of magnification frame.
     private final Rect mMagnificationFrameBoundary = new Rect();
     // The top Y of the system gesture rect at the bottom. Set to -1 if it is invalid.
@@ -192,6 +202,18 @@
     private boolean mOverlapWithGestureInsets;
     private boolean mIsDragging;
 
+    // Window Magnification Setting view
+    private WindowMagnificationSettings mWindowMagnificationSettings;
+
+    private static final int MAX_HORIZONTAL_MOVE_ANGLE = 50;
+    private static final int HORIZONTAL = 1;
+    private static final int VERTICAL = 0;
+    private static final double HORIZONTAL_LOCK_BASE =
+            Math.tan(Math.toRadians(MAX_HORIZONTAL_MOVE_ANGLE));
+
+    private boolean mAllowDiagonalScrolling = false;
+    private boolean mEditSizeEnable = false;
+
     @Nullable
     private MirrorWindowControl mMirrorWindowControl;
 
@@ -223,7 +245,12 @@
         mWindowBounds = new Rect(mWm.getCurrentWindowMetrics().getBounds());
 
         mResources = mContext.getResources();
-        mScale = mResources.getInteger(R.integer.magnification_default_scale);
+        mScale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+                mResources.getInteger(R.integer.magnification_default_scale),
+                UserHandle.USER_CURRENT);
+
+
         mBounceEffectDuration = mResources.getInteger(
                 com.android.internal.R.integer.config_shortAnimTime);
         updateDimensions();
@@ -241,6 +268,10 @@
         mGestureDetector =
                 new MagnificationGestureDetector(mContext, handler, this);
 
+        mWindowMagnificationSettings =
+                new WindowMagnificationSettings(mContext, mWindowMagnificationSettingsCallback,
+                        mSfVsyncFrameProvider);
+
         // Initialize listeners.
         mMirrorViewRunnable = () -> {
             if (mMirrorView != null) {
@@ -326,6 +357,26 @@
         return false;
     }
 
+    private void changeMagnificationSize(@MagnificationSize int index) {
+        final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
+        int size = (int) (initSize * MAGNIFICATION_SCALE_OPTIONS[index]);
+        setWindowSize(size, size);
+    }
+
+    private void setEditMagnifierSizeMode(boolean enable) {
+        mEditSizeEnable = enable;
+        applyResourcesValues();
+
+        if (isWindowVisible()) {
+            updateDimensions();
+            applyTapExcludeRegion();
+        }
+    }
+
+    private void setDiagonalScrolling(boolean enable) {
+        mAllowDiagonalScrolling = enable;
+    }
+
     /**
      * Wraps {@link WindowMagnificationController#deleteWindowMagnification()}} with transition
      * animation. If the window magnification is enabling, it runs the animation in reverse.
@@ -346,6 +397,9 @@
         if (!isWindowVisible()) {
             return;
         }
+
+        closeMagnificationSettings();
+
         if (mMirrorSurface != null) {
             mTransaction.remove(mMirrorSurface).apply();
             mMirrorSurface = null;
@@ -368,6 +422,8 @@
         mMirrorViewBounds.setEmpty();
         mSourceBounds.setEmpty();
         updateSystemUIStateIfNeeded();
+        setEditMagnifierSizeMode(false);
+
         mContext.unregisterComponentCallbacks(this);
         // Notify source bounds empty when magnification is deleted.
         mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, new Rect());
@@ -505,7 +561,7 @@
 
     /** Returns the rotation degree change of two {@link Surface.Rotation} */
     private int getDegreeFromRotation(@Surface.Rotation int newRotation,
-            @Surface.Rotation int oldRotation) {
+                                      @Surface.Rotation int oldRotation) {
         final int rotationDiff = oldRotation - newRotation;
         final int degree = (rotationDiff + 4) % 4 * 90;
         return degree;
@@ -534,6 +590,8 @@
         mMirrorView = LayoutInflater.from(mContext).inflate(R.layout.window_magnifier_view, null);
         mMirrorSurfaceView = mMirrorView.findViewById(R.id.surface_view);
 
+        mMirrorBorderView = mMirrorView.findViewById(R.id.magnification_inner_border);
+
         // Allow taps to go through to the mirror SurfaceView below.
         mMirrorSurfaceView.addOnLayoutChangeListener(mMirrorSurfaceViewLayoutChangeListener);
 
@@ -600,6 +658,18 @@
         }
     }
 
+    private void showMagnificationSettings() {
+        if (mWindowMagnificationSettings != null) {
+            mWindowMagnificationSettings.showSettingPanel();
+        }
+    }
+
+    private void closeMagnificationSettings() {
+        if (mWindowMagnificationSettings != null) {
+            mWindowMagnificationSettings.hideSettingPanel();
+        }
+    }
+
     /**
      * Sets the window size with given width and height in pixels without changing the
      * window center. The width or the height will be clamped in the range
@@ -668,12 +738,14 @@
         mTopDrag = mMirrorView.findViewById(R.id.top_handle);
         mRightDrag = mMirrorView.findViewById(R.id.right_handle);
         mBottomDrag = mMirrorView.findViewById(R.id.bottom_handle);
+        mCloseView = mMirrorView.findViewById(R.id.close_button);
 
         mDragView.setOnTouchListener(this);
         mLeftDrag.setOnTouchListener(this);
         mTopDrag.setOnTouchListener(this);
         mRightDrag.setOnTouchListener(this);
         mBottomDrag.setOnTouchListener(this);
+        mCloseView.setOnTouchListener(this);
     }
 
     /**
@@ -743,8 +815,8 @@
     @Override
     public boolean onTouch(View v, MotionEvent event) {
         if (v == mDragView || v == mLeftDrag || v == mTopDrag || v == mRightDrag
-                || v == mBottomDrag) {
-            return mGestureDetector.onTouch(event);
+                || v == mBottomDrag || v == mCloseView) {
+            return mGestureDetector.onTouch(v, event);
         }
         return false;
     }
@@ -768,6 +840,7 @@
         int right = displayFrame.right - (halfWidth - (int) (halfWidth / scale));
         int top = displayFrame.top + (halfHeight - (int) (halfHeight / scale));
         int bottom = displayFrame.bottom - (halfHeight - (int) (halfHeight / scale));
+
         mSourceBounds.set(left, top, right, bottom);
 
         // SourceBound's center is equal to center[X,Y] but calculated from MagnificationFrame's
@@ -950,7 +1023,7 @@
      *                                       or {@link Float#NaN} to leave unchanged.
      */
     void enableWindowMagnificationInternal(float scale, float centerX, float centerY,
-            float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY) {
+                float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY) {
         if (Float.compare(scale, 1.0f)  <= 0) {
             deleteWindowMagnification();
             return;
@@ -983,6 +1056,7 @@
         if (!isWindowVisible()) {
             createMirrorWindow();
             showControls();
+            applyResourcesValues();
         } else {
             modifyWindowMagnification(false);
         }
@@ -997,6 +1071,7 @@
         if (mAnimationController.isAnimating() || !isWindowVisible() || mScale == scale) {
             return;
         }
+
         enableWindowMagnificationInternal(scale, Float.NaN, Float.NaN);
         mHandler.removeCallbacks(mUpdateStateDescriptionRunnable);
         mHandler.postDelayed(mUpdateStateDescriptionRunnable, UPDATE_STATE_DESCRIPTION_DELAY_MS);
@@ -1014,19 +1089,42 @@
         if (mAnimationController.isAnimating() || mMirrorSurfaceView == null) {
             return;
         }
+
+        if (!mAllowDiagonalScrolling) {
+            int direction = selectDirectionForMove(abs(offsetX), abs(offsetY));
+
+            if (direction == HORIZONTAL) {
+                offsetY = 0;
+            } else {
+                offsetX = 0;
+            }
+        }
+
         if (updateMagnificationFramePosition((int) offsetX, (int) offsetY)) {
             modifyWindowMagnification(false);
         }
     }
 
     void moveWindowMagnifierToPosition(float positionX, float positionY,
-            IRemoteMagnificationAnimationCallback callback) {
+                                       IRemoteMagnificationAnimationCallback callback) {
         if (mMirrorSurfaceView == null) {
             return;
         }
         mAnimationController.moveWindowMagnifierToPosition(positionX, positionY, callback);
     }
 
+    private int selectDirectionForMove(float diffX, float diffY) {
+        int direction = 0;
+        float result = diffY / diffX;
+
+        if (result <= HORIZONTAL_LOCK_BASE) {
+            direction = HORIZONTAL;  // horizontal move
+        } else {
+            direction = VERTICAL;  // vertical move
+        }
+        return direction;
+    }
+
     /**
      * Gets the scale.
      *
@@ -1072,17 +1170,143 @@
     }
 
     @Override
-    public boolean onSingleTap() {
-        animateBounceEffect();
+    public boolean onSingleTap(View view) {
+        handleSingleTap(view);
         return true;
     }
 
     @Override
-    public boolean onDrag(float offsetX, float offsetY) {
-        move((int) offsetX, (int) offsetY);
+    public boolean onDrag(View view, float offsetX, float offsetY) {
+        if (mEditSizeEnable) {
+            changeWindowSize(view, offsetX, offsetY);
+        } else {
+            move((int) offsetX, (int) offsetY);
+        }
         return true;
     }
 
+    private void handleSingleTap(View view) {
+        int id = view.getId();
+        if (id == R.id.drag_handle) {
+            showMagnificationSettings();
+        } else if (id == R.id.close_button) {
+            setEditMagnifierSizeMode(false);
+        } else {
+            animateBounceEffect();
+        }
+    }
+
+    private void applyResourcesValues() {
+        mMirrorBorderView.setBackgroundColor(mResources.getColor(mEditSizeEnable
+                ? R.color.magnification_border_color_change : R.color.magnification_border_color));
+
+        if (mEditSizeEnable) {
+            mDragView.setVisibility(View.GONE);
+            mCloseView.setVisibility(View.VISIBLE);
+        } else {
+            mDragView.setVisibility(View.VISIBLE);
+            mCloseView.setVisibility(View.GONE);
+        }
+    }
+
+    public boolean changeWindowSize(View view, float offsetX, float offsetY) {
+        boolean bRTL = isRTL(mContext);
+        final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
+
+        final int maxHeightSize = mWindowBounds.height() - 2 * mMirrorSurfaceMargin;
+        final int maxWidthSize = mWindowBounds.width() - 2 * mMirrorSurfaceMargin;
+
+        Rect tempRect = new Rect();
+        tempRect.set(mMagnificationFrame);
+
+        if (view == mLeftDrag) {
+            if (bRTL) {
+                tempRect.right += offsetX;
+                if (tempRect.right > mWindowBounds.width()) {
+                    return false;
+                }
+            } else {
+                tempRect.left += offsetX;
+                if (tempRect.left < 0) {
+                    return false;
+                }
+            }
+        } else if (view == mRightDrag) {
+            if (bRTL) {
+                tempRect.left += offsetX;
+                if (tempRect.left < 0) {
+                    return false;
+                }
+            } else {
+                tempRect.right += offsetX;
+                if (tempRect.right > mWindowBounds.width()) {
+                    return false;
+                }
+            }
+        } else if (view == mTopDrag) {
+            tempRect.top += offsetY;
+            if (tempRect.top < 0) {
+                return false;
+            }
+        } else if (view == mBottomDrag) {
+            tempRect.bottom += offsetY;
+            if (tempRect.bottom > mWindowBounds.height()) {
+                return false;
+            }
+        }
+
+        if (tempRect.width() < initSize || tempRect.height() < initSize
+                || tempRect.width() > maxWidthSize || tempRect.height() > maxHeightSize) {
+            return false;
+        }
+
+        mMagnificationFrame.set(tempRect);
+
+        computeBounceAnimationScale();
+        calculateMagnificationFrameBoundary();
+
+        modifyWindowMagnification(true);
+        return true;
+    }
+
+    private static boolean isRTL(Context context) {
+        Configuration config = context.getResources().getConfiguration();
+        if (config == null) {
+            return false;
+        }
+        return (config.screenLayout & Configuration.SCREENLAYOUT_LAYOUTDIR_MASK)
+                == Configuration.SCREENLAYOUT_LAYOUTDIR_RTL;
+    }
+
+    private WindowMagnificationSettingsCallback mWindowMagnificationSettingsCallback =
+            new WindowMagnificationSettingsCallback() {
+        @Override
+        public void onSetDiagonalScrolling(boolean enable) {
+            setDiagonalScrolling(enable);
+        }
+
+        @Override
+        public void onModeSwitch(int newMode) {
+            mWindowMagnifierCallback.onModeSwitch(mDisplayId, newMode);
+        }
+
+        @Override
+        public void onSetMagnifierSize(@MagnificationSize int index) {
+            changeMagnificationSize(index);
+        }
+
+        @Override
+        public void onEditMagnifierSizeMode(boolean enable) {
+            setEditMagnifierSizeMode(enable);
+        }
+
+        @Override
+        public void onMagnifierScale(float scale) {
+            mWindowMagnifierCallback.onPerformScaleAction(mDisplayId,
+                    A11Y_ACTION_SCALE_RANGE.clamp(scale));
+        }
+    };
+
     @Override
     public boolean onStart(float x, float y) {
         mIsDragging = true;
@@ -1138,7 +1362,7 @@
         pw.println("      mMagnificationFrame:"
                 + (isWindowVisible() ? mMagnificationFrame : "empty"));
         pw.println("      mSourceBounds:"
-                 + (mSourceBounds.isEmpty() ? "empty" : mSourceBounds));
+                + (mSourceBounds.isEmpty() ? "empty" : mSourceBounds));
         pw.println("      mSystemGestureTop:" + mSystemGestureTop);
         pw.println("      mMagnificationFrameOffsetX:" + mMagnificationFrameOffsetX);
         pw.println("      mMagnificationFrameOffsetY:" + mMagnificationFrameOffsetY);
@@ -1149,6 +1373,11 @@
         @Override
         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
             super.onInitializeAccessibilityNodeInfo(host, info);
+            final AccessibilityAction clickAction = new AccessibilityAction(
+                    AccessibilityAction.ACTION_CLICK.getId(), mContext.getResources().getString(
+                    R.string.magnification_mode_switch_click_label));
+            info.addAction(clickAction);
+            info.setClickable(true);
             info.addAction(
                     new AccessibilityAction(R.id.accessibility_action_zoom_in,
                             mContext.getString(R.string.accessibility_control_zoom_in)));
@@ -1176,7 +1405,10 @@
         }
 
         private boolean performA11yAction(int action) {
-            if (action == R.id.accessibility_action_zoom_in) {
+            if (action == AccessibilityAction.ACTION_CLICK.getId()) {
+                // Simulate tapping the drag view so it opens the Settings.
+                handleSingleTap(mDragView);
+            } else if (action == R.id.accessibility_action_zoom_in) {
                 final float scale = mScale + A11Y_CHANGE_SCALE_DIFFERENCE;
                 mWindowMagnifierCallback.onPerformScaleAction(mDisplayId,
                         A11Y_ACTION_SCALE_RANGE.clamp(scale));
@@ -1199,4 +1431,4 @@
             return true;
         }
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
new file mode 100644
index 0000000..9cffd5d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -0,0 +1,592 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.annotation.IntDef;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.graphics.Insets;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.MathUtils;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.WindowMetrics;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.Switch;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+
+/**
+ * Class to set value about WindowManificationSettings.
+ */
+class WindowMagnificationSettings implements MagnificationGestureDetector.OnGestureListener {
+    private static final String TAG = "WindowMagnificationSettings";
+    private final Context mContext;
+    private final AccessibilityManager mAccessibilityManager;
+    private final WindowManager mWindowManager;
+
+    private final Runnable mWindowInsetChangeRunnable;
+    private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
+
+    private final LayoutParams mParams;
+    @VisibleForTesting
+    final Rect mDraggableWindowBounds = new Rect();
+    private boolean mIsVisible = false;
+    private final MagnificationGestureDetector mGestureDetector;
+    private boolean mSingleTapDetected = false;
+
+    private SeekBar mZoomSeekbar;
+    private Switch mAllowDiagonalScrollingSwitch;
+    private LinearLayout mPanelView;
+    private LinearLayout mSettingView;
+    private LinearLayout mButtonView;
+    private ImageButton mSmallButton;
+    private ImageButton mMediumButton;
+    private ImageButton mLargeButton;
+    private Button mCloseButton;
+    private Button mEditButton;
+    private ImageButton mChangeModeButton;
+    private boolean mAllowDiagonalScrolling = false;
+    private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f;
+    private static final float A11Y_SCALE_MIN_VALUE = 2.0f;
+    private WindowMagnificationSettingsCallback mCallback;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            MagnificationSize.NONE,
+            MagnificationSize.SMALL,
+            MagnificationSize.MEDIUM,
+            MagnificationSize.LARGE,
+    })
+    /** Denotes the Magnification size type. */
+    @interface MagnificationSize {
+        int NONE = 0;
+        int SMALL  = 1;
+        int MEDIUM = 2;
+        int LARGE = 3;
+    }
+
+    @VisibleForTesting
+    WindowMagnificationSettings(Context context, WindowMagnificationSettingsCallback callback,
+                           SfVsyncFrameCallbackProvider sfVsyncFrameProvider) {
+        mContext = context;
+        mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
+        mWindowManager = mContext.getSystemService(WindowManager.class);
+        mSfVsyncFrameProvider = sfVsyncFrameProvider;
+        mCallback = callback;
+
+        mAllowDiagonalScrolling = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, 0,
+                UserHandle.USER_CURRENT) == 1;
+
+        float scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0,
+                UserHandle.USER_CURRENT);
+
+        mSettingView = (LinearLayout) View.inflate(context,
+                R.layout.window_magnification_settings_view, null);
+
+        mSettingView.setClickable(true);
+        mSettingView.setFocusable(true);
+        mSettingView.setOnTouchListener(this::onTouch);
+
+        mPanelView = mSettingView.findViewById(R.id.magnifier_panel_view);
+        mSmallButton = mSettingView.findViewById(R.id.magnifier_small_button);
+        mMediumButton = mSettingView.findViewById(R.id.magnifier_medium_button);
+        mLargeButton = mSettingView.findViewById(R.id.magnifier_large_button);
+        mCloseButton = mSettingView.findViewById(R.id.magnifier_close_button);
+        mEditButton = mSettingView.findViewById(R.id.magnifier_edit_button);
+        mChangeModeButton = mSettingView.findViewById(R.id.magnifier_full_button);
+
+        mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_seekbar);
+        mZoomSeekbar.setOnSeekBarChangeListener(new ZoomSeekbarChangeListener());
+        setSeekbarProgress(scale);
+        mAllowDiagonalScrollingSwitch =
+                (Switch) mSettingView.findViewById(R.id.magnifier_horizontal_lock_switch);
+        mAllowDiagonalScrollingSwitch.setChecked(mAllowDiagonalScrolling);
+        mAllowDiagonalScrollingSwitch.setOnCheckedChangeListener((view, checked) -> {
+            toggleDiagonalScrolling();
+        });
+
+        mParams = createLayoutParams(context);
+        applyResourcesValues();
+
+        mSmallButton.setAccessibilityDelegate(mButtonDelegate);
+        mSmallButton.setOnClickListener(mButtonClickListener);
+
+        mMediumButton.setAccessibilityDelegate(mButtonDelegate);
+        mMediumButton.setOnClickListener(mButtonClickListener);
+
+        mLargeButton.setAccessibilityDelegate(mButtonDelegate);
+        mLargeButton.setOnClickListener(mButtonClickListener);
+
+        mCloseButton.setAccessibilityDelegate(mButtonDelegate);
+        mCloseButton.setOnClickListener(mButtonClickListener);
+
+        mChangeModeButton.setAccessibilityDelegate(mButtonDelegate);
+        mChangeModeButton.setOnClickListener(mButtonClickListener);
+
+        mEditButton.setAccessibilityDelegate(mButtonDelegate);
+        mEditButton.setOnClickListener(mButtonClickListener);
+
+        mWindowInsetChangeRunnable = this::onWindowInsetChanged;
+        mSettingView.setOnApplyWindowInsetsListener((v, insets) -> {
+            // Adds a pending post check to avoiding redundant calculation because this callback
+            // is sent frequently when the switch icon window dragged by the users.
+            if (!mSettingView.getHandler().hasCallbacks(mWindowInsetChangeRunnable)) {
+                mSettingView.getHandler().post(mWindowInsetChangeRunnable);
+            }
+            return v.onApplyWindowInsets(insets);
+        });
+
+        mGestureDetector = new MagnificationGestureDetector(context,
+                context.getMainThreadHandler(), this);
+    }
+
+    private class ZoomSeekbarChangeListener implements SeekBar.OnSeekBarChangeListener {
+        @Override
+        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+            float scale = progress * A11Y_CHANGE_SCALE_DIFFERENCE + A11Y_SCALE_MIN_VALUE;
+            Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale,
+                    UserHandle.USER_CURRENT);
+            mCallback.onMagnifierScale(scale);
+        }
+
+        @Override
+        public void onStartTrackingTouch(SeekBar seekBar) {
+            // Do nothing
+        }
+
+        @Override
+        public void onStopTrackingTouch(SeekBar seekBar) {
+            // Do nothing
+        }
+    }
+
+    private CharSequence formatContentDescription(int viewId) {
+        if (viewId == R.id.magnifier_small_button) {
+            return mContext.getResources().getString(
+                    R.string.accessibility_magnification_small);
+        } else if (viewId == R.id.magnifier_medium_button) {
+            return mContext.getResources().getString(
+                    R.string.accessibility_magnification_medium);
+        } else if (viewId == R.id.magnifier_large_button) {
+            return mContext.getResources().getString(
+                    R.string.accessibility_magnification_large);
+        } else if (viewId == R.id.magnifier_close_button) {
+            return mContext.getResources().getString(
+                    R.string.accessibility_magnification_close);
+        } else if (viewId == R.id.magnifier_edit_button) {
+            return mContext.getResources().getString(
+                    R.string.accessibility_resize);
+        } else {
+            return mContext.getResources().getString(
+                    R.string.magnification_mode_switch_description);
+        }
+    }
+
+    private void applyResourcesValues() {
+        final int padding = mContext.getResources().getDimensionPixelSize(
+                R.dimen.magnification_switch_button_padding);
+
+        PorterDuffColorFilter filter = new PorterDuffColorFilter(mContext.getColor(
+                R.color.accessibility_magnifier_icon_color), PorterDuff.Mode.SRC_ATOP);
+
+        mSmallButton.setImageResource(R.drawable.ic_magnification_menu_small);
+        mSmallButton.setColorFilter(filter);
+        mSmallButton.setBackgroundResource(
+                R.drawable.accessibility_magnification_setting_view_btn_bg);
+        mSmallButton.setPadding(padding, padding, padding, padding);
+
+        mMediumButton.setImageResource(R.drawable.ic_magnification_menu_medium);
+        mMediumButton.setColorFilter(filter);
+        mMediumButton.setBackgroundResource(
+                R.drawable.accessibility_magnification_setting_view_btn_bg);
+        mMediumButton.setPadding(padding, padding, padding, padding);
+
+        mLargeButton.setImageResource(R.drawable.ic_magnification_menu_large);
+        mLargeButton.setColorFilter(filter);
+        mLargeButton.setBackgroundResource(
+                R.drawable.accessibility_magnification_setting_view_btn_bg);
+        mLargeButton.setPadding(padding, padding, padding, padding);
+
+        mChangeModeButton.setImageResource(R.drawable.ic_open_in_new_fullscreen);
+        mChangeModeButton.setColorFilter(filter);
+        mChangeModeButton.setBackgroundResource(
+                R.drawable.accessibility_magnification_setting_view_btn_bg);
+        mChangeModeButton.setPadding(padding, padding, padding, padding);
+
+        mCloseButton.setBackgroundResource(
+                R.drawable.accessibility_magnification_setting_view_btn_bg);
+        mEditButton.setBackgroundResource(
+                R.drawable.accessibility_magnification_setting_view_btn_bg);
+    }
+
+    private final AccessibilityDelegate mButtonDelegate = new AccessibilityDelegate() {
+        @Override
+        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+            super.onInitializeAccessibilityNodeInfo(host, info);
+
+            info.setContentDescription(formatContentDescription(host.getId()));
+            final AccessibilityAction clickAction = new AccessibilityAction(
+                    AccessibilityAction.ACTION_CLICK.getId(), mContext.getResources().getString(
+                    R.string.magnification_mode_switch_click_label));
+            info.addAction(clickAction);
+            info.setClickable(true);
+            info.addAction(new AccessibilityAction(R.id.accessibility_action_move_up,
+                    mContext.getString(R.string.accessibility_control_move_up)));
+            info.addAction(new AccessibilityAction(R.id.accessibility_action_move_down,
+                    mContext.getString(R.string.accessibility_control_move_down)));
+            info.addAction(new AccessibilityAction(R.id.accessibility_action_move_left,
+                    mContext.getString(R.string.accessibility_control_move_left)));
+            info.addAction(new AccessibilityAction(R.id.accessibility_action_move_right,
+                    mContext.getString(R.string.accessibility_control_move_right)));
+        }
+
+        @Override
+        public boolean performAccessibilityAction(View host, int action, Bundle args) {
+            if (performA11yAction(host, action)) {
+                return true;
+            }
+            return super.performAccessibilityAction(host, action, args);
+        }
+
+        private boolean performA11yAction(View view, int action) {
+            final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+            if (action == AccessibilityAction.ACTION_CLICK.getId()) {
+                handleSingleTap(view);
+            } else if (action == R.id.accessibility_action_move_up) {
+                moveButton(0, -windowBounds.height());
+            } else if (action == R.id.accessibility_action_move_down) {
+                moveButton(0, windowBounds.height());
+            } else if (action == R.id.accessibility_action_move_left) {
+                moveButton(-windowBounds.width(), 0);
+            } else if (action == R.id.accessibility_action_move_right) {
+                moveButton(windowBounds.width(), 0);
+            } else {
+                return false;
+            }
+            return true;
+        }
+    };
+
+    private void applyResourcesValuesWithDensityChanged() {
+        if (mIsVisible) {
+            // Reset button to make its window layer always above the mirror window.
+            hideSettingPanel();
+            showSettingPanel(false);
+        }
+    }
+
+    private boolean onTouch(View v, MotionEvent event) {
+        if (!mIsVisible) {
+            return false;
+        }
+        return mGestureDetector.onTouch(v, event);
+    }
+
+    private View.OnClickListener mButtonClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View view) {
+            int id = view.getId();
+            if (id == R.id.magnifier_small_button) {
+                setMagnifierSize(MagnificationSize.SMALL);
+            } else if (id == R.id.magnifier_medium_button) {
+                setMagnifierSize(MagnificationSize.MEDIUM);
+            } else if (id == R.id.magnifier_large_button) {
+                setMagnifierSize(MagnificationSize.LARGE);
+            } else if (id == R.id.magnifier_edit_button) {
+                editMagnifierSizeMode(true);
+            } else if (id == R.id.magnifier_close_button) {
+                hideSettingPanel();
+            } else if (id == R.id.magnifier_full_button) {
+                hideSettingPanel();
+                toggleMagnificationMode();
+            } else {
+                hideSettingPanel();
+            }
+        }
+    };
+
+    @Override
+    public boolean onSingleTap(View view) {
+        mSingleTapDetected = true;
+        handleSingleTap(view);
+        return true;
+    }
+
+    @Override
+    public boolean onDrag(View v, float offsetX, float offsetY) {
+        moveButton(offsetX, offsetY);
+        return true;
+    }
+
+    @Override
+    public boolean onStart(float x, float y) {
+        return true;
+    }
+
+    @Override
+    public boolean onFinish(float xOffset, float yOffset) {
+        if (!mSingleTapDetected) {
+            showSettingPanel();
+        }
+        mSingleTapDetected = false;
+        return true;
+    }
+
+    @VisibleForTesting
+    public ViewGroup getSettingView() {
+        return mSettingView;
+    }
+
+    private void moveButton(float offsetX, float offsetY) {
+        mSfVsyncFrameProvider.postFrameCallback(l -> {
+            mParams.x += offsetX;
+            mParams.y += offsetY;
+            updateButtonViewLayoutIfNeeded();
+        });
+    }
+
+    public void hideSettingPanel() {
+        if (!mIsVisible) {
+            return;
+        }
+
+        // Reset button status.
+        mWindowManager.removeView(mSettingView);
+        mIsVisible = false;
+        mParams.x = 0;
+        mParams.y = 0;
+
+        mContext.unregisterReceiver(mScreenOffReceiver);
+    }
+
+    public void showSettingPanel() {
+        showSettingPanel(true);
+    }
+
+    public void setScaleSeekbar(float scale) {
+        setSeekbarProgress(scale);
+    }
+
+    private void toggleMagnificationMode() {
+        mCallback.onModeSwitch(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+    }
+
+    /**
+     * Shows magnification panel for set window magnification.
+     * When the panel is going to be visible by calling this method, the layout position can be
+     * reset depending on the flag.
+     *
+     * @param resetPosition if the button position needs be reset
+     */
+    private void showSettingPanel(boolean resetPosition) {
+        if (!mIsVisible) {
+            if (resetPosition) {
+                mDraggableWindowBounds.set(getDraggableWindowBounds());
+                mParams.x = mDraggableWindowBounds.right;
+                mParams.y = mDraggableWindowBounds.bottom;
+            }
+
+            mWindowManager.addView(mSettingView, mParams);
+
+            // Exclude magnification switch button from system gesture area.
+            setSystemGestureExclusion();
+            mIsVisible = true;
+        }
+        mContext.registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+    }
+
+    private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            hideSettingPanel();
+        }
+    };
+
+    private void setSeekbarProgress(float scale) {
+        int index = (int) ((scale - A11Y_SCALE_MIN_VALUE) / A11Y_CHANGE_SCALE_DIFFERENCE);
+        if (index < 0) {
+            index = 0;
+        }
+        mZoomSeekbar.setProgress(index);
+    }
+
+    void onConfigurationChanged(int configDiff) {
+        if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) {
+            applyResourcesValues();
+            return;
+        }
+        if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+            final Rect previousDraggableBounds = new Rect(mDraggableWindowBounds);
+            mDraggableWindowBounds.set(getDraggableWindowBounds());
+            // Keep the Y position with the same height ratio before the window bounds and
+            // draggable bounds are changed.
+            final float windowHeightFraction = (float) (mParams.y - previousDraggableBounds.top)
+                    / previousDraggableBounds.height();
+            mParams.y = (int) (windowHeightFraction * mDraggableWindowBounds.height())
+                    + mDraggableWindowBounds.top;
+            return;
+        }
+        if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0) {
+            applyResourcesValuesWithDensityChanged();
+            return;
+        }
+        if ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0) {
+            updateAccessibilityWindowTitle();
+            return;
+        }
+    }
+
+    private void onWindowInsetChanged() {
+        final Rect newBounds = getDraggableWindowBounds();
+        if (mDraggableWindowBounds.equals(newBounds)) {
+            return;
+        }
+        mDraggableWindowBounds.set(newBounds);
+    }
+
+    private void updateButtonViewLayoutIfNeeded() {
+        if (mIsVisible) {
+            mParams.x = MathUtils.constrain(mParams.x, mDraggableWindowBounds.left,
+                    mDraggableWindowBounds.right);
+            mParams.y = MathUtils.constrain(mParams.y, mDraggableWindowBounds.top,
+                    mDraggableWindowBounds.bottom);
+            mWindowManager.updateViewLayout(mSettingView, mParams);
+        }
+    }
+
+    private void updateAccessibilityWindowTitle() {
+        mParams.accessibilityTitle = getAccessibilityWindowTitle(mContext);
+        if (mIsVisible) {
+            mWindowManager.updateViewLayout(mSettingView, mParams);
+        }
+    }
+
+    private void handleSingleTap(View view) {
+        int id = view.getId();
+        if (id == R.id.magnifier_small_button) {
+            setMagnifierSize(MagnificationSize.SMALL);
+        } else if (id == R.id.magnifier_medium_button) {
+            setMagnifierSize(MagnificationSize.MEDIUM);
+        } else if (id == R.id.magnifier_large_button) {
+            setMagnifierSize(MagnificationSize.LARGE);
+        } else if (id == R.id.magnifier_full_button) {
+            hideSettingPanel();
+            toggleMagnificationMode();
+        } else {
+            hideSettingPanel();
+        }
+    }
+
+    public void editMagnifierSizeMode(boolean enable) {
+        setEditMagnifierSizeMode(enable);
+        hideSettingPanel();
+    }
+
+    private void setMagnifierSize(@MagnificationSize int index) {
+        mCallback.onSetMagnifierSize(index);
+    }
+
+    private void toggleDiagonalScrolling() {
+        boolean enabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, 0,
+                UserHandle.USER_CURRENT) == 1;
+
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, enabled ? 0 : 1,
+                UserHandle.USER_CURRENT);
+
+        mCallback.onSetDiagonalScrolling(!enabled);
+    }
+
+    private void setEditMagnifierSizeMode(boolean enable) {
+        mCallback.onEditMagnifierSizeMode(enable);
+    }
+
+    private static LayoutParams createLayoutParams(Context context) {
+        final LayoutParams params = new LayoutParams(
+                LayoutParams.WRAP_CONTENT,
+                LayoutParams.WRAP_CONTENT,
+                LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
+                LayoutParams.FLAG_NOT_FOCUSABLE,
+                PixelFormat.TRANSPARENT);
+        params.gravity = Gravity.TOP | Gravity.START;
+        params.accessibilityTitle = getAccessibilityWindowTitle(context);
+        params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        return params;
+    }
+
+    private Rect getDraggableWindowBounds() {
+        final int layoutMargin = mContext.getResources().getDimensionPixelSize(
+                R.dimen.magnification_switch_button_margin);
+        final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
+        final Insets windowInsets = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(
+                WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
+        final Rect boundRect = new Rect(windowMetrics.getBounds());
+        boundRect.offsetTo(0, 0);
+        boundRect.inset(0, 0, mParams.width, mParams.height);
+        boundRect.inset(windowInsets);
+        boundRect.inset(layoutMargin, layoutMargin);
+
+        return boundRect;
+    }
+
+    private static String getAccessibilityWindowTitle(Context context) {
+        return context.getString(com.android.internal.R.string.android_system_label);
+    }
+
+    private void setSystemGestureExclusion() {
+        mSettingView.post(() -> {
+            mSettingView.setSystemGestureExclusionRects(
+                    Collections.singletonList(
+                            new Rect(0, 0, mSettingView.getWidth(), mSettingView.getHeight())));
+        });
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java
new file mode 100644
index 0000000..22ec650
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java
@@ -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.systemui.accessibility;
+
+import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
+
+/**
+ * A callback to inform WindowMagnificationController about
+ * the setting value change or the user interaction.
+ */
+public interface WindowMagnificationSettingsCallback {
+
+    /**
+     * Called when change magnification size.
+     *
+     * @param index Magnification size index.
+     * 0 : MagnificationSize.NONE, 1 : MagnificationSize.SMALL,
+     * 2 : MagnificationSize.MEDIUM, 3: MagnificationSize.LARGE
+     */
+    void onSetMagnifierSize(@MagnificationSize int index);
+
+    /**
+     * Called when set allow diagonal scrolling.
+     *
+     * @param enable Allow diagonal scrolling enable value.
+     */
+    void onSetDiagonalScrolling(boolean enable);
+
+    /**
+     * Called when change magnification size on free mode.
+     *
+     * @param enable Free mode enable value.
+     */
+    void onEditMagnifierSizeMode(boolean enable);
+
+    /**
+     * Called when set magnification scale.
+     *
+     * @param scale Magnification scale value.
+     */
+    void onMagnifierScale(float scale);
+
+    /**
+     * Called when magnification mode changed.
+     *
+     * @param newMode Magnification mode
+     * 1 : ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, 2 : ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
+     */
+    void onModeSwitch(int newMode);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
index c334ca6..19caaf4 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
@@ -60,4 +60,12 @@
      * @param displayId The logical display id.
      */
     void onMove(int displayId);
+
+    /**
+     * Called when magnification mode changed.
+     *
+     * @param displayId The logical display id.
+     * @param newMode Magnification mode.
+     */
+    void onModeSwitch(int displayId, int newMode);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
index 55611f7..e60d4e1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
@@ -18,7 +18,7 @@
 import android.content.Context
 import android.graphics.drawable.Drawable
 import android.util.Log
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.R
 import com.android.systemui.biometrics.AuthBiometricView.BiometricState
 import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
@@ -33,8 +33,8 @@
 
 /** Face only icon animator for BiometricPrompt. */
 class AuthBiometricFaceIconController(
-    context: Context,
-    iconView: ImageView
+        context: Context,
+        iconView: LottieAnimationView
 ) : AuthIconController(context, iconView) {
 
     // false = dark to light, true = light to dark
@@ -76,44 +76,44 @@
         if (newState == STATE_AUTHENTICATING_ANIMATING_IN) {
             showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_authenticating
+                    R.string.biometric_dialog_face_icon_description_authenticating
             )
         } else if (newState == STATE_AUTHENTICATING) {
             startPulsing()
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_authenticating
+                    R.string.biometric_dialog_face_icon_description_authenticating
             )
         } else if (oldState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) {
             animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_confirmed
+                    R.string.biometric_dialog_face_icon_description_confirmed
             )
         } else if (lastStateIsErrorIcon && newState == STATE_IDLE) {
             animateIconOnce(R.drawable.face_dialog_error_to_idle)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_idle
+                    R.string.biometric_dialog_face_icon_description_idle
             )
         } else if (lastStateIsErrorIcon && newState == STATE_AUTHENTICATED) {
             animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_authenticated
+                    R.string.biometric_dialog_face_icon_description_authenticated
             )
         } else if (newState == STATE_ERROR && oldState != STATE_ERROR) {
             animateIconOnce(R.drawable.face_dialog_dark_to_error)
         } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
             animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_authenticated
+                    R.string.biometric_dialog_face_icon_description_authenticated
             )
         } else if (newState == STATE_PENDING_CONFIRMATION) {
             animateIconOnce(R.drawable.face_dialog_wink_from_dark)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_authenticated
+                    R.string.biometric_dialog_face_icon_description_authenticated
             )
         } else if (newState == STATE_IDLE) {
             showStaticDrawable(R.drawable.face_dialog_idle_static)
             iconView.contentDescription = context.getString(
-                R.string.biometric_dialog_face_icon_description_idle
+                    R.string.biometric_dialog_face_icon_description_idle
             )
         } else {
             Log.w(TAG, "Unhandled state: $newState")
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
index 3e4e573..40d1eff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
@@ -16,42 +16,43 @@
 
 package com.android.systemui.biometrics
 
+import android.annotation.RawRes
 import android.content.Context
-import android.graphics.drawable.Drawable
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.R
 import com.android.systemui.biometrics.AuthBiometricView.BiometricState
-import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
 import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
 import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
 import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
+import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
 
 /** Face/Fingerprint combined icon animator for BiometricPrompt. */
 class AuthBiometricFingerprintAndFaceIconController(
-    context: Context,
-    iconView: ImageView
+        context: Context,
+        iconView: LottieAnimationView
 ) : AuthBiometricFingerprintIconController(context, iconView) {
 
     override val actsAsConfirmButton: Boolean = true
 
     override fun shouldAnimateForTransition(
-        @BiometricState oldState: Int,
-        @BiometricState newState: Int
+            @BiometricState oldState: Int,
+            @BiometricState newState: Int
     ): Boolean = when (newState) {
         STATE_PENDING_CONFIRMATION -> true
         STATE_AUTHENTICATED -> false
         else -> super.shouldAnimateForTransition(oldState, newState)
     }
 
+    @RawRes
     override fun getAnimationForTransition(
-        @BiometricState oldState: Int,
-        @BiometricState newState: Int
-    ): Drawable? = when (newState) {
+            @BiometricState oldState: Int,
+            @BiometricState newState: Int
+    ): Int? = when (newState) {
         STATE_PENDING_CONFIRMATION -> {
             if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                context.getDrawable(R.drawable.fingerprint_dialog_error_to_unlock)
+                R.raw.fingerprint_dialogue_error_to_unlock_lottie
             } else {
-                context.getDrawable(R.drawable.fingerprint_dialog_fp_to_unlock)
+                R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
             }
         }
         STATE_AUTHENTICATED -> null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index 606a73a..9b5f54a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -16,10 +16,9 @@
 
 package com.android.systemui.biometrics
 
+import android.annotation.RawRes
 import android.content.Context
-import android.graphics.drawable.AnimatedVectorDrawable
-import android.graphics.drawable.Drawable
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.R
 import com.android.systemui.biometrics.AuthBiometricView.BiometricState
 import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
@@ -32,42 +31,42 @@
 
 /** Fingerprint only icon animator for BiometricPrompt.  */
 open class AuthBiometricFingerprintIconController(
-    context: Context,
-    iconView: ImageView
+        context: Context,
+        iconView: LottieAnimationView
 ) : AuthIconController(context, iconView) {
 
-    var iconLayoutParamsSize = 0
+    var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
         set(value) {
             if (field == value) {
                 return
             }
-            iconView.layoutParams.width = value
-            iconView.layoutParams.height = value
+            iconView.layoutParams.width = value.first
+            iconView.layoutParams.height = value.second
             field = value
         }
 
     init {
-        iconLayoutParamsSize = context.resources.getDimensionPixelSize(
-            R.dimen.biometric_dialog_fingerprint_icon_size
-        )
+        iconLayoutParamSize = Pair(context.resources.getDimensionPixelSize(
+                R.dimen.biometric_dialog_fingerprint_icon_width),
+                context.resources.getDimensionPixelSize(
+                        R.dimen.biometric_dialog_fingerprint_icon_height))
     }
 
     override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
         val icon = getAnimationForTransition(lastState, newState) ?: return
 
-        iconView.setImageDrawable(icon)
+        if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
+            iconView.setAnimation(icon)
+        }
 
         val iconContentDescription = getIconContentDescription(newState)
         if (iconContentDescription != null) {
             iconView.contentDescription = iconContentDescription
         }
 
-        (icon as? AnimatedVectorDrawable)?.apply {
-            reset()
-            if (shouldAnimateForTransition(lastState, newState)) {
-                forceAnimationOnUI()
-                start()
-            }
+        iconView.frame = 0
+        if (shouldAnimateForTransition(lastState, newState)) {
+            iconView.playAnimation()
         }
     }
 
@@ -86,35 +85,44 @@
     }
 
     protected open fun shouldAnimateForTransition(
-        @BiometricState oldState: Int,
-        @BiometricState newState: Int
+            @BiometricState oldState: Int,
+            @BiometricState newState: Int
     ) = when (newState) {
         STATE_HELP,
         STATE_ERROR -> true
         STATE_AUTHENTICATING_ANIMATING_IN,
         STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
-        STATE_AUTHENTICATED -> false
+        STATE_AUTHENTICATED -> true
         else -> false
     }
 
+    @RawRes
     protected open fun getAnimationForTransition(
-        @BiometricState oldState: Int,
-        @BiometricState newState: Int
-    ): Drawable? {
+            @BiometricState oldState: Int,
+            @BiometricState newState: Int
+    ): Int? {
         val id = when (newState) {
             STATE_HELP,
-            STATE_ERROR -> R.drawable.fingerprint_dialog_fp_to_error
+            STATE_ERROR -> {
+                R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+            }
             STATE_AUTHENTICATING_ANIMATING_IN,
             STATE_AUTHENTICATING -> {
                 if (oldState == STATE_ERROR || oldState == STATE_HELP) {
-                    R.drawable.fingerprint_dialog_error_to_fp
+                    R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
                 } else {
-                    R.drawable.fingerprint_dialog_fp_to_error
+                    R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
                 }
             }
-            STATE_AUTHENTICATED -> R.drawable.fingerprint_dialog_fp_to_error
+            STATE_AUTHENTICATED -> {
+                if (oldState == STATE_ERROR || oldState == STATE_HELP) {
+                    R.raw.fingerprint_dialogue_error_to_success_lottie
+                } else {
+                    R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+                }
+            }
             else -> return null
         }
-        return if (id != null) context.getDrawable(id) else null
+        return if (id != null) return id else null
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
index 24046f0..9cce066 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
@@ -75,7 +75,7 @@
         }
     }
 
-    override fun getDelayAfterAuthenticatedDurationMs() = 0
+    override fun getDelayAfterAuthenticatedDurationMs() = 500
 
     override fun getStateForAfterError() = STATE_AUTHENTICATING
 
@@ -90,8 +90,9 @@
 
     fun updateOverrideIconLayoutParamsSize() {
         udfpsAdapter?.let {
-            (mIconController as? AuthBiometricFingerprintIconController)?.iconLayoutParamsSize =
-                    it.getSensorDiameter(scaleFactorProvider?.provide() ?: 1.0f)
+            val sensorDiameter = it.getSensorDiameter(scaleFactorProvider?.provide() ?: 1.0f)
+            (mIconController as? AuthBiometricFingerprintIconController)?.iconLayoutParamSize =
+                    Pair(sensorDiameter, sensorDiameter)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
index ce5e600..15f487b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
@@ -22,15 +22,15 @@
 import android.graphics.drawable.AnimatedVectorDrawable
 import android.graphics.drawable.Drawable
 import android.util.Log
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.biometrics.AuthBiometricView.BiometricState
 
 private const val TAG = "AuthIconController"
 
 /** Controller for animating the BiometricPrompt icon/affordance. */
 abstract class AuthIconController(
-    protected val context: Context,
-    protected val iconView: ImageView
+        protected val context: Context,
+        protected val iconView: LottieAnimationView
 ) : Animatable2.AnimationCallback() {
 
     /** If this controller should ignore events and pause. */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index d7ae9ef..fc5cf9f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -41,14 +41,14 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Button;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.R;
-import com.android.systemui.util.LargeScreenUtils;
+
+import com.airbnb.lottie.LottieAnimationView;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -133,7 +133,7 @@
     private TextView mSubtitleView;
     private TextView mDescriptionView;
     private View mIconHolderView;
-    protected ImageView mIconView;
+    protected LottieAnimationView mIconView;
     protected TextView mIndicatorView;
 
     @VisibleForTesting @NonNull AuthIconController mIconController;
@@ -468,6 +468,7 @@
                 break;
 
             case STATE_AUTHENTICATED:
+                removePendingAnimations();
                 if (mSize != AuthDialog.SIZE_SMALL) {
                     mConfirmButton.setVisibility(View.GONE);
                     mNegativeButton.setVisibility(View.GONE);
@@ -824,25 +825,12 @@
         return new AuthDialog.LayoutParams(width, totalHeight);
     }
 
-    private boolean isLargeDisplay() {
-        return LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
-    }
-
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         final int width = MeasureSpec.getSize(widthMeasureSpec);
         final int height = MeasureSpec.getSize(heightMeasureSpec);
 
-        final boolean isLargeDisplay = isLargeDisplay();
-
-        final int newWidth;
-        if (isLargeDisplay) {
-            // TODO(b/201811580): Unless we can come up with a one-size-fits-all equation, we may
-            //  want to consider moving this to an overlay.
-            newWidth = 2 * Math.min(width, height) / 3;
-        } else {
-            newWidth = Math.min(width, height);
-        }
+        final int newWidth = Math.min(width, height);
 
         // Use "newWidth" instead, so the landscape dialog width is the same as the portrait
         // width.
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/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index cf50f7f..d1589b2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -517,8 +517,6 @@
                                     scaledMajor);
                             Log.v(TAG, "onTouch | finger down: " + touchInfo);
                             mTouchLogTime = mSystemClock.elapsedRealtime();
-                            mPowerManager.userActivity(mSystemClock.uptimeMillis(),
-                                    PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
                             handled = true;
                         } else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) {
                             Log.v(TAG, "onTouch | finger move: " + touchInfo);
@@ -846,6 +844,9 @@
             return;
         }
         mLatencyTracker.onActionStart(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
+        // Refresh screen timeout and boost process priority if possible.
+        mPowerManager.userActivity(mSystemClock.uptimeMillis(),
+                PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
 
         if (!mOnFingerDown) {
             playStartHaptic();
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/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 47ea27f..0839338 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -30,6 +30,8 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.core.graphics.ColorUtils;
+
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
@@ -42,7 +44,8 @@
  * @hide
  */
 final class WirelessChargingLayout extends FrameLayout {
-    private static final long RIPPLE_ANIMATION_DURATION = 1500;
+    private static final long CIRCLE_RIPPLE_ANIMATION_DURATION = 1500;
+    private static final long ROUNDED_BOX_RIPPLE_ANIMATION_DURATION = 1750;
     private static final int SCRIM_COLOR = 0x4C000000;
     private static final int SCRIM_FADE_DURATION = 300;
     private RippleView mRippleView;
@@ -131,17 +134,30 @@
                 "backgroundColor", SCRIM_COLOR, Color.TRANSPARENT);
         scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION);
         scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR);
-        scrimFadeOutAnimator.setStartDelay(RIPPLE_ANIMATION_DURATION - SCRIM_FADE_DURATION);
+        scrimFadeOutAnimator.setStartDelay((rippleShape == RippleShape.CIRCLE
+                ? CIRCLE_RIPPLE_ANIMATION_DURATION : ROUNDED_BOX_RIPPLE_ANIMATION_DURATION)
+                - SCRIM_FADE_DURATION);
         AnimatorSet animatorSetScrim = new AnimatorSet();
         animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator);
         animatorSetScrim.start();
 
         mRippleView = findViewById(R.id.wireless_charging_ripple);
         mRippleView.setupShader(rippleShape);
+        if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) {
+            mRippleView.setDuration(ROUNDED_BOX_RIPPLE_ANIMATION_DURATION);
+            mRippleView.setSparkleStrength(0.22f);
+            int color = Utils.getColorAttr(mRippleView.getContext(),
+                    android.R.attr.colorAccent).getDefaultColor();
+            mRippleView.setColor(ColorUtils.setAlphaComponent(color, 28));
+        } else {
+            mRippleView.setDuration(CIRCLE_RIPPLE_ANIMATION_DURATION);
+            mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(),
+                    android.R.attr.colorAccent).getDefaultColor());
+        }
+
         OnAttachStateChangeListener listener = new OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View view) {
-                mRippleView.setDuration(RIPPLE_ANIMATION_DURATION);
                 mRippleView.startRipple();
                 mRippleView.removeOnAttachStateChangeListener(this);
             }
@@ -232,13 +248,13 @@
             int height = getMeasuredHeight();
             mRippleView.setCenter(width * 0.5f, height * 0.5f);
             if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) {
-                mRippleView.setMaxSize(width * 1.5f, height * 1.5f);
+                // Those magic numbers are introduced for visual polish. This aspect ratio maps with
+                // the tablet's docking station.
+                mRippleView.setMaxSize(width * 1.36f, height * 1.46f);
             } else {
                 float maxSize = Math.max(width, height);
                 mRippleView.setMaxSize(maxSize, maxSize);
             }
-            mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(),
-                    android.R.attr.colorAccent).getDefaultColor());
         }
 
         super.onLayout(changed, left, top, right, bottom);
diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt b/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
index 6f3beac..a0b19dc 100644
--- a/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
@@ -35,7 +35,7 @@
      *             " - downstream canceled or failed.",
      *          it,
      *    )
-     *}
+     * }
      * ```
      */
     fun <T> SendChannel<T>.trySendWithFailureLogging(
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Position.kt
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
rename to packages/SystemUI/src/com/android/systemui/common/shared/model/Position.kt
index 7c9df10..52f6167 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Position.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.common.data.model
+package com.android.systemui.common.shared.model
 
 /** Models a two-dimensional position */
 data class Position(
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/dreams/complication/dagger/DreamClockTimeComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
index 5250d44..7d9f105 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
@@ -37,7 +37,7 @@
 public interface DreamClockTimeComplicationModule {
     String DREAM_CLOCK_TIME_COMPLICATION_VIEW = "clock_time_complication_view";
     String TAG_WEIGHT = "'wght' ";
-    int WEIGHT = 200;
+    int WEIGHT = 400;
 
     /**
      * Provides the complication view.
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 6eb77bd..29d6765 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -92,7 +92,7 @@
      * Whether the KeyguardBottomArea(View|Controller) should use the modern architecture or the old
      * one.
      */
-    public static final UnreleasedFlag MODERN_BOTTOM_AREA = new UnreleasedFlag(206, true);
+    public static final ReleasedFlag MODERN_BOTTOM_AREA = new ReleasedFlag(206, true);
 
     public static final UnreleasedFlag LOCKSCREEN_CUSTOM_CLOCKS = new UnreleasedFlag(207);
   
@@ -151,10 +151,14 @@
     public static final ResourceBooleanFlag STATUS_BAR_USER_SWITCHER =
             new ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip);
 
-    public static final UnreleasedFlag STATUS_BAR_LETTERBOX_APPEARANCE =
-            new UnreleasedFlag(603, false);
+    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
@@ -182,6 +186,7 @@
     public static final UnreleasedFlag MEDIA_SESSION_ACTIONS = new UnreleasedFlag(901);
     public static final ReleasedFlag MEDIA_NEARBY_DEVICES = new ReleasedFlag(903);
     public static final ReleasedFlag MEDIA_MUTE_AWAIT = new ReleasedFlag(904);
+    public static final UnreleasedFlag MEDIA_DREAM_COMPLICATION = new UnreleasedFlag(905);
 
     // 1000 - dock
     public static final ReleasedFlag SIMULATE_DOCK_THROUGH_CHARGING =
@@ -236,6 +241,10 @@
     // 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);
 
     // Pay no attention to the reflection behind the curtain.
     // ========================== Curtain ==========================
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/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index bd00ce6..1f52fc6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -27,6 +27,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -56,8 +57,11 @@
 public class KeyguardIndicationRotateTextViewController extends
         ViewController<KeyguardIndicationTextView> implements Dumpable {
     public static String TAG = "KgIndicationRotatingCtrl";
-    private static final long DEFAULT_INDICATION_SHOW_LENGTH = 3500; // milliseconds
-    public static final long IMPORTANT_MSG_MIN_DURATION = 2000L + 600L; // 2000ms + [Y in duration]
+    private static final long DEFAULT_INDICATION_SHOW_LENGTH =
+            KeyguardIndicationController.DEFAULT_HIDE_DELAY_MS
+                    - KeyguardIndicationTextView.Y_IN_DURATION;
+    public static final long IMPORTANT_MSG_MIN_DURATION =
+            2000L + KeyguardIndicationTextView.Y_IN_DURATION;
 
     private final StatusBarStateController mStatusBarStateController;
     private final float mMaxAlpha;
@@ -375,6 +379,7 @@
     public static final int INDICATION_TYPE_USER_LOCKED = 8;
     public static final int INDICATION_TYPE_REVERSE_CHARGING = 10;
     public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE = 11;
+    public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP = 12;
 
     @IntDef({
             INDICATION_TYPE_NONE,
@@ -388,7 +393,8 @@
             INDICATION_TYPE_RESTING,
             INDICATION_TYPE_USER_LOCKED,
             INDICATION_TYPE_REVERSE_CHARGING,
-            INDICATION_TYPE_BIOMETRIC_MESSAGE
+            INDICATION_TYPE_BIOMETRIC_MESSAGE,
+            INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface IndicationType{}
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..3199284 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -73,8 +73,6 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.EventLog;
-import android.util.Log;
-import android.util.Slog;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.view.IRemoteAnimationFinishedCallback;
@@ -106,6 +104,7 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.KeyguardViewController;
 import com.android.keyguard.ViewMediatorCallback;
+import com.android.keyguard.logging.KeyguardViewMediatorLogger;
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.DejankUtils;
@@ -513,8 +512,7 @@
         public void onKeyguardVisibilityChanged(boolean showing) {
             synchronized (KeyguardViewMediator.this) {
                 if (!showing && mPendingPinLock) {
-                    Log.i(TAG, "PIN lock requested, starting keyguard");
-
+                    mLogger.logPinLockRequestedStartingKeyguard();
                     // Bring the keyguard back in order to show the PIN lock
                     mPendingPinLock = false;
                     doKeyguardLocked(null);
@@ -524,7 +522,7 @@
 
         @Override
         public void onUserSwitching(int userId) {
-            if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId));
+            mLogger.logUserSwitching(userId);
             // Note that the mLockPatternUtils user has already been updated from setCurrentUser.
             // We need to force a reset of the views, since lockNow (called by
             // ActivityManagerService) will not reconstruct the keyguard if it is already showing.
@@ -542,7 +540,7 @@
 
         @Override
         public void onUserSwitchComplete(int userId) {
-            if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
+            mLogger.logOnUserSwitchComplete(userId);
             if (userId != UserHandle.USER_SYSTEM) {
                 UserInfo info = UserManager.get(mContext).getUserInfo(userId);
                 // Don't try to dismiss if the user has Pin/Pattern/Password set
@@ -570,8 +568,7 @@
         public void onSimStateChanged(int subId, int slotId, int simState) {
 
             if (DEBUG_SIM_STATES) {
-                Log.d(TAG, "onSimStateChanged(subId=" + subId + ", slotId=" + slotId
-                        + ",state=" + simState + ")");
+                mLogger.logOnSimStateChanged(subId, slotId, String.valueOf(simState));
             }
 
             int size = mKeyguardStateCallbacks.size();
@@ -580,7 +577,7 @@
                 try {
                     mKeyguardStateCallbacks.get(i).onSimSecureStateChanged(simPinSecure);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to call onSimSecureStateChanged", e);
+                    mLogger.logFailedToCallOnSimSecureStateChanged(e);
                     if (e instanceof DeadObjectException) {
                         mKeyguardStateCallbacks.remove(i);
                     }
@@ -603,9 +600,9 @@
                     synchronized (KeyguardViewMediator.this) {
                         if (shouldWaitForProvisioning()) {
                             if (!mShowing) {
-                                if (DEBUG_SIM_STATES) Log.d(TAG, "ICC_ABSENT isn't showing,"
-                                        + " we need to show the keyguard since the "
-                                        + "device isn't provisioned yet.");
+                                if (DEBUG_SIM_STATES) {
+                                    mLogger.logIccAbsentIsNotShowing();
+                                }
                                 doKeyguardLocked(null);
                             } else {
                                 resetStateLocked();
@@ -615,8 +612,9 @@
                             // MVNO SIMs can become transiently NOT_READY when switching networks,
                             // so we should only lock when they are ABSENT.
                             if (lastSimStateWasLocked) {
-                                if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to ABSENT when the "
-                                        + "previous state was locked. Reset the state.");
+                                if (DEBUG_SIM_STATES) {
+                                    mLogger.logSimMovedToAbsent();
+                                }
                                 resetStateLocked();
                             }
                             mSimWasLocked.append(slotId, false);
@@ -629,9 +627,9 @@
                         mSimWasLocked.append(slotId, true);
                         mPendingPinLock = true;
                         if (!mShowing) {
-                            if (DEBUG_SIM_STATES) Log.d(TAG,
-                                    "INTENT_VALUE_ICC_LOCKED and keygaurd isn't "
-                                    + "showing; need to show keyguard so user can enter sim pin");
+                            if (DEBUG_SIM_STATES) {
+                                mLogger.logIntentValueIccLocked();
+                            }
                             doKeyguardLocked(null);
                         } else {
                             resetStateLocked();
@@ -641,29 +639,36 @@
                 case TelephonyManager.SIM_STATE_PERM_DISABLED:
                     synchronized (KeyguardViewMediator.this) {
                         if (!mShowing) {
-                            if (DEBUG_SIM_STATES) Log.d(TAG, "PERM_DISABLED and "
-                                  + "keygaurd isn't showing.");
+                            if (DEBUG_SIM_STATES) {
+                                mLogger.logPermDisabledKeyguardNotShowing();
+                            }
                             doKeyguardLocked(null);
                         } else {
-                            if (DEBUG_SIM_STATES) Log.d(TAG, "PERM_DISABLED, resetStateLocked to"
-                                  + "show permanently disabled message in lockscreen.");
+                            if (DEBUG_SIM_STATES) {
+                                mLogger.logPermDisabledResetStateLocked();
+                            }
                             resetStateLocked();
                         }
                     }
                     break;
                 case TelephonyManager.SIM_STATE_READY:
                     synchronized (KeyguardViewMediator.this) {
-                        if (DEBUG_SIM_STATES) Log.d(TAG, "READY, reset state? " + mShowing);
+                        if (DEBUG_SIM_STATES) {
+                            mLogger.logReadyResetState(mShowing);
+                        }
                         if (mShowing && mSimWasLocked.get(slotId, false)) {
-                            if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to READY when the "
-                                    + "previously was locked. Reset the state.");
+                            if (DEBUG_SIM_STATES) {
+                                mLogger.logSimMovedToReady();
+                            }
                             mSimWasLocked.append(slotId, false);
                             resetStateLocked();
                         }
                     }
                     break;
                 default:
-                    if (DEBUG_SIM_STATES) Log.v(TAG, "Unspecific state: " + simState);
+                    if (DEBUG_SIM_STATES) {
+                        mLogger.logUnspecifiedSimState(simState);
+                    }
                     break;
             }
         }
@@ -708,7 +713,7 @@
             if (targetUserId != ActivityManager.getCurrentUser()) {
                 return;
             }
-            if (DEBUG) Log.d(TAG, "keyguardDone");
+            mLogger.logKeyguardDone();
             tryKeyguardDone();
         }
 
@@ -727,7 +732,7 @@
         @Override
         public void keyguardDonePending(boolean strongAuth, int targetUserId) {
             Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardDonePending");
-            if (DEBUG) Log.d(TAG, "keyguardDonePending");
+            mLogger.logKeyguardDonePending();
             if (targetUserId != ActivityManager.getCurrentUser()) {
                 Trace.endSection();
                 return;
@@ -746,7 +751,7 @@
         @Override
         public void keyguardGone() {
             Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardGone");
-            if (DEBUG) Log.d(TAG, "keyguardGone");
+            mLogger.logKeyguardGone();
             mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false);
             mKeyguardDisplayManager.hide();
             Trace.endSection();
@@ -832,8 +837,7 @@
 
                 @Override
                 public void onLaunchAnimationCancelled() {
-                    Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: "
-                            + mOccluded);
+                    mLogger.logOccludeLaunchAnimationCancelled(mOccluded);
                 }
 
                 @Override
@@ -853,8 +857,7 @@
                 @Override
                 public void setLaunchContainer(@NonNull ViewGroup launchContainer) {
                     // No-op, launch container is always the shade.
-                    Log.wtf(TAG, "Someone tried to change the launch container for the "
-                            + "ActivityLaunchAnimator, which should never happen.");
+                    mLogger.logActivityLaunchAnimatorLaunchContainerChanged();
                 }
 
                 @NonNull
@@ -905,8 +908,7 @@
                     }
 
                     setOccluded(isKeyguardOccluded /* isOccluded */, false /* animate */);
-                    Log.d(TAG, "Unocclude animation cancelled. Occluded state is now: "
-                            + mOccluded);
+                    mLogger.logUnoccludeAnimationCancelled(mOccluded);
                 }
 
                 @Override
@@ -914,12 +916,11 @@
                         RemoteAnimationTarget[] wallpapers,
                         RemoteAnimationTarget[] nonApps,
                         IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
-                    Log.d(TAG, "UnoccludeAnimator#onAnimationStart. Set occluded = false.");
+                    mLogger.logUnoccludeAnimatorOnAnimationStart();
                     setOccluded(false /* isOccluded */, true /* animate */);
 
                     if (apps == null || apps.length == 0 || apps[0] == null) {
-                        Log.d(TAG, "No apps provided to unocclude runner; "
-                                + "skipping animation and unoccluding.");
+                        mLogger.logNoAppsProvidedToUnoccludeRunner();
                         finishedCallback.onAnimationFinished();
                         return;
                     }
@@ -1007,6 +1008,7 @@
     private ScreenOnCoordinator mScreenOnCoordinator;
 
     private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
+    private KeyguardViewMediatorLogger mLogger;
 
     /**
      * Injected constructor. See {@link KeyguardModule}.
@@ -1035,7 +1037,8 @@
             InteractionJankMonitor interactionJankMonitor,
             DreamOverlayStateController dreamOverlayStateController,
             Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
-            Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
+            Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
+            KeyguardViewMediatorLogger logger) {
         super(context);
         mFalsingCollector = falsingCollector;
         mLockPatternUtils = lockPatternUtils;
@@ -1078,6 +1081,7 @@
         mDreamOverlayStateController = dreamOverlayStateController;
 
         mActivityLaunchAnimator = activityLaunchAnimator;
+        mLogger = logger;
 
         mPowerButtonY = context.getResources().getDimensionPixelSize(
                 R.dimen.physical_power_button_center_screen_location_y);
@@ -1141,21 +1145,21 @@
             mLockSoundId = mLockSounds.load(soundPath, 1);
         }
         if (soundPath == null || mLockSoundId == 0) {
-            Log.w(TAG, "failed to load lock sound from " + soundPath);
+            mLogger.logFailedLoadLockSound(soundPath);
         }
         soundPath = Settings.Global.getString(cr, Settings.Global.UNLOCK_SOUND);
         if (soundPath != null) {
             mUnlockSoundId = mLockSounds.load(soundPath, 1);
         }
         if (soundPath == null || mUnlockSoundId == 0) {
-            Log.w(TAG, "failed to load unlock sound from " + soundPath);
+            mLogger.logFailedLoadUnlockSound(soundPath);
         }
         soundPath = Settings.Global.getString(cr, Settings.Global.TRUSTED_SOUND);
         if (soundPath != null) {
             mTrustedSoundId = mLockSounds.load(soundPath, 1);
         }
         if (soundPath == null || mTrustedSoundId == 0) {
-            Log.w(TAG, "failed to load trusted sound from " + soundPath);
+            mLogger.logFailedLoadTrustedSound(soundPath);
         }
 
         int lockSoundDefaultAttenuation = mContext.getResources().getInteger(
@@ -1184,7 +1188,7 @@
 
     private void handleSystemReady() {
         synchronized (this) {
-            if (DEBUG) Log.d(TAG, "onSystemReady");
+            mLogger.logOnSystemReady();
             mSystemReady = true;
             doKeyguardLocked(null);
             mUpdateMonitor.registerCallback(mUpdateCallback);
@@ -1202,7 +1206,7 @@
      * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_TIMEOUT}.
      */
     public void onStartedGoingToSleep(@WindowManagerPolicyConstants.OffReason int offReason) {
-        if (DEBUG) Log.d(TAG, "onStartedGoingToSleep(" + offReason + ")");
+        mLogger.logOnStartedGoingToSleep(offReason);
         synchronized (this) {
             mDeviceInteractive = false;
             mPowerGestureIntercepted = false;
@@ -1218,11 +1222,11 @@
             long timeout = getLockTimeout(KeyguardUpdateMonitor.getCurrentUser());
             mLockLater = false;
             if (mExitSecureCallback != null) {
-                if (DEBUG) Log.d(TAG, "pending exit secure callback cancelled");
+                mLogger.logPendingExitSecureCallbackCancelled();
                 try {
                     mExitSecureCallback.onKeyguardExitResult(false);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
+                    mLogger.logFailedOnKeyguardExitResultFalse(e);
                 }
                 mExitSecureCallback = null;
                 if (!mExternallyEnabled) {
@@ -1267,7 +1271,7 @@
      */
     public void onFinishedGoingToSleep(
             @WindowManagerPolicyConstants.OffReason int offReason, boolean cameraGestureTriggered) {
-        if (DEBUG) Log.d(TAG, "onFinishedGoingToSleep(" + offReason + ")");
+        mLogger.logOnFinishedGoingToSleep(offReason);
         synchronized (this) {
             mDeviceInteractive = false;
             mGoingToSleep = false;
@@ -1325,13 +1329,7 @@
             //   - The screen off animation is cancelled by the device waking back up. We will call
             //     maybeHandlePendingLock from KeyguardViewMediator#onStartedWakingUp.
             if (mScreenOffAnimationController.isKeyguardShowDelayed()) {
-                if (DEBUG) {
-                    Log.d(TAG, "#maybeHandlePendingLock: not handling because the screen off "
-                            + "animation's isKeyguardShowDelayed() returned true. This should be "
-                            + "handled soon by #onStartedWakingUp, or by the end actions of the "
-                            + "screen off animation.");
-                }
-
+                mLogger.logMaybeHandlePendingLockNotHandling();
                 return;
             }
 
@@ -1341,18 +1339,11 @@
             // StatusBar#finishKeyguardFadingAway, which is always responsible for setting
             // isKeyguardGoingAway to false.
             if (mKeyguardStateController.isKeyguardGoingAway()) {
-                if (DEBUG) {
-                    Log.d(TAG, "#maybeHandlePendingLock: not handling because the keyguard is "
-                            + "going away. This should be handled shortly by "
-                            + "StatusBar#finishKeyguardFadingAway.");
-                }
-
+                mLogger.logMaybeHandlePendingLockKeyguardGoingAway();
                 return;
             }
 
-            if (DEBUG) {
-                Log.d(TAG, "#maybeHandlePendingLock: handling pending lock; locking keyguard.");
-            }
+            mLogger.logMaybeHandlePendingLockHandling();
 
             doKeyguardLocked(null);
             setPendingLock(false);
@@ -1421,8 +1412,7 @@
         PendingIntent sender = PendingIntent.getBroadcast(mContext,
                 0, intent, PendingIntent.FLAG_CANCEL_CURRENT |  PendingIntent.FLAG_IMMUTABLE);
         mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, when, sender);
-        if (DEBUG) Log.d(TAG, "setting alarm to turn off keyguard, seq = "
-                         + mDelayedShowingSequence);
+        mLogger.logSetAlarmToTurnOffKeyguard(mDelayedShowingSequence);
         doKeyguardLaterForChildProfilesLocked();
     }
 
@@ -1482,7 +1472,7 @@
             mAnimatingScreenOff = false;
             cancelDoKeyguardLaterLocked();
             cancelDoKeyguardForChildProfilesLocked();
-            if (DEBUG) Log.d(TAG, "onStartedWakingUp, seq = " + mDelayedShowingSequence);
+            mLogger.logOnStartedWakingUp(mDelayedShowingSequence);
             notifyStartedWakingUp();
         }
         mUpdateMonitor.dispatchStartedWakingUp();
@@ -1542,37 +1532,35 @@
      */
     public void setKeyguardEnabled(boolean enabled) {
         synchronized (this) {
-            if (DEBUG) Log.d(TAG, "setKeyguardEnabled(" + enabled + ")");
+            mLogger.logSetKeyguardEnabled(enabled);
 
             mExternallyEnabled = enabled;
 
             if (!enabled && mShowing) {
                 if (mExitSecureCallback != null) {
-                    if (DEBUG) Log.d(TAG, "in process of verifyUnlock request, ignoring");
+                    mLogger.logIgnoreVerifyUnlockRequest();
                     // we're in the process of handling a request to verify the user
                     // can get past the keyguard. ignore extraneous requests to disable / re-enable
                     return;
                 }
 
                 // hiding keyguard that is showing, remember to reshow later
-                if (DEBUG) Log.d(TAG, "remembering to reshow, hiding keyguard, "
-                        + "disabling status bar expansion");
+                mLogger.logRememberToReshowLater();
                 mNeedToReshowWhenReenabled = true;
                 updateInputRestrictedLocked();
                 hideLocked();
             } else if (enabled && mNeedToReshowWhenReenabled) {
                 // re-enabled after previously hidden, reshow
-                if (DEBUG) Log.d(TAG, "previously hidden, reshowing, reenabling "
-                        + "status bar expansion");
+                mLogger.logPreviouslyHiddenReshow();
                 mNeedToReshowWhenReenabled = false;
                 updateInputRestrictedLocked();
 
                 if (mExitSecureCallback != null) {
-                    if (DEBUG) Log.d(TAG, "onKeyguardExitResult(false), resetting");
+                    mLogger.logOnKeyguardExitResultFalseResetting();
                     try {
                         mExitSecureCallback.onKeyguardExitResult(false);
                     } catch (RemoteException e) {
-                        Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
+                        mLogger.logFailedToCallOnKeyguardExitResultFalse(e);
                     }
                     mExitSecureCallback = null;
                     resetStateLocked();
@@ -1584,7 +1572,7 @@
                     // and causing an ANR).
                     mWaitingUntilKeyguardVisible = true;
                     mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING, KEYGUARD_DONE_DRAWING_TIMEOUT_MS);
-                    if (DEBUG) Log.d(TAG, "waiting until mWaitingUntilKeyguardVisible is false");
+                    mLogger.logWaitingUntilKeyguardVisibleIsFalse();
                     while (mWaitingUntilKeyguardVisible) {
                         try {
                             wait();
@@ -1592,7 +1580,7 @@
                             Thread.currentThread().interrupt();
                         }
                     }
-                    if (DEBUG) Log.d(TAG, "done waiting for mWaitingUntilKeyguardVisible");
+                    mLogger.logDoneWaitingUntilKeyguardVisible();
                 }
             }
         }
@@ -1604,31 +1592,31 @@
     public void verifyUnlock(IKeyguardExitCallback callback) {
         Trace.beginSection("KeyguardViewMediator#verifyUnlock");
         synchronized (this) {
-            if (DEBUG) Log.d(TAG, "verifyUnlock");
+            mLogger.logVerifyUnlock();
             if (shouldWaitForProvisioning()) {
                 // don't allow this api when the device isn't provisioned
-                if (DEBUG) Log.d(TAG, "ignoring because device isn't provisioned");
+                mLogger.logIgnoreUnlockDeviceNotProvisioned();
                 try {
                     callback.onKeyguardExitResult(false);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
+                    mLogger.logFailedToCallOnKeyguardExitResultFalse(e);
                 }
             } else if (mExternallyEnabled) {
                 // this only applies when the user has externally disabled the
                 // keyguard.  this is unexpected and means the user is not
                 // using the api properly.
-                Log.w(TAG, "verifyUnlock called when not externally disabled");
+                mLogger.logVerifyUnlockCalledNotExternallyDisabled();
                 try {
                     callback.onKeyguardExitResult(false);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
+                    mLogger.logFailedToCallOnKeyguardExitResultFalse(e);
                 }
             } else if (mExitSecureCallback != null) {
                 // already in progress with someone else
                 try {
                     callback.onKeyguardExitResult(false);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
+                    mLogger.logFailedToCallOnKeyguardExitResultFalse(e);
                 }
             } else if (!isSecure()) {
 
@@ -1640,7 +1628,7 @@
                 try {
                     callback.onKeyguardExitResult(true);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
+                    mLogger.logFailedToCallOnKeyguardExitResultFalse(e);
                 }
             } else {
 
@@ -1649,7 +1637,7 @@
                 try {
                     callback.onKeyguardExitResult(false);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
+                    mLogger.logFailedToCallOnKeyguardExitResultFalse(e);
                 }
             }
         }
@@ -1667,10 +1655,8 @@
      * Notify us when the keyguard is occluded by another window
      */
     public void setOccluded(boolean isOccluded, boolean animate) {
-        Log.d(TAG, "setOccluded(" + isOccluded + ")");
-
         Trace.beginSection("KeyguardViewMediator#setOccluded");
-        if (DEBUG) Log.d(TAG, "setOccluded " + isOccluded);
+        mLogger.logSetOccluded(isOccluded);
         mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_TRANSITION_FROM_AOD);
         mHandler.removeMessages(SET_OCCLUDED);
         Message msg = mHandler.obtainMessage(SET_OCCLUDED, isOccluded ? 1 : 0, animate ? 1 : 0);
@@ -1699,7 +1685,7 @@
      */
     private void handleSetOccluded(boolean isOccluded, boolean animate) {
         Trace.beginSection("KeyguardViewMediator#handleSetOccluded");
-        Log.d(TAG, "handleSetOccluded(" + isOccluded + ")");
+        mLogger.logHandleSetOccluded(isOccluded);
         synchronized (KeyguardViewMediator.this) {
             if (mHiding && isOccluded) {
                 // We're in the process of going away but WindowManager wants to show a
@@ -1756,7 +1742,7 @@
                 try {
                     callback.onInputRestrictedStateChanged(inputRestricted);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to call onDeviceProvisioned", e);
+                    mLogger.logFailedToCallOnDeviceProvisioned(e);
                     if (e instanceof DeadObjectException) {
                         mKeyguardStateCallbacks.remove(callback);
                     }
@@ -1771,8 +1757,7 @@
     private void doKeyguardLocked(Bundle options) {
         // if another app is disabling us, don't show
         if (!mExternallyEnabled) {
-            if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
-
+            mLogger.logDoKeyguardNotShowingExternallyDisabled();
             mNeedToReshowWhenReenabled = true;
             return;
         }
@@ -1781,7 +1766,7 @@
         // to account for the hiding animation which results in a delay and discrepancy
         // between flags
         if (mShowing && mKeyguardViewControllerLazy.get().isShowing()) {
-            if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
+            mLogger.logDoKeyguardNotShowingAlreadyShowing();
             resetStateLocked();
             return;
         }
@@ -1800,20 +1785,19 @@
                     || ((absent || disabled) && requireSim);
 
             if (!lockedOrMissing && shouldWaitForProvisioning()) {
-                if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned"
-                        + " and the sim is not locked or missing");
+                mLogger.logDoKeyguardNotShowingDeviceNotProvisioned();
                 return;
             }
 
             boolean forceShow = options != null && options.getBoolean(OPTION_FORCE_SHOW, false);
             if (mLockPatternUtils.isLockScreenDisabled(KeyguardUpdateMonitor.getCurrentUser())
                     && !lockedOrMissing && !forceShow) {
-                if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
+                mLogger.logDoKeyguardNotShowingLockScreenOff();
                 return;
             }
         }
 
-        if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen");
+        mLogger.logDoKeyguardShowingLockScreen();
         showLocked(options);
     }
 
@@ -1851,32 +1835,23 @@
      * @see #handleReset
      */
     private void resetStateLocked() {
-        if (DEBUG) Log.e(TAG, "resetStateLocked");
+        mLogger.logResetStateLocked();
         Message msg = mHandler.obtainMessage(RESET);
         mHandler.sendMessage(msg);
     }
 
-    /**
-     * Send message to keyguard telling it to verify unlock
-     * @see #handleVerifyUnlock()
-     */
-    private void verifyUnlockLocked() {
-        if (DEBUG) Log.d(TAG, "verifyUnlockLocked");
-        mHandler.sendEmptyMessage(VERIFY_UNLOCK);
-    }
-
     private void notifyStartedGoingToSleep() {
-        if (DEBUG) Log.d(TAG, "notifyStartedGoingToSleep");
+        mLogger.logNotifyStartedGoingToSleep();
         mHandler.sendEmptyMessage(NOTIFY_STARTED_GOING_TO_SLEEP);
     }
 
     private void notifyFinishedGoingToSleep() {
-        if (DEBUG) Log.d(TAG, "notifyFinishedGoingToSleep");
+        mLogger.logNotifyFinishedGoingToSleep();
         mHandler.sendEmptyMessage(NOTIFY_FINISHED_GOING_TO_SLEEP);
     }
 
     private void notifyStartedWakingUp() {
-        if (DEBUG) Log.d(TAG, "notifyStartedWakingUp");
+        mLogger.logNotifyStartedWakingUp();
         mHandler.sendEmptyMessage(NOTIFY_STARTED_WAKING_UP);
     }
 
@@ -1886,7 +1861,7 @@
      */
     private void showLocked(Bundle options) {
         Trace.beginSection("KeyguardViewMediator#showLocked acquiring mShowKeyguardWakeLock");
-        if (DEBUG) Log.d(TAG, "showLocked");
+        mLogger.logShowLocked();
         // ensure we stay awake until we are finished displaying the keyguard
         mShowKeyguardWakeLock.acquire();
         Message msg = mHandler.obtainMessage(SHOW, options);
@@ -1903,7 +1878,7 @@
      */
     private void hideLocked() {
         Trace.beginSection("KeyguardViewMediator#hideLocked");
-        if (DEBUG) Log.d(TAG, "hideLocked");
+        mLogger.logHideLocked();
         Message msg = mHandler.obtainMessage(HIDE);
         mHandler.sendMessage(msg);
         Trace.endSection();
@@ -1982,8 +1957,7 @@
         public void onReceive(Context context, Intent intent) {
             if (DELAYED_KEYGUARD_ACTION.equals(intent.getAction())) {
                 final int sequence = intent.getIntExtra("seq", 0);
-                if (DEBUG) Log.d(TAG, "received DELAYED_KEYGUARD_ACTION with seq = "
-                        + sequence + ", mDelayedShowingSequence = " + mDelayedShowingSequence);
+                mLogger.logReceivedDelayedKeyguardAction(sequence, mDelayedShowingSequence);
                 synchronized (KeyguardViewMediator.this) {
                     if (mDelayedShowingSequence == sequence) {
                         doKeyguardLocked(null);
@@ -2016,7 +1990,7 @@
 
     private void keyguardDone() {
         Trace.beginSection("KeyguardViewMediator#keyguardDone");
-        if (DEBUG) Log.d(TAG, "keyguardDone()");
+        mLogger.logKeyguardDone();
         userActivity();
         EventLog.writeEvent(70000, 2);
         Message msg = mHandler.obtainMessage(KEYGUARD_DONE);
@@ -2108,7 +2082,7 @@
                 case KEYGUARD_DONE_PENDING_TIMEOUT:
                     Trace.beginSection("KeyguardViewMediator#handleMessage"
                             + " KEYGUARD_DONE_PENDING_TIMEOUT");
-                    Log.w(TAG, "Timeout while waiting for activity drawn!");
+                    mLogger.logTimeoutWhileActivityDrawn();
                     Trace.endSection();
                     break;
                 case SYSTEM_READY:
@@ -2119,14 +2093,15 @@
     };
 
     private void tryKeyguardDone() {
-        if (DEBUG) {
-            Log.d(TAG, "tryKeyguardDone: pending - " + mKeyguardDonePending + ", animRan - "
-                    + mHideAnimationRun + " animRunning - " + mHideAnimationRunning);
-        }
+        mLogger.logTryKeyguardDonePending(
+                mKeyguardDonePending,
+                mHideAnimationRun,
+                mHideAnimationRunning
+        );
         if (!mKeyguardDonePending && mHideAnimationRun && !mHideAnimationRunning) {
             handleKeyguardDone();
         } else if (!mHideAnimationRun) {
-            if (DEBUG) Log.d(TAG, "tryKeyguardDone: starting pre-hide animation");
+            mLogger.logTryKeyguardDonePreHideAnimation();
             mHideAnimationRun = true;
             mHideAnimationRunning = true;
             mKeyguardViewControllerLazy.get()
@@ -2146,14 +2121,14 @@
                 mLockPatternUtils.getDevicePolicyManager().reportKeyguardDismissed(currentUser);
             }
         });
-        if (DEBUG) Log.d(TAG, "handleKeyguardDone");
+        mLogger.logHandleKeyguardDone();
         synchronized (this) {
             resetKeyguardDonePendingLocked();
         }
 
         if (mGoingToSleep) {
             mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser);
-            Log.i(TAG, "Device is going to sleep, aborting keyguardDone");
+            mLogger.logDeviceGoingToSleep();
             return;
         }
         setPendingLock(false); // user may have authenticated during the screen off animation
@@ -2161,7 +2136,7 @@
             try {
                 mExitSecureCallback.onKeyguardExitResult(true /* authenciated */);
             } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to call onKeyguardExitResult()", e);
+                mLogger.logFailedToCallOnKeyguardExitResultTrue(e);
             }
 
             mExitSecureCallback = null;
@@ -2204,9 +2179,9 @@
     private void handleKeyguardDoneDrawing() {
         Trace.beginSection("KeyguardViewMediator#handleKeyguardDoneDrawing");
         synchronized(this) {
-            if (DEBUG) Log.d(TAG, "handleKeyguardDoneDrawing");
+            mLogger.logHandleKeyguardDoneDrawing();
             if (mWaitingUntilKeyguardVisible) {
-                if (DEBUG) Log.d(TAG, "handleKeyguardDoneDrawing: notifying mWaitingUntilKeyguardVisible");
+                mLogger.logHandleKeyguardDoneDrawingNotifyingKeyguardVisible();
                 mWaitingUntilKeyguardVisible = false;
                 notifyAll();
 
@@ -2256,12 +2231,11 @@
 
     private void updateActivityLockScreenState(boolean showing, boolean aodShowing) {
         mUiBgExecutor.execute(() -> {
-            if (DEBUG) {
-                Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
-            }
+            mLogger.logUpdateActivityLockScreenState(showing, aodShowing);
             try {
                 ActivityTaskManager.getService().setLockScreenShown(showing, aodShowing);
             } catch (RemoteException e) {
+                mLogger.logFailedToCallSetLockScreenShown(e);
             }
         });
     }
@@ -2278,10 +2252,10 @@
         }
         synchronized (KeyguardViewMediator.this) {
             if (!mSystemReady) {
-                if (DEBUG) Log.d(TAG, "ignoring handleShow because system is not ready.");
+                mLogger.logIgnoreHandleShow();
                 return;
             } else {
-                if (DEBUG) Log.d(TAG, "handleShow");
+                mLogger.logHandleShow();
             }
 
             mHiding = false;
@@ -2313,7 +2287,7 @@
         @Override
         public void run() {
             Trace.beginSection("KeyguardViewMediator.mKeyGuardGoingAwayRunnable");
-            if (DEBUG) Log.d(TAG, "keyguardGoingAway");
+            mLogger.logKeyguardGoingAway();
             mKeyguardViewControllerLazy.get().keyguardGoingAway();
 
             int flags = 0;
@@ -2357,7 +2331,7 @@
                 try {
                     ActivityTaskManager.getService().keyguardGoingAway(keyguardFlag);
                 } catch (RemoteException e) {
-                    Log.e(TAG, "Error while calling WindowManager", e);
+                    mLogger.logFailedToCallKeyguardGoingAway(keyguardFlag, e);
                 }
             });
             Trace.endSection();
@@ -2365,7 +2339,7 @@
     };
 
     private final Runnable mHideAnimationFinishedRunnable = () -> {
-        Log.e(TAG, "mHideAnimationFinishedRunnable#run");
+        mLogger.logHideAnimationFinishedRunnable();
         mHideAnimationRunning = false;
         tryKeyguardDone();
     };
@@ -2377,22 +2351,22 @@
     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");
         }
 
         synchronized (KeyguardViewMediator.this) {
-            if (DEBUG) Log.d(TAG, "handleHide");
+            mLogger.logHandleHide();
 
             if (mustNotUnlockCurrentUser()) {
                 // In split system user mode, we never unlock system user. The end user has to
                 // switch to another user.
                 // TODO: We should stop it early by disabling the swipe up flow. Right now swipe up
                 // still completes and makes the screen blank.
-                if (DEBUG) Log.d(TAG, "Split system user, quit unlocking.");
+                mLogger.logSplitSystemUserQuitUnlocking();
                 mKeyguardExitAnimationRunner = null;
                 return;
             }
@@ -2409,6 +2383,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();
     }
@@ -2417,8 +2398,7 @@
             RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
             RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) {
         Trace.beginSection("KeyguardViewMediator#handleStartKeyguardExitAnimation");
-        Log.d(TAG, "handleStartKeyguardExitAnimation startTime=" + startTime
-                + " fadeoutDuration=" + fadeoutDuration);
+        mLogger.logHandleStartKeyguardExitAnimation(startTime, fadeoutDuration);
         synchronized (KeyguardViewMediator.this) {
 
             // Tell ActivityManager that we canceled the keyguard animation if
@@ -2434,7 +2414,7 @@
                     try {
                         finishedCallback.onAnimationFinished();
                     } catch (RemoteException e) {
-                        Slog.w(TAG, "Failed to call onAnimationFinished", e);
+                        mLogger.logFailedToCallOnAnimationFinished(e);
                     }
                 }
                 setShowingLocked(mShowing, true /* force */);
@@ -2457,7 +2437,7 @@
                                 try {
                                     finishedCallback.onAnimationFinished();
                                 } catch (RemoteException e) {
-                                    Slog.w(TAG, "Failed to call onAnimationFinished", e);
+                                    mLogger.logFailedToCallOnAnimationFinished(e);
                                 }
                                 onKeyguardExitFinished();
                                 mKeyguardViewControllerLazy.get().hide(0 /* startTime */,
@@ -2476,7 +2456,7 @@
                     runner.onAnimationStart(WindowManager.TRANSIT_KEYGUARD_GOING_AWAY, apps,
                             wallpapers, nonApps, callback);
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to call onAnimationStart", e);
+                    mLogger.logFailedToCallOnAnimationStart(e);
                 }
 
             // When remaining on the shade, there's no need to do a fancy remote animation,
@@ -2541,7 +2521,7 @@
                             try {
                                 finishedCallback.onAnimationFinished();
                             } catch (RemoteException e) {
-                                Slog.e(TAG, "RemoteException");
+                                mLogger.logFailedToCallOnAnimationFinished(e);
                             } finally {
                                 mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
                             }
@@ -2552,7 +2532,7 @@
                             try {
                                 finishedCallback.onAnimationFinished();
                             } catch (RemoteException e) {
-                                Slog.e(TAG, "RemoteException");
+                                mLogger.logFailedToCallOnAnimationFinished(e);
                             } finally {
                                 mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
                             }
@@ -2621,11 +2601,9 @@
      * @param cancelled {@code true} if the animation was cancelled before it finishes.
      */
     public void onKeyguardExitRemoteAnimationFinished(boolean cancelled) {
-        Log.d(TAG, "onKeyguardExitRemoteAnimationFinished");
+        mLogger.logOnKeyguardExitRemoteAnimationFinished();
         if (!mSurfaceBehindRemoteAnimationRunning && !mSurfaceBehindRemoteAnimationRequested) {
-            Log.d(TAG, "skip onKeyguardExitRemoteAnimationFinished cancelled=" + cancelled
-                    + " surfaceAnimationRunning=" + mSurfaceBehindRemoteAnimationRunning
-                    + " surfaceAnimationRequested=" + mSurfaceBehindRemoteAnimationRequested);
+            mLogger.logSkipOnKeyguardExitRemoteAnimationFinished(cancelled, false, false);
             return;
         }
 
@@ -2639,13 +2617,13 @@
             onKeyguardExitFinished();
 
             if (mKeyguardStateController.isDismissingFromSwipe() || wasShowing) {
-                Log.d(TAG, "onKeyguardExitRemoteAnimationFinished"
-                        + "#hideKeyguardViewAfterRemoteAnimation");
+                mLogger.logOnKeyguardExitRemoteAnimationFinishedHideKeyguardView();
                 mKeyguardUnlockAnimationControllerLazy.get().hideKeyguardViewAfterRemoteAnimation();
             } else {
-                Log.d(TAG, "skip hideKeyguardViewAfterRemoteAnimation"
-                        + " dismissFromSwipe=" + mKeyguardStateController.isDismissingFromSwipe()
-                        + " wasShowing=" + wasShowing);
+                mLogger.logSkipHideKeyguardViewAfterRemoteAnimation(
+                        mKeyguardStateController.isDismissingFromSwipe(),
+                        wasShowing
+                );
             }
 
             finishSurfaceBehindRemoteAnimation(cancelled);
@@ -2742,7 +2720,7 @@
         }
 
         if (mStatusBarManager == null) {
-            Log.w(TAG, "Could not get status bar manager");
+            mLogger.logCouldNotGetStatusBarManager();
         } else {
             // Disable aspects of the system/status/navigation bars that must not be re-enabled by
             // windows that appear on top, ever
@@ -2760,12 +2738,13 @@
                 }
                 flags |= StatusBarManager.DISABLE_RECENT;
             }
-
-            if (DEBUG) {
-                Log.d(TAG, "adjustStatusBarLocked: mShowing=" + mShowing + " mOccluded=" + mOccluded
-                        + " isSecure=" + isSecure() + " force=" + forceHideHomeRecentsButtons
-                        +  " --> flags=0x" + Integer.toHexString(flags));
-            }
+            mLogger.logAdjustStatusBarLocked(
+                    mShowing,
+                    mOccluded,
+                    isSecure(),
+                    forceHideHomeRecentsButtons,
+                    Integer.toHexString(flags)
+            );
 
             mStatusBarManager.disable(flags);
         }
@@ -2777,7 +2756,7 @@
      */
     private void handleReset() {
         synchronized (KeyguardViewMediator.this) {
-            if (DEBUG) Log.d(TAG, "handleReset");
+            mLogger.logHandleReset();
             mKeyguardViewControllerLazy.get().reset(true /* hideBouncerWhenShowing */);
         }
     }
@@ -2789,7 +2768,7 @@
     private void handleVerifyUnlock() {
         Trace.beginSection("KeyguardViewMediator#handleVerifyUnlock");
         synchronized (KeyguardViewMediator.this) {
-            if (DEBUG) Log.d(TAG, "handleVerifyUnlock");
+            mLogger.logHandleVerifyUnlock();
             setShowingLocked(true);
             mKeyguardViewControllerLazy.get().dismissAndCollapse();
         }
@@ -2798,7 +2777,7 @@
 
     private void handleNotifyStartedGoingToSleep() {
         synchronized (KeyguardViewMediator.this) {
-            if (DEBUG) Log.d(TAG, "handleNotifyStartedGoingToSleep");
+            mLogger.logHandleNotifyStartedGoingToSleep();
             mKeyguardViewControllerLazy.get().onStartedGoingToSleep();
         }
     }
@@ -2809,7 +2788,7 @@
      */
     private void handleNotifyFinishedGoingToSleep() {
         synchronized (KeyguardViewMediator.this) {
-            if (DEBUG) Log.d(TAG, "handleNotifyFinishedGoingToSleep");
+            mLogger.logHandleNotifyFinishedGoingToSleep();
             mKeyguardViewControllerLazy.get().onFinishedGoingToSleep();
         }
     }
@@ -2817,7 +2796,7 @@
     private void handleNotifyStartedWakingUp() {
         Trace.beginSection("KeyguardViewMediator#handleMotifyStartedWakingUp");
         synchronized (KeyguardViewMediator.this) {
-            if (DEBUG) Log.d(TAG, "handleNotifyWakingUp");
+            mLogger.logHandleNotifyWakingUp();
             mKeyguardViewControllerLazy.get().onStartedWakingUp();
         }
         Trace.endSection();
@@ -3083,7 +3062,7 @@
                 try {
                     callback.onShowingStateChanged(showing, KeyguardUpdateMonitor.getCurrentUser());
                 } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to call onShowingStateChanged", e);
+                    mLogger.logFailedToCallOnShowingStateChanged(e);
                     if (e instanceof DeadObjectException) {
                         mKeyguardStateCallbacks.remove(callback);
                     }
@@ -3102,7 +3081,7 @@
             try {
                 mKeyguardStateCallbacks.get(i).onTrustedChanged(trusted);
             } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to call notifyTrustedChangedLocked", e);
+                mLogger.logFailedToCallNotifyTrustedChangedLocked(e);
                 if (e instanceof DeadObjectException) {
                     mKeyguardStateCallbacks.remove(i);
                 }
@@ -3125,7 +3104,7 @@
                 callback.onTrustedChanged(mUpdateMonitor.getUserHasTrust(
                         KeyguardUpdateMonitor.getCurrentUser()));
             } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to call to IKeyguardStateCallback", e);
+                mLogger.logFailedToCallIKeyguardStateCallback(e);
             }
         }
     }
@@ -3202,7 +3181,7 @@
             // internal state to reflect that immediately, vs. waiting for the launch animator to
             // begin. Otherwise, calls to setShowingLocked, etc. will not know that we're about to
             // be occluded and might re-show the keyguard.
-            Log.d(TAG, "OccludeAnimator#onAnimationStart. Set occluded = true.");
+            mLogger.logOccludeAnimatorOnAnimationStart();
             setOccluded(true /* isOccluded */, false /* animate */);
         }
 
@@ -3210,8 +3189,7 @@
         public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException {
             super.onAnimationCancelled(isKeyguardOccluded);
 
-            Log.d(TAG, "Occlude animation cancelled by WM. "
-                    + "Setting occluded state to: " + isKeyguardOccluded);
+            mLogger.logOccludeAnimationCancelledByWm(isKeyguardOccluded);
             setOccluded(isKeyguardOccluded /* occluded */, false /* animate */);
 
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 430b59c..fdea62d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -30,6 +30,7 @@
 import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
+import com.android.keyguard.logging.KeyguardViewMediatorLogger;
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -44,7 +45,6 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
-import com.android.systemui.keyguard.domain.usecase.KeyguardUseCaseModule;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -73,7 +73,6 @@
             FalsingModule.class,
             KeyguardQuickAffordanceModule.class,
             KeyguardRepositoryModule.class,
-            KeyguardUseCaseModule.class,
         })
 public class KeyguardModule {
     /**
@@ -107,7 +106,8 @@
             InteractionJankMonitor interactionJankMonitor,
             DreamOverlayStateController dreamOverlayStateController,
             Lazy<NotificationShadeWindowController> notificationShadeWindowController,
-            Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
+            Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
+            KeyguardViewMediatorLogger logger) {
         return new KeyguardViewMediator(
                 context,
                 falsingCollector,
@@ -134,7 +134,8 @@
                 interactionJankMonitor,
                 dreamOverlayStateController,
                 notificationShadeWindowController,
-                activityLaunchAnimator);
+                activityLaunchAnimator,
+                logger);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 62cf1a6..e52d9ee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.common.data.model.Position
+import com.android.systemui.common.shared.model.Position
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.policy.KeyguardStateController
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
new file mode 100644
index 0000000..ede50b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.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.keyguard.domain.interactor
+
+import com.android.systemui.common.shared.model.Position
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates business-logic specifically related to the keyguard bottom area. */
+@SysUISingleton
+class KeyguardBottomAreaInteractor
+@Inject
+constructor(
+    private val repository: KeyguardRepository,
+) {
+    /** Whether to animate the next doze mode transition. */
+    val animateDozingTransitions: Flow<Boolean> = repository.animateBottomAreaDozingTransitions
+    /** The amount of alpha for the UI components of the bottom area. */
+    val alpha: Flow<Float> = repository.bottomAreaAlpha
+    /** The position of the keyguard clock. */
+    val clockPosition: Flow<Position> = repository.clockPosition
+
+    fun setClockPosition(x: Int, y: Int) {
+        repository.setClockPosition(x, y)
+    }
+
+    fun setAlpha(alpha: Float) {
+        repository.setBottomAreaAlpha(alpha)
+    }
+
+    fun setAnimateDozingTransitions(animate: Boolean) {
+        repository.setAnimateDozingTransitions(animate)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
new file mode 100644
index 0000000..dccc941
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -0,0 +1,43 @@
+/*
+ *  Copyright (C) 2022 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Encapsulates business-logic related to the keyguard but not to a more specific part within it.
+ */
+@SysUISingleton
+class KeyguardInteractor
+@Inject
+constructor(
+    repository: KeyguardRepository,
+) {
+    /**
+     * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
+     * all.
+     */
+    val dozeAmount: Flow<Float> = repository.dozeAmount
+    /** Whether the system is in doze mode. */
+    val isDozing: Flow<Boolean> = repository.isDozing
+    /** Whether the keyguard is showing ot not. */
+    val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
new file mode 100644
index 0000000..9a69e26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -0,0 +1,136 @@
+/*
+ *  Copyright (C) 2022 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.content.Intent
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+@SysUISingleton
+class KeyguardQuickAffordanceInteractor
+@Inject
+constructor(
+    private val keyguardInteractor: KeyguardInteractor,
+    private val registry: KeyguardQuickAffordanceRegistry<out KeyguardQuickAffordanceConfig>,
+    private val lockPatternUtils: LockPatternUtils,
+    private val keyguardStateController: KeyguardStateController,
+    private val userTracker: UserTracker,
+    private val activityStarter: ActivityStarter,
+) {
+    /** Returns an observable for the quick affordance at the given position. */
+    fun quickAffordance(
+        position: KeyguardQuickAffordancePosition
+    ): Flow<KeyguardQuickAffordanceModel> {
+        return combine(
+            quickAffordanceInternal(position),
+            keyguardInteractor.isDozing,
+            keyguardInteractor.isKeyguardShowing,
+        ) { affordance, isDozing, isKeyguardShowing ->
+            if (!isDozing && isKeyguardShowing) {
+                affordance
+            } else {
+                KeyguardQuickAffordanceModel.Hidden
+            }
+        }
+    }
+
+    /**
+     * Notifies that a quick affordance has been clicked by the user.
+     *
+     * @param configKey The configuration key corresponding to the [KeyguardQuickAffordanceModel] of
+     * the affordance that was clicked
+     * @param animationController An optional controller for the activity-launch animation
+     */
+    fun onQuickAffordanceClicked(
+        configKey: KClass<out KeyguardQuickAffordanceConfig>,
+        animationController: ActivityLaunchAnimator.Controller?,
+    ) {
+        @Suppress("UNCHECKED_CAST") val config = registry.get(configKey as KClass<Nothing>)
+        when (val result = config.onQuickAffordanceClicked(animationController)) {
+            is KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity ->
+                launchQuickAffordance(
+                    intent = result.intent,
+                    canShowWhileLocked = result.canShowWhileLocked,
+                    animationController = animationController
+                )
+            is KeyguardQuickAffordanceConfig.OnClickedResult.Handled -> Unit
+        }
+    }
+
+    private fun quickAffordanceInternal(
+        position: KeyguardQuickAffordancePosition
+    ): Flow<KeyguardQuickAffordanceModel> {
+        val configs = registry.getAll(position)
+        return combine(configs.map { config -> config.state }) { states ->
+            val index = states.indexOfFirst { it is KeyguardQuickAffordanceConfig.State.Visible }
+            if (index != -1) {
+                val visibleState = states[index] as KeyguardQuickAffordanceConfig.State.Visible
+                KeyguardQuickAffordanceModel.Visible(
+                    configKey = configs[index]::class,
+                    icon = visibleState.icon,
+                    contentDescriptionResourceId = visibleState.contentDescriptionResourceId,
+                )
+            } else {
+                KeyguardQuickAffordanceModel.Hidden
+            }
+        }
+    }
+
+    private fun launchQuickAffordance(
+        intent: Intent,
+        canShowWhileLocked: Boolean,
+        animationController: ActivityLaunchAnimator.Controller?,
+    ) {
+        @LockPatternUtils.StrongAuthTracker.StrongAuthFlags
+        val strongAuthFlags =
+            lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
+        val needsToUnlockFirst =
+            when {
+                strongAuthFlags ==
+                    LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT -> true
+                !canShowWhileLocked && !keyguardStateController.isUnlocked -> true
+                else -> false
+            }
+        if (needsToUnlockFirst) {
+            activityStarter.postStartActivityDismissingKeyguard(
+                intent,
+                0 /* delay */,
+                animationController
+            )
+        } else {
+            activityStarter.startActivity(
+                intent,
+                true /* dismissShade */,
+                animationController,
+                true /* showOverLockscreenWhenLocked */,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
index 411a2ca..eff1469 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
@@ -42,21 +42,4 @@
          */
         @StringRes val contentDescriptionResourceId: Int,
     ) : KeyguardQuickAffordanceModel()
-
-    companion object {
-        fun from(
-            state: KeyguardQuickAffordanceConfig.State?,
-            configKey: KClass<out KeyguardQuickAffordanceConfig>,
-        ): KeyguardQuickAffordanceModel {
-            return when (state) {
-                is KeyguardQuickAffordanceConfig.State.Visible ->
-                    Visible(
-                        configKey = configKey,
-                        icon = state.icon,
-                        contentDescriptionResourceId = state.contentDescriptionResourceId,
-                    )
-                else -> Hidden
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
index a7b3828..94024d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
@@ -25,5 +25,5 @@
     @Binds
     fun keyguardQuickAffordanceRegistry(
         impl: KeyguardQuickAffordanceRegistryImpl
-    ): KeyguardQuickAffordanceRegistry
+    ): KeyguardQuickAffordanceRegistry<out KeyguardQuickAffordanceConfig>
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
index 2c37f93..ad40ee7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
@@ -22,9 +22,9 @@
 import kotlin.reflect.KClass
 
 /** Central registry of all known quick affordance configs. */
-interface KeyguardQuickAffordanceRegistry {
-    fun getAll(position: KeyguardQuickAffordancePosition): List<KeyguardQuickAffordanceConfig>
-    fun get(configClass: KClass<out KeyguardQuickAffordanceConfig>): KeyguardQuickAffordanceConfig
+interface KeyguardQuickAffordanceRegistry<T : KeyguardQuickAffordanceConfig> {
+    fun getAll(position: KeyguardQuickAffordancePosition): List<T>
+    fun get(configClass: KClass<out T>): T
 }
 
 class KeyguardQuickAffordanceRegistryImpl
@@ -33,7 +33,7 @@
     homeControls: HomeControlsKeyguardQuickAffordanceConfig,
     quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
     qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
-) : KeyguardQuickAffordanceRegistry {
+) : KeyguardQuickAffordanceRegistry<KeyguardQuickAffordanceConfig> {
     private val configsByPosition =
         mapOf(
             KeyguardQuickAffordancePosition.BOTTOM_START to
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
deleted file mode 100644
index 403d343..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import dagger.Binds
-import dagger.Module
-
-@Module
-interface KeyguardUseCaseModule {
-
-    @Binds
-    fun launchQuickAffordance(
-        impl: LaunchKeyguardQuickAffordanceUseCaseImpl
-    ): LaunchKeyguardQuickAffordanceUseCase
-
-    @Binds
-    fun observeKeyguardQuickAffordance(
-        impl: ObserveKeyguardQuickAffordanceUseCaseImpl
-    ): ObserveKeyguardQuickAffordanceUseCase
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt
deleted file mode 100644
index 3d60399..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import android.content.Intent
-import com.android.internal.widget.LockPatternUtils
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.StrongAuthFlags
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import javax.inject.Inject
-
-/** Defines interface for classes that can launch a quick affordance. */
-interface LaunchKeyguardQuickAffordanceUseCase {
-    operator fun invoke(
-        intent: Intent,
-        canShowWhileLocked: Boolean,
-        animationController: ActivityLaunchAnimator.Controller?,
-    )
-}
-
-/** Real implementation of [LaunchKeyguardQuickAffordanceUseCase] */
-class LaunchKeyguardQuickAffordanceUseCaseImpl
-@Inject
-constructor(
-    private val lockPatternUtils: LockPatternUtils,
-    private val keyguardStateController: KeyguardStateController,
-    private val userTracker: UserTracker,
-    private val activityStarter: ActivityStarter,
-) : LaunchKeyguardQuickAffordanceUseCase {
-    override operator fun invoke(
-        intent: Intent,
-        canShowWhileLocked: Boolean,
-        animationController: ActivityLaunchAnimator.Controller?,
-    ) {
-        @StrongAuthFlags
-        val strongAuthFlags =
-            lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
-        val needsToUnlockFirst =
-            when {
-                strongAuthFlags ==
-                    LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT -> true
-                !canShowWhileLocked && !keyguardStateController.isUnlocked -> true
-                else -> false
-            }
-        if (needsToUnlockFirst) {
-            activityStarter.postStartActivityDismissingKeyguard(
-                intent,
-                0 /* delay */,
-                animationController
-            )
-        } else {
-            activityStarter.startActivity(
-                intent,
-                true /* dismissShade */,
-                animationController,
-                true /* showOverLockscreenWhenLocked */,
-            )
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt
deleted file mode 100644
index ca37727..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Use-case for observing whether doze state transitions should animate the bottom area */
-class ObserveAnimateBottomAreaTransitionsUseCase
-@Inject
-constructor(
-    private val repository: KeyguardRepository,
-) {
-    operator fun invoke(): Flow<Boolean> {
-        return repository.animateBottomAreaDozingTransitions
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt
deleted file mode 100644
index 151b704..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Use-case for observing the alpha of the bottom area */
-class ObserveBottomAreaAlphaUseCase
-@Inject
-constructor(
-    private val repository: KeyguardRepository,
-) {
-    operator fun invoke(): Flow<Float> {
-        return repository.bottomAreaAlpha
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt
deleted file mode 100644
index 02c5737..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.common.domain.model.Position
-import com.android.systemui.common.domain.model.Position.Companion.toDomainLayer
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-
-/** Use-case for observing the position of the clock. */
-class ObserveClockPositionUseCase
-@Inject
-constructor(
-    private val repository: KeyguardRepository,
-) {
-    operator fun invoke(): Flow<Position> {
-        return repository.clockPosition.map { it.toDomainLayer() }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt
deleted file mode 100644
index 56d6182..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Use-case for observing the amount of doze the system is in. */
-class ObserveDozeAmountUseCase
-@Inject
-constructor(
-    private val repository: KeyguardRepository,
-) {
-    operator fun invoke(): Flow<Float> {
-        return repository.dozeAmount
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt
deleted file mode 100644
index 1d241d9..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Use-case for observing whether we are dozing. */
-class ObserveIsDozingUseCase
-@Inject
-constructor(
-    private val repository: KeyguardRepository,
-) {
-    operator fun invoke(): Flow<Boolean> {
-        return repository.isDozing
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt
deleted file mode 100644
index 11af123..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- *  Copyright (C) 2022 The Android Open Source Project
- *
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
- *
- *       http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/**
- * Use-case for observing whether the keyguard is currently being shown.
- *
- * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in the
- * z-order (which is not really above the system UI window, but rather - the lock-screen becomes
- * invisible to reveal the "occluding activity").
- */
-class ObserveIsKeyguardShowingUseCase
-@Inject
-constructor(
-    private val repository: KeyguardRepository,
-) {
-    operator fun invoke(): Flow<Boolean> {
-        return repository.isKeyguardShowing
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
deleted file mode 100644
index 8dee8b3..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-
-/** Defines interface for use-case for observing the model of a quick affordance in the keyguard. */
-interface ObserveKeyguardQuickAffordanceUseCase {
-    operator fun invoke(
-        position: KeyguardQuickAffordancePosition
-    ): Flow<KeyguardQuickAffordanceModel>
-}
-
-class ObserveKeyguardQuickAffordanceUseCaseImpl
-@Inject
-constructor(
-    private val registry: KeyguardQuickAffordanceRegistry,
-    private val isDozingUseCase: ObserveIsDozingUseCase,
-    private val isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase,
-) : ObserveKeyguardQuickAffordanceUseCase {
-    override fun invoke(
-        position: KeyguardQuickAffordancePosition
-    ): Flow<KeyguardQuickAffordanceModel> {
-        return combine(
-            affordance(position),
-            isDozingUseCase(),
-            isKeyguardShowingUseCase(),
-        ) { affordance, isDozing, isKeyguardShowing ->
-            if (!isDozing && isKeyguardShowing) {
-                affordance
-            } else {
-                KeyguardQuickAffordanceModel.Hidden
-            }
-        }
-    }
-
-    private fun affordance(
-        position: KeyguardQuickAffordancePosition
-    ): Flow<KeyguardQuickAffordanceModel> {
-        val configs = registry.getAll(position)
-        return combine(configs.map { config -> config.state }) { states ->
-            val index =
-                states.indexOfFirst { state ->
-                    state is KeyguardQuickAffordanceConfig.State.Visible
-                }
-            val visibleState =
-                if (index != -1) {
-                    states[index] as KeyguardQuickAffordanceConfig.State.Visible
-                } else {
-                    null
-                }
-            KeyguardQuickAffordanceModel.from(visibleState, configs[index]::class)
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
deleted file mode 100644
index 9315339..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.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.keyguard.domain.usecase
-
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
-import javax.inject.Inject
-import kotlin.reflect.KClass
-
-/** Use-case for handling a click on a keyguard quick affordance (e.g. bottom button). */
-class OnKeyguardQuickAffordanceClickedUseCase
-@Inject
-constructor(
-    private val registry: KeyguardQuickAffordanceRegistry,
-    private val launchAffordanceUseCase: LaunchKeyguardQuickAffordanceUseCase,
-) {
-    operator fun invoke(
-        configKey: KClass<*>,
-        animationController: ActivityLaunchAnimator.Controller?,
-    ) {
-        @Suppress("UNCHECKED_CAST")
-        val config = registry.get(configKey as KClass<out KeyguardQuickAffordanceConfig>)
-        when (val result = config.onQuickAffordanceClicked(animationController)) {
-            is OnClickedResult.StartActivity ->
-                launchAffordanceUseCase(
-                    intent = result.intent,
-                    canShowWhileLocked = result.canShowWhileLocked,
-                    animationController = animationController
-                )
-            is OnClickedResult.Handled -> Unit
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt
deleted file mode 100644
index 8f746e5..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-
-/** Use-case for setting the updated clock position. */
-class SetClockPositionUseCase
-@Inject
-constructor(
-    private val keyguardRepository: KeyguardRepository,
-) {
-    operator fun invoke(x: Int, y: Int) {
-        keyguardRepository.setClockPosition(x, y)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt
deleted file mode 100644
index 90be1ec..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-
-/** Use-case for setting the alpha that the keyguard bottom area should use */
-class SetKeyguardBottomAreaAlphaUseCase
-@Inject
-constructor(
-    private val repository: KeyguardRepository,
-) {
-    operator fun invoke(alpha: Float) {
-        repository.setBottomAreaAlpha(alpha)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt
deleted file mode 100644
index 007780a..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.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.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-
-/**
- * Use-case for setting whether the keyguard bottom area should animate the next doze transitions
- */
-class SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
-@Inject
-constructor(
-    private val repository: KeyguardRepository,
-) {
-    operator fun invoke(animate: Boolean) {
-        repository.setAnimateDozingTransitions(animate)
-    }
-}
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 04d30bf..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
@@ -95,35 +95,23 @@
         view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 launch {
-                    combine(viewModel.startButton, viewModel.animateButtonReveal) {
-                            buttonModel,
-                            animateReveal ->
-                            Pair(buttonModel, animateReveal)
-                        }
-                        .collect { (buttonModel, animateReveal) ->
-                            updateButton(
-                                view = startButton,
-                                viewModel = buttonModel,
-                                animateReveal = animateReveal,
-                                falsingManager = falsingManager,
-                            )
-                        }
+                    viewModel.startButton.collect { buttonModel ->
+                        updateButton(
+                            view = startButton,
+                            viewModel = buttonModel,
+                            falsingManager = falsingManager,
+                        )
+                    }
                 }
 
                 launch {
-                    combine(viewModel.endButton, viewModel.animateButtonReveal) {
-                            buttonModel,
-                            animateReveal ->
-                            Pair(buttonModel, animateReveal)
-                        }
-                        .collect { (buttonModel, animateReveal) ->
-                            updateButton(
-                                view = endButton,
-                                viewModel = buttonModel,
-                                animateReveal = animateReveal,
-                                falsingManager = falsingManager,
-                            )
-                        }
+                    viewModel.endButton.collect { buttonModel ->
+                        updateButton(
+                            view = endButton,
+                            viewModel = buttonModel,
+                            falsingManager = falsingManager,
+                        )
+                    }
                 }
 
                 launch {
@@ -226,7 +214,6 @@
     private fun updateButton(
         view: ImageView,
         viewModel: KeyguardQuickAffordanceViewModel,
-        animateReveal: Boolean,
         falsingManager: FalsingManager,
     ) {
         if (!viewModel.isVisible) {
@@ -236,7 +223,7 @@
 
         if (!view.isVisible) {
             view.isVisible = true
-            if (animateReveal) {
+            if (viewModel.animateReveal) {
                 view.alpha = 0f
                 view.translationY = view.height / 2f
                 view
@@ -264,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 d296e76..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,16 +16,13 @@
 
 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
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
-import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -36,32 +33,39 @@
 class KeyguardBottomAreaViewModel
 @Inject
 constructor(
-    private val observeQuickAffordanceUseCase: ObserveKeyguardQuickAffordanceUseCase,
-    private val onQuickAffordanceClickedUseCase: OnKeyguardQuickAffordanceClickedUseCase,
-    observeBottomAreaAlphaUseCase: ObserveBottomAreaAlphaUseCase,
-    observeIsDozingUseCase: ObserveIsDozingUseCase,
-    observeAnimateBottomAreaTransitionsUseCase: ObserveAnimateBottomAreaTransitionsUseCase,
-    private val observeDozeAmountUseCase: ObserveDozeAmountUseCase,
-    observeClockPositionUseCase: ObserveClockPositionUseCase,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
+    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)
     /** An observable for the view-model of the "end button" quick affordance. */
     val endButton: Flow<KeyguardQuickAffordanceViewModel> =
         button(KeyguardQuickAffordancePosition.BOTTOM_END)
-    /**
-     * An observable for whether the next time a quick action button becomes visible, it should
-     * animate.
-     */
-    val animateButtonReveal: Flow<Boolean> =
-        observeAnimateBottomAreaTransitionsUseCase().distinctUntilChanged()
     /** An observable for whether the overlay container should be visible. */
     val isOverlayContainerVisible: Flow<Boolean> =
-        observeIsDozingUseCase().map { !it }.distinctUntilChanged()
+        keyguardInteractor.isDozing.map { !it }.distinctUntilChanged()
     /** An observable for the alpha level for the entire bottom area. */
-    val alpha: Flow<Float> = observeBottomAreaAlphaUseCase().distinctUntilChanged()
+    val alpha: Flow<Float> = bottomAreaInteractor.alpha.distinctUntilChanged()
     /** An observable for whether the indication area should be padded. */
     val isIndicationAreaPadded: Flow<Boolean> =
         combine(startButton, endButton) { startButtonModel, endButtonModel ->
@@ -70,11 +74,11 @@
             .distinctUntilChanged()
     /** An observable for the x-offset by which the indication area should be translated. */
     val indicationAreaTranslationX: Flow<Float> =
-        observeClockPositionUseCase().map { it.x.toFloat() }.distinctUntilChanged()
+        bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
 
     /** Returns an observable for the y-offset by which the indication area should be translated. */
     fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
-        return observeDozeAmountUseCase()
+        return keyguardInteractor.dozeAmount
             .map { dozeAmount ->
                 dozeAmount *
                     (burnInHelperWrapper.burnInOffset(
@@ -88,27 +92,51 @@
     private fun button(
         position: KeyguardQuickAffordancePosition
     ): Flow<KeyguardQuickAffordanceViewModel> {
-        return observeQuickAffordanceUseCase(position)
-            .map { model -> model.toViewModel() }
+        return combine(
+                quickAffordanceInteractor.quickAffordance(position),
+                bottomAreaInteractor.animateDozingTransitions.distinctUntilChanged(),
+                areQuickAffordancesFullyOpaque,
+            ) { model, animateReveal, isFullyOpaque ->
+                model.toViewModel(
+                    animateReveal = animateReveal,
+                    isClickable = isFullyOpaque,
+                )
+            }
             .distinctUntilChanged()
     }
 
-    private fun KeyguardQuickAffordanceModel.toViewModel(): KeyguardQuickAffordanceViewModel {
+    private fun KeyguardQuickAffordanceModel.toViewModel(
+        animateReveal: Boolean,
+        isClickable: Boolean,
+    ): KeyguardQuickAffordanceViewModel {
         return when (this) {
             is KeyguardQuickAffordanceModel.Visible ->
                 KeyguardQuickAffordanceViewModel(
                     configKey = configKey,
                     isVisible = true,
+                    animateReveal = animateReveal,
                     icon = icon,
                     contentDescriptionResourceId = contentDescriptionResourceId,
                     onClicked = { parameters ->
-                        onQuickAffordanceClickedUseCase(
+                        quickAffordanceInteractor.onQuickAffordanceClicked(
                             configKey = parameters.configKey,
                             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 2417998..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
@@ -19,18 +19,22 @@
 import androidx.annotation.StringRes
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
 import kotlin.reflect.KClass
 
 /** Models the UI state of a keyguard quick affordance button. */
 data class KeyguardQuickAffordanceViewModel(
-    val configKey: KClass<*>? = null,
+    val configKey: KClass<out KeyguardQuickAffordanceConfig>? = null,
     val isVisible: Boolean = false,
+    /** Whether to animate the transition of the quick affordance from invisible to visible. */
+    val animateReveal: Boolean = false,
     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<*>,
+        val configKey: KClass<out KeyguardQuickAffordanceConfig>,
         val animationController: ActivityLaunchAnimator.Controller?,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index 6124e10..77ad806 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -158,8 +158,13 @@
      * add more detail to every log may do more to improve overall logging than adding more logs
      * with this method.
      */
-    fun log(tag: String, level: LogLevel, @CompileTimeConstant message: String) =
-            log(tag, level, {str1 = message}, { str1!! })
+    fun log(
+            tag: String,
+            level: LogLevel,
+            @CompileTimeConstant message: String,
+            exception: Throwable? = null
+    ) =
+            log(tag, level, {str1 = message}, { str1!! }, exception)
 
     /**
      * You should call [log] instead of this method.
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
index 323ee21..684839f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
@@ -1,4 +1,7 @@
 package com.android.systemui.log.dagger
 
+import javax.inject.Qualifier
+
 /** A [com.android.systemui.log.LogBuffer] for KeyguardUpdateMonitor. */
+@Qualifier
 annotation class KeyguardUpdateMonitorLog
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardViewMediatorLog.kt
similarity index 75%
copy from packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
copy to packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardViewMediatorLog.kt
index 7c9df10..88e227b 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardViewMediatorLog.kt
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.common.data.model
+package com.android.systemui.log.dagger
 
-/** Models a two-dimensional position */
-data class Position(
-    val x: Int,
-    val y: Int,
-)
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for KeyguardViewMediator. */
+@Qualifier
+annotation class KeyguardViewMediatorLog
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index c858bc3..9af42f8 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -84,6 +84,14 @@
         return factory.create("LSShadeTransitionLog", 50);
     }
 
+    /** Provides a logging buffer for Shade messages. */
+    @Provides
+    @SysUISingleton
+    @ShadeLog
+    public static LogBuffer provideShadeLogBuffer(LogBufferFactory factory) {
+        return factory.create("ShadeLog", 500, false);
+    }
+
     /** Provides a logging buffer for all logs related to managing notification sections. */
     @Provides
     @SysUISingleton
@@ -262,7 +270,7 @@
     @SysUISingleton
     @StatusBarConnectivityLog
     public static LogBuffer provideStatusBarConnectivityBuffer(LogBufferFactory factory) {
-        return factory.create("StatusBarConnectivityLog", 64);
+        return factory.create("SbConnectivity", 64);
     }
 
     /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
@@ -297,4 +305,15 @@
     public static LogBuffer provideKeyguardUpdateMonitorLogBuffer(LogBufferFactory factory) {
         return factory.create("KeyguardUpdateMonitorLog", 200);
     }
+
+    /**
+     * Provides a {@link LogBuffer} for use by
+     * {@link com.android.systemui.keyguard.KeyguardViewMediator}.
+     */
+    @Provides
+    @SysUISingleton
+    @KeyguardViewMediatorLog
+    public static LogBuffer provideKeyguardViewMediatorLogBuffer(LogBufferFactory factory) {
+        return factory.create("KeyguardViewMediatorLog", 100);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
similarity index 60%
rename from packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt
rename to packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
index f697c0a..bd0d298 100644
--- a/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
@@ -14,21 +14,20 @@
  * limitations under the License.
  */
 
-package com.android.systemui.common.domain.model
+package com.android.systemui.log.dagger;
 
-import com.android.systemui.common.data.model.Position as DataLayerPosition
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-/** Models a two-dimensional position */
-data class Position(
-    val x: Int,
-    val y: Int,
-) {
-    companion object {
-        fun DataLayerPosition.toDomainLayer(): Position {
-            return Position(
-                x = x,
-                y = y,
-            )
-        }
-    }
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for Shade touch handling messages. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface ShadeLog {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
index 237b505..32600fb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
@@ -18,19 +18,25 @@
 
 import android.content.Context
 import android.content.res.Configuration
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
 import android.view.View
 import android.view.ViewGroup
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.dagger.MediaModule.KEYGUARD
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.stack.MediaContainerView
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.LargeScreenUtils
+import com.android.systemui.util.settings.SecureSettings
 import javax.inject.Inject
 import javax.inject.Named
 
@@ -43,9 +49,10 @@
     @param:Named(KEYGUARD) private val mediaHost: MediaHost,
     private val bypassController: KeyguardBypassController,
     private val statusBarStateController: SysuiStatusBarStateController,
-    private val notifLockscreenUserManager: NotificationLockscreenUserManager,
     private val context: Context,
-    configurationController: ConfigurationController
+    private val secureSettings: SecureSettings,
+    @Main private val handler: Handler,
+    configurationController: ConfigurationController,
 ) {
 
     init {
@@ -60,6 +67,24 @@
             }
         })
 
+        val settingsObserver: ContentObserver = object : ContentObserver(handler) {
+            override fun onChange(selfChange: Boolean, uri: Uri?) {
+                if (uri == lockScreenMediaPlayerUri) {
+                    allowMediaPlayerOnLockScreen =
+                            secureSettings.getBoolForUser(
+                                    Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
+                                    true,
+                                    UserHandle.USER_CURRENT
+                            )
+                    refreshMediaPosition()
+                }
+            }
+        }
+        secureSettings.registerContentObserverForUser(
+                Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
+                settingsObserver,
+                UserHandle.USER_ALL)
+
         // First let's set the desired state that we want for this host
         mediaHost.expansion = MediaHostState.EXPANDED
         mediaHost.showsOnlyActiveMedia = true
@@ -101,6 +126,13 @@
     private var splitShadeContainer: ViewGroup? = null
 
     /**
+     * Track the media player setting status on lock screen.
+     */
+    private var allowMediaPlayerOnLockScreen: Boolean = true
+    private val lockScreenMediaPlayerUri =
+            secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN)
+
+    /**
      * Attaches media container in single pane mode, situated at the top of the notifications list
      */
     fun attachSinglePaneContainer(mediaView: MediaContainerView?) {
@@ -164,7 +196,7 @@
         visible = mediaHost.visible &&
                 !bypassController.bypassEnabled &&
                 keyguardOrUserSwitcher &&
-                notifLockscreenUserManager.shouldShowLockscreenNotifications()
+                allowMediaPlayerOnLockScreen
         if (visible) {
             showMediaPlayer()
         } else {
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/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 458ed40..ae4c7c7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -22,7 +22,12 @@
 import android.annotation.IntDef
 import android.content.Context
 import android.content.res.Configuration
+import android.database.ContentObserver
 import android.graphics.Rect
+import android.net.Uri
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
 import android.util.Log
 import android.util.MathUtils
 import android.view.View
@@ -33,11 +38,12 @@
 import com.android.systemui.R
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dreams.DreamOverlayStateController
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.NotifPanelEvents
 import com.android.systemui.statusbar.CrossFadeHelper
-import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
@@ -46,6 +52,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.LargeScreenUtils
 import com.android.systemui.util.animation.UniqueObjectHostView
+import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.traceSection
 import javax.inject.Inject
 
@@ -84,14 +91,31 @@
     private val keyguardStateController: KeyguardStateController,
     private val bypassController: KeyguardBypassController,
     private val mediaCarouselController: MediaCarouselController,
-    private val notifLockscreenUserManager: NotificationLockscreenUserManager,
+    private val keyguardViewController: KeyguardViewController,
+    private val dreamOverlayStateController: DreamOverlayStateController,
     configurationController: ConfigurationController,
     wakefulnessLifecycle: WakefulnessLifecycle,
-    private val keyguardViewController: KeyguardViewController,
-    private val dreamOverlayStateController: DreamOverlayStateController
+    panelEventsEvents: NotifPanelEvents,
+    private val secureSettings: SecureSettings,
+    @Main private val handler: Handler,
 ) {
 
     /**
+     * Track the media player setting status on lock screen.
+     */
+    private var allowMediaPlayerOnLockScreen: Boolean = true
+    private val lockScreenMediaPlayerUri =
+            secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN)
+
+    /**
+     * Whether we "skip" QQS during panel expansion.
+     *
+     * This means that when expanding the panel we go directly to QS. Also when we are on QS and
+     * start closing the panel, it fully collapses instead of going to QQS.
+     */
+    private var skipQqsOnExpansion: Boolean = false
+
+    /**
      * The root overlay of the hierarchy. This is where the media notification is attached to
      * whenever the view is transitioning from one host to another. It also make sure that the
      * view is always in its final state when it is attached to a view host.
@@ -504,6 +528,30 @@
         mediaCarouselController.updateUserVisibility = {
             mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
         }
+
+        panelEventsEvents.registerListener(object : NotifPanelEvents.Listener {
+            override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {
+                skipQqsOnExpansion = isExpandImmediateEnabled
+                updateDesiredLocation()
+            }
+        })
+
+        val settingsObserver: ContentObserver = object : ContentObserver(handler) {
+            override fun onChange(selfChange: Boolean, uri: Uri?) {
+                if (uri == lockScreenMediaPlayerUri) {
+                    allowMediaPlayerOnLockScreen =
+                            secureSettings.getBoolForUser(
+                                    Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
+                                    true,
+                                    UserHandle.USER_CURRENT
+                            )
+                }
+            }
+        }
+        secureSettings.registerContentObserverForUser(
+                Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
+                settingsObserver,
+                UserHandle.USER_ALL)
     }
 
     private fun updateConfiguration() {
@@ -701,6 +749,9 @@
         if (isCurrentlyInGuidedTransformation()) {
             return false
         }
+        if (skipQqsOnExpansion) {
+            return false
+        }
         // This is an invalid transition, and can happen when using the camera gesture from the
         // lock screen. Disallow.
         if (previousLocation == LOCATION_LOCKSCREEN &&
@@ -852,6 +903,9 @@
      * otherwise
      */
     private fun getTransformationProgress(): Float {
+        if (skipQqsOnExpansion) {
+            return -1.0f
+        }
         val progress = getQSTransformationProgress()
         if (statusbarState != StatusBarState.KEYGUARD && progress >= 0) {
             return progress
@@ -1013,7 +1067,6 @@
         }
         val onLockscreen = (!bypassController.bypassEnabled &&
             (statusbarState == StatusBarState.KEYGUARD))
-        val allowedOnLockscreen = notifLockscreenUserManager.shouldShowLockscreenNotifications()
         val location = when {
             dreamOverlayActive -> LOCATION_DREAM_OVERLAY
             (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
@@ -1021,7 +1074,7 @@
             !hasActiveMedia -> LOCATION_QS
             onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
             onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
-            onLockscreen && allowedOnLockscreen -> LOCATION_LOCKSCREEN
+            onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
             else -> LOCATION_QQS
         }
         // When we're on lock screen and the player is not active, we should keep it in QS.
@@ -1042,6 +1095,10 @@
             // reattach it without an animation
             return LOCATION_LOCKSCREEN
         }
+        if (skipQqsOnExpansion) {
+            // When doing an immediate expand or collapse, we want to keep it in QS.
+            return LOCATION_QS
+        }
         return location
     }
 
@@ -1089,7 +1146,7 @@
         return !statusBarStateController.isDozing &&
                 !keyguardViewController.isBouncerShowing &&
                 statusBarStateController.state == StatusBarState.KEYGUARD &&
-                notifLockscreenUserManager.shouldShowLockscreenNotifications() &&
+                allowMediaPlayerOnLockScreen &&
                 statusBarStateController.isExpanded &&
                 !qsExpanded
     }
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/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 7e263d8..08cf57c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -296,27 +296,17 @@
         mMediaOutputController.setRefreshing(true);
         // Update header icon
         final int iconRes = getHeaderIconRes();
-        final IconCompat iconCompat = getHeaderIcon();
-        final Drawable appSourceDrawable = getAppSourceIcon();
+        final IconCompat headerIcon = getHeaderIcon();
+        final IconCompat appSourceIcon = getAppSourceIcon();
         boolean colorSetUpdated = false;
         mCastAppLayout.setVisibility(
                 mMediaOutputController.shouldShowLaunchSection()
                         ? View.VISIBLE : View.GONE);
-        if (appSourceDrawable != null) {
-            mAppResourceIcon.setImageDrawable(appSourceDrawable);
-            mAppButton.setCompoundDrawablesWithIntrinsicBounds(resizeDrawable(appSourceDrawable,
-                            mContext.getResources().getDimensionPixelSize(
-                                    R.dimen.media_output_dialog_app_tier_icon_size
-                            )),
-                    null, null, null);
-        } else {
-            mAppResourceIcon.setVisibility(View.GONE);
-        }
         if (iconRes != 0) {
             mHeaderIcon.setVisibility(View.VISIBLE);
             mHeaderIcon.setImageResource(iconRes);
-        } else if (iconCompat != null) {
-            Icon icon = iconCompat.toIcon(mContext);
+        } else if (headerIcon != null) {
+            Icon icon = headerIcon.toIcon(mContext);
             if (icon.getType() != Icon.TYPE_BITMAP && icon.getType() != Icon.TYPE_ADAPTIVE_BITMAP) {
                 // icon doesn't support getBitmap, use default value for color scheme
                 updateButtonBackgroundColorFilter();
@@ -336,6 +326,18 @@
         } else {
             mHeaderIcon.setVisibility(View.GONE);
         }
+        if (appSourceIcon != null) {
+            Icon appIcon = appSourceIcon.toIcon(mContext);
+            mAppResourceIcon.setColorFilter(mMediaOutputController.getColorItemContent());
+            mAppResourceIcon.setImageIcon(appIcon);
+        } else {
+            Drawable appIconDrawable = mMediaOutputController.getAppSourceIconFromPackage();
+            if (appIconDrawable != null) {
+                mAppResourceIcon.setImageDrawable(appIconDrawable);
+            } else {
+                mAppResourceIcon.setVisibility(View.GONE);
+            }
+        }
         if (mHeaderIcon.getVisibility() == View.VISIBLE) {
             final int size = getHeaderIconSize();
             final int padding = mContext.getResources().getDimensionPixelSize(
@@ -480,7 +482,7 @@
         }
     }
 
-    abstract Drawable getAppSourceIcon();
+    abstract IconCompat getAppSourceIcon();
 
     abstract int getHeaderIconRes();
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index 310469d..35baf013 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -19,7 +19,6 @@
 import android.app.AlertDialog;
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.text.method.HideReturnsTransformationMethod;
 import android.text.method.PasswordTransformationMethod;
@@ -116,8 +115,8 @@
     }
 
     @Override
-    Drawable getAppSourceIcon() {
-        return mMediaOutputController.getAppSourceIcon();
+    IconCompat getAppSourceIcon() {
+        return mMediaOutputController.getNotificationSmallIcon();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
index 0fa3265..2b5d6fd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.media.dialog
 
+import android.app.KeyguardManager
 import android.content.Context
 import android.media.AudioManager
 import android.media.session.MediaSessionManager
@@ -45,7 +46,8 @@
     private val dialogLaunchAnimator: DialogLaunchAnimator,
     private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
     private val audioManager: AudioManager,
-    private val powerExemptionManager: PowerExemptionManager
+    private val powerExemptionManager: PowerExemptionManager,
+    private val keyGuardManager: KeyguardManager
 ) {
     var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
 
@@ -57,7 +59,7 @@
         val controller = MediaOutputController(context, packageName,
                 mediaSessionManager, lbm, starter, notifCollection,
                 dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
-                powerExemptionManager)
+                powerExemptionManager, keyGuardManager)
         val dialog =
                 MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
         mediaOutputBroadcastDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 27095b3..96817c9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -20,6 +20,7 @@
 
 import android.annotation.CallbackExecutor;
 import android.app.AlertDialog;
+import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.WallpaperColors;
 import android.bluetooth.BluetoothLeBroadcast;
@@ -120,6 +121,7 @@
     final List<MediaDevice> mCachedMediaDevices = new CopyOnWriteArrayList<>();
     private final AudioManager mAudioManager;
     private final PowerExemptionManager mPowerExemptionManager;
+    private final KeyguardManager mKeyGuardManager;
     private final NearbyMediaDevicesManager mNearbyMediaDevicesManager;
     private final Map<String, Integer> mNearbyDeviceInfoMap = new ConcurrentHashMap<>();
 
@@ -154,7 +156,8 @@
             DialogLaunchAnimator dialogLaunchAnimator,
             Optional<NearbyMediaDevicesManager> nearbyMediaDevicesManagerOptional,
             AudioManager audioManager,
-            PowerExemptionManager powerExemptionManager) {
+            PowerExemptionManager powerExemptionManager,
+            KeyguardManager keyGuardManager) {
         mContext = context;
         mPackageName = packageName;
         mMediaSessionManager = mediaSessionManager;
@@ -163,6 +166,7 @@
         mNotifCollection = notifCollection;
         mAudioManager = audioManager;
         mPowerExemptionManager = powerExemptionManager;
+        mKeyGuardManager = keyGuardManager;
         InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm);
         mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
         mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
@@ -300,7 +304,7 @@
         }
     }
 
-    Drawable getAppSourceIcon() {
+    Drawable getAppSourceIconFromPackage() {
         if (mPackageName.isEmpty()) {
             return null;
         }
@@ -407,10 +411,28 @@
                 device.getId());
         boolean isSelectedDeviceInGroup = getSelectedMediaDevice().size() > 1
                 && getSelectedMediaDevice().contains(device);
-        return (!hasAdjustVolumeUserRestriction() && isConnected && !isTransferring())
+        return (!hasAdjustVolumeUserRestriction() && isConnected && !isAnyDeviceTransferring())
                 || isSelectedDeviceInGroup;
     }
 
+    IconCompat getNotificationSmallIcon() {
+        if (TextUtils.isEmpty(mPackageName)) {
+            return null;
+        }
+        for (NotificationEntry entry : mNotifCollection.getAllNotifs()) {
+            final Notification notification = entry.getSbn().getNotification();
+            if (notification.isMediaNotification()
+                    && TextUtils.equals(entry.getSbn().getPackageName(), mPackageName)) {
+                final Icon icon = notification.getSmallIcon();
+                if (icon == null) {
+                    break;
+                }
+                return IconCompat.createFromIcon(icon);
+            }
+        }
+        return null;
+    }
+
     IconCompat getNotificationIcon() {
         if (TextUtils.isEmpty(mPackageName)) {
             return null;
@@ -686,7 +708,7 @@
                 UserHandle.of(UserHandle.myUserId()));
     }
 
-    boolean isTransferring() {
+    boolean isAnyDeviceTransferring() {
         synchronized (mMediaDevicesLock) {
             for (MediaDevice device : mMediaDevices) {
                 if (device.getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) {
@@ -701,7 +723,8 @@
         ActivityLaunchAnimator.Controller controller =
                 mDialogLaunchAnimator.createActivityLaunchController(view);
 
-        if (controller == null) {
+        if (controller == null || (mKeyGuardManager != null
+                && mKeyGuardManager.isKeyguardLocked())) {
             mCallback.dismissDialog();
         }
 
@@ -753,7 +776,7 @@
         MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
                 mMediaSessionManager, mLocalBluetoothManager, mActivityStarter,
                 mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager),
-                mAudioManager, mPowerExemptionManager);
+                mAudioManager, mPowerExemptionManager, mKeyGuardManager);
         MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true,
                 broadcastSender, controller);
         mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index 9fb96b5..cb6f5a7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -17,7 +17,6 @@
 package com.android.systemui.media.dialog;
 
 import android.content.Context;
-import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.view.View;
 import android.view.WindowManager;
@@ -81,8 +80,8 @@
     }
 
     @Override
-    Drawable getAppSourceIcon() {
-        return mMediaOutputController.getAppSourceIcon();
+    IconCompat getAppSourceIcon() {
+        return mMediaOutputController.getNotificationSmallIcon();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index 8249a7c..543efed 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.media.dialog
 
+import android.app.KeyguardManager
 import android.content.Context
 import android.media.AudioManager
 import android.media.session.MediaSessionManager
@@ -47,7 +48,8 @@
     private val dialogLaunchAnimator: DialogLaunchAnimator,
     private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
     private val audioManager: AudioManager,
-    private val powerExemptionManager: PowerExemptionManager
+    private val powerExemptionManager: PowerExemptionManager,
+    private val keyGuardManager: KeyguardManager
 ) {
     companion object {
         private const val INTERACTION_JANK_TAG = "media_output"
@@ -63,7 +65,7 @@
             context, packageName,
             mediaSessionManager, lbm, starter, notifCollection,
             dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
-            powerExemptionManager)
+            powerExemptionManager, keyGuardManager)
         val dialog =
             MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller, uiEventLogger)
         mediaOutputDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
index e077fed..c544871 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.media.dream;
 
+import static com.android.systemui.flags.Flags.MEDIA_DREAM_COMPLICATION;
+
 import android.content.Context;
 
 import androidx.annotation.NonNull;
@@ -23,6 +25,7 @@
 
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.MediaData;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.media.SmartspaceMediaData;
@@ -34,7 +37,7 @@
  * the media complication as appropriate
  */
 public class MediaDreamSentinel extends CoreStartable {
-    private MediaDataManager.Listener mListener = new MediaDataManager.Listener() {
+    private final MediaDataManager.Listener mListener = new MediaDataManager.Listener() {
         private boolean mAdded;
         @Override
         public void onSmartspaceMediaDataRemoved(@NonNull String key, boolean immediately) {
@@ -63,6 +66,10 @@
         public void onMediaDataLoaded(@NonNull String key, @Nullable String oldKey,
                 @NonNull MediaData data, boolean immediately, int receivedSmartspaceCardLatency,
                 boolean isSsReactivated) {
+            if (!mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)) {
+                return;
+            }
+
             if (mAdded) {
                 return;
             }
@@ -79,15 +86,18 @@
     private final MediaDataManager mMediaDataManager;
     private final DreamOverlayStateController mDreamOverlayStateController;
     private final MediaDreamComplication mComplication;
+    private final FeatureFlags mFeatureFlags;
 
     @Inject
     public MediaDreamSentinel(Context context, MediaDataManager mediaDataManager,
             DreamOverlayStateController dreamOverlayStateController,
-            MediaDreamComplication complication) {
+            MediaDreamComplication complication,
+            FeatureFlags featureFlags) {
         super(context);
         mMediaDataManager = mediaDataManager;
         mDreamOverlayStateController = dreamOverlayStateController;
         mComplication = complication;
+        mFeatureFlags = featureFlags;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index 8757904..00b0ff9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -68,7 +68,7 @@
                     .addFeature("feature")
             val useAppIcon = !(args.size >= 3 && args[2] == "useAppIcon=false")
             if (useAppIcon) {
-                routeInfo.setPackageName(TEST_PACKAGE_NAME)
+                routeInfo.setClientPackageName(TEST_PACKAGE_NAME)
             }
 
             statusBarManager.updateMediaTapToTransferSenderDisplay(
@@ -134,7 +134,7 @@
                 .addFeature("feature")
             val useAppIcon = !(args.size >= 2 && args[1] == "useAppIcon=false")
             if (useAppIcon) {
-                routeInfo.setPackageName(TEST_PACKAGE_NAME)
+                routeInfo.setClientPackageName(TEST_PACKAGE_NAME)
             }
 
             statusBarManager.updateMediaTapToTransferReceiverDisplay(
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 9ab83b8..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
@@ -56,7 +56,7 @@
         internal val logger: MediaTttLogger,
         internal val windowManager: WindowManager,
         private val viewUtil: ViewUtil,
-        @Main internal val mainExecutor: DelayableExecutor,
+        @Main private val mainExecutor: DelayableExecutor,
         private val accessibilityManager: AccessibilityManager,
         private val configurationController: ConfigurationController,
         private val powerManager: PowerManager,
@@ -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/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
index d3b5bc6..aa10f7e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -18,7 +18,6 @@
 
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogLevel
-import com.android.systemui.log.dagger.MediaTttSenderLogBuffer
 
 /**
  * A logger for media tap-to-transfer events.
@@ -27,7 +26,7 @@
  */
 class MediaTttLogger(
     private val deviceTypeTag: String,
-    @MediaTttSenderLogBuffer private val buffer: LogBuffer
+    private val buffer: LogBuffer
 ){
     /** Logs a change in the chip state for the given [mediaRouteId]. */
     fun logStateChange(stateName: String, mediaRouteId: String) {
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 0f1ae00..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.packageName,
+                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/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index b94b8bf..9335489 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -122,7 +122,7 @@
         val chipState = newChipInfo.state
 
         // App icon
-        val iconName = setIcon(currentChipView, newChipInfo.routeInfo.packageName)
+        val iconName = setIcon(currentChipView, newChipInfo.routeInfo.clientPackageName)
 
         // Text
         val otherDeviceName = newChipInfo.routeInfo.name.toString()
@@ -160,12 +160,8 @@
             duration = ANIMATION_DURATION,
             includeMargins = true,
             includeFadeIn = true,
-        )
-
-        // We can only request focus once the animation finishes.
-        mainExecutor.executeDelayed(
-                { chipInnerView.requestAccessibilityFocus() },
-                ANIMATION_DURATION
+            // We can only request focus once the animation finishes.
+            onAnimationEnd = { chipInnerView.requestAccessibilityFocus() },
         )
     }
 
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/people/ui/viewmodel/PeopleViewModel.kt b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
index 0834a5a..e27bfb3 100644
--- a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
@@ -31,7 +31,6 @@
 import com.android.systemui.people.data.repository.PeopleTileRepository
 import com.android.systemui.people.data.repository.PeopleWidgetRepository
 import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -52,7 +51,7 @@
      * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
      */
     private val _priorityTiles = MutableStateFlow(priorityTiles())
-    val priorityTiles: Flow<List<PeopleTileViewModel>> = _priorityTiles.asStateFlow()
+    val priorityTiles: StateFlow<List<PeopleTileViewModel>> = _priorityTiles.asStateFlow()
 
     /**
      * The list of the priority tiles/conversations.
@@ -61,7 +60,7 @@
      * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
      */
     private val _recentTiles = MutableStateFlow(recentTiles())
-    val recentTiles: Flow<List<PeopleTileViewModel>> = _recentTiles.asStateFlow()
+    val recentTiles: StateFlow<List<PeopleTileViewModel>> = _recentTiles.asStateFlow()
 
     /** The ID of the widget currently being edited/added. */
     private val _appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 833573d..be44202 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -39,6 +39,7 @@
 
 import javax.inject.Inject;
 import javax.inject.Named;
+import javax.inject.Provider;
 
 /** Controller for {@link QuickQSPanel}. */
 @QSScope
@@ -52,20 +53,21 @@
                 }
             };
 
-    private final boolean mUsingCollapsedLandscapeMedia;
+    private final Provider<Boolean> mUsingCollapsedLandscapeMediaProvider;
 
     @Inject
     QuickQSPanelController(QuickQSPanel view, QSTileHost qsTileHost,
             QSCustomizerController qsCustomizerController,
             @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
             @Named(QUICK_QS_PANEL) MediaHost mediaHost,
-            @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA) boolean usingCollapsedLandscapeMedia,
+            @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA)
+                    Provider<Boolean> usingCollapsedLandscapeMediaProvider,
             MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
             DumpManager dumpManager
     ) {
         super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
                 uiEventLogger, qsLogger, dumpManager);
-        mUsingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia;
+        mUsingCollapsedLandscapeMediaProvider = usingCollapsedLandscapeMediaProvider;
     }
 
     @Override
@@ -80,7 +82,8 @@
         int rotation = getRotation();
         boolean isLandscape = rotation == RotationUtils.ROTATION_LANDSCAPE
                 || rotation == RotationUtils.ROTATION_SEASCAPE;
-        if (!mUsingCollapsedLandscapeMedia || !isLandscape) {
+        boolean usingCollapsedLandscapeMedia = mUsingCollapsedLandscapeMediaProvider.get();
+        if (!usingCollapsedLandscapeMedia || !isLandscape) {
             mMediaHost.setExpansion(MediaHost.EXPANDED);
         } else {
             mMediaHost.setExpansion(MediaHost.COLLAPSED);
@@ -126,7 +129,6 @@
         super.setTiles(tiles, /* collapsedView */ true);
     }
 
-    /** */
     public void setContentMargins(int marginStart, int marginEnd) {
         mView.setContentMargins(marginStart, marginEnd, mMediaHost.getHostView());
     }
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/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
index 56a1874..db7c1fd 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
@@ -67,7 +67,7 @@
 
                 float rippleInsideAlpha = (1.-inside) * in_fadeFill;
                 float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
-                float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+                float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
                 vec4 ripple = in_color * rippleAlpha;
                 return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
             }
@@ -83,7 +83,7 @@
 
                 float rippleInsideAlpha = (1.-inside) * in_fadeFill;
                 float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
-                float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+                float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
                 vec4 ripple = in_color * rippleAlpha;
                 return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
             }
@@ -99,7 +99,7 @@
 
                 float rippleInsideAlpha = (1.-inside) * in_fadeFill;
                 float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
-                float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+                float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
                 vec4 ripple = in_color * rippleAlpha;
                 return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
index 8b01201..60c8f37 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
@@ -81,6 +81,7 @@
         rippleShader.color = RIPPLE_DEFAULT_COLOR
         rippleShader.progress = 0f
         rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
+        rippleShader.pixelDensity = resources.displayMetrics.density
 
         ripplePaint.shader = rippleShader
     }
@@ -124,6 +125,13 @@
         rippleShader.rippleFill = rippleFill
     }
 
+    /**
+     * Set the intensity of the sparkles.
+     */
+    fun setSparkleStrength(strength: Float) {
+        rippleShader.sparkleStrength = strength
+    }
+
     override fun onDraw(canvas: Canvas?) {
         if (canvas == null || !canvas.isHardwareAccelerated) {
             // Drawing with the ripple shader requires hardware acceleration, so skip
diff --git a/lowpan/java/android/net/lowpan/LowpanIdentity.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
similarity index 66%
rename from lowpan/java/android/net/lowpan/LowpanIdentity.aidl
rename to packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
index fcef98f..f7c4dad 100644
--- a/lowpan/java/android/net/lowpan/LowpanIdentity.aidl
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
@@ -1,5 +1,5 @@
 /**
- * Copyright (C) 2017 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.
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
-package android.net.lowpan;
+package com.android.systemui.screenshot;
 
-parcelable LowpanIdentity cpp_header "android/net/lowpan/LowpanIdentity.h";
+/** Interface implemented by ScreenshotProxyService */
+interface IScreenshotProxy {
+
+    /** 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 beb54c8..a918e5d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -16,53 +16,84 @@
 
 package com.android.systemui.screenshot
 
-import android.net.Uri
-import android.util.Log
-import android.view.WindowManager.ScreenshotType
-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(
-        @ScreenshotType type: Int,
-        onSavedListener: Consumer<Uri>,
-        request: ScreenshotRequest,
-        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 (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 (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: $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 f1f0223..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(msg.what, uriConsumer, screenshotRequest, requestCallback);
-            return true;
+            mProcessor.processAsync(request,
+                    (r) -> dispatchToController(r, onSaved, callback));
         }
 
-        switch (msg.what) {
+        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/NotifPanelEvents.kt b/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
index ce9d89f..4558061 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
@@ -29,11 +29,25 @@
     interface Listener {
 
         /** Invoked when the notification panel starts or stops collapsing. */
-        fun onPanelCollapsingChanged(isCollapsing: Boolean)
+        @JvmDefault
+        fun onPanelCollapsingChanged(isCollapsing: Boolean) {}
 
         /**
          * Invoked when the notification panel starts or stops launching an [android.app.Activity].
          */
-        fun onLaunchingActivityChanged(isLaunchingActivity: Boolean)
+        @JvmDefault
+        fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {}
+
+        /**
+         * Invoked when the "expand immediate" attribute changes.
+         *
+         * An example of expanding immediately is when swiping down from the top with two fingers.
+         * Instead of going to QQS, we immediately expand to full QS.
+         *
+         * Another example is when full QS is showing, and we swipe up from the bottom. Instead of
+         * going to QQS, the panel fully collapses.
+         */
+        @JvmDefault
+        fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 7f0e76b..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;
@@ -127,9 +127,7 @@
 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase;
-import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
-import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
 import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.media.MediaDataManager;
@@ -444,7 +442,6 @@
      */
     private boolean mQsAnimatorExpand;
     private boolean mIsLaunchTransitionFinished;
-    private boolean mOnlyAffordanceInThisMotion;
     private ValueAnimator mQsSizeChangeAnimator;
 
     private boolean mQsScrimEnabled = true;
@@ -700,11 +697,7 @@
 
     private final CameraGestureHelper mCameraGestureHelper;
     private final Provider<KeyguardBottomAreaViewModel> mKeyguardBottomAreaViewModelProvider;
-    private final Provider<SetClockPositionUseCase> mSetClockPositionUseCaseProvider;
-    private final Provider<SetKeyguardBottomAreaAlphaUseCase>
-            mSetKeyguardBottomAreaAlphaUseCaseProvider;
-    private final Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
-            mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
+    private final Provider<KeyguardBottomAreaInteractor> mKeyguardBottomAreaInteractorProvider;
 
     @Inject
     public NotificationPanelViewController(NotificationPanelView view,
@@ -726,6 +719,7 @@
             AccessibilityManager accessibilityManager, @DisplayId int displayId,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             MetricsLogger metricsLogger,
+            ShadeLogger shadeLogger,
             ConfigurationController configurationController,
             Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
@@ -776,10 +770,7 @@
             SystemClock systemClock,
             CameraGestureHelper cameraGestureHelper,
             Provider<KeyguardBottomAreaViewModel> keyguardBottomAreaViewModelProvider,
-            Provider<SetClockPositionUseCase> setClockPositionUseCaseProvider,
-            Provider<SetKeyguardBottomAreaAlphaUseCase> setKeyguardBottomAreaAlphaUseCaseProvider,
-            Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
-                    setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider) {
+            Provider<KeyguardBottomAreaInteractor> keyguardBottomAreaInteractorProvider) {
         super(view,
                 falsingManager,
                 dozeLog,
@@ -795,6 +786,7 @@
                 panelExpansionStateManager,
                 ambientState,
                 interactionJankMonitor,
+                shadeLogger,
                 systemClock);
         mView = view;
         mVibratorHelper = vibratorHelper;
@@ -961,10 +953,7 @@
                     }
                 });
         mCameraGestureHelper = cameraGestureHelper;
-        mSetClockPositionUseCaseProvider = setClockPositionUseCaseProvider;
-        mSetKeyguardBottomAreaAlphaUseCaseProvider = setKeyguardBottomAreaAlphaUseCaseProvider;
-        mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider =
-                setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
+        mKeyguardBottomAreaInteractorProvider = keyguardBottomAreaInteractorProvider;
     }
 
     @VisibleForTesting
@@ -1427,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) {
@@ -1448,7 +1428,7 @@
         float expandedFraction =
                 mScreenOffAnimationController.shouldExpandNotifications()
                         ? 1.0f : getExpandedFraction();
-        float darkamount =
+        float darkAmount =
                 mScreenOffAnimationController.shouldExpandNotifications()
                         ? 1.0f : mInterpolatedDarkAmount;
 
@@ -1466,7 +1446,7 @@
                 mKeyguardStatusViewController.getLockscreenHeight(),
                 userSwitcherHeight,
                 userSwitcherPreferredY,
-                darkamount, mOverStretchAmount,
+                darkAmount, mOverStretchAmount,
                 bypassEnabled, getUnlockedStackScrollerPadding(),
                 computeQsExpansionFraction(),
                 mDisplayTopInset,
@@ -1475,7 +1455,7 @@
                 mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
                 mKeyguardStatusViewController.isClockTopAligned());
         mClockPositionAlgorithm.run(mClockPositionResult);
-        mSetClockPositionUseCaseProvider.get().invoke(
+        mKeyguardBottomAreaInteractorProvider.get().setClockPosition(
                 mClockPositionResult.clockX, mClockPositionResult.clockY);
         boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
         boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
@@ -1498,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) {
@@ -1524,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();
     }
 
     /**
@@ -1702,12 +1733,17 @@
         }
 
         if (mQsExpanded) {
-            mQsExpandImmediate = true;
+            setQsExpandImmediate(true);
             setShowShelfOnly(true);
         }
         super.collapse(delayed, speedUpFactor);
     }
 
+    private void setQsExpandImmediate(boolean expandImmediate) {
+        mQsExpandImmediate = expandImmediate;
+        mPanelEventsEmitter.notifyExpandImmediateChange(expandImmediate);
+    }
+
     private void setShowShelfOnly(boolean shelfOnly) {
         mNotificationStackScrollLayoutController.setShouldShowShelfOnly(
                 shelfOnly && !mSplitShadeEnabled);
@@ -1760,7 +1796,7 @@
 
     public void expandWithQs() {
         if (isQsExpansionEnabled()) {
-            mQsExpandImmediate = true;
+            setQsExpandImmediate(true);
             setShowShelfOnly(true);
         }
         if (mSplitShadeEnabled && isOnKeyguard()) {
@@ -1839,6 +1875,8 @@
                 }
                 if (mQsExpansionAnimator != null) {
                     mInitialHeightOnTouch = mQsExpansionHeight;
+                    mShadeLog.logMotionEvent(event,
+                            "onQsIntercept: down action, QS tracking enabled");
                     mQsTracking = true;
                     traceQsJank(true /* startTracing */, false /* wasCancelled */);
                     mNotificationStackScrollLayoutController.cancelLongPress();
@@ -1866,12 +1904,16 @@
                     setQsExpansion(h + mInitialHeightOnTouch);
                     trackMovement(event);
                     return true;
+                } else {
+                    mShadeLog.logMotionEvent(event,
+                            "onQsIntercept: move ignored because qs tracking disabled");
                 }
                 if ((h > getTouchSlop(event) || (h < -getTouchSlop(event) && mQsExpanded))
                         && Math.abs(h) > Math.abs(x - mInitialTouchX)
                         && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
                     if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept - start tracking expansion");
                     mView.getParent().requestDisallowInterceptTouchEvent(true);
+                    mShadeLog.onQsInterceptMoveQsTrackingEnabled(h);
                     mQsTracking = true;
                     traceQsJank(true /* startTracing */, false /* wasCancelled */);
                     onQsExpansionStarted();
@@ -1887,6 +1929,7 @@
             case MotionEvent.ACTION_CANCEL:
             case MotionEvent.ACTION_UP:
                 trackMovement(event);
+                mShadeLog.logMotionEvent(event, "onQsIntercept: up action, QS tracking disabled");
                 mQsTracking = false;
                 break;
         }
@@ -1924,7 +1967,6 @@
 
     private void initDownStates(MotionEvent event) {
         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-            mOnlyAffordanceInThisMotion = false;
             mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
             mDozingOnDown = isDozing();
             mDownX = event.getX();
@@ -2063,6 +2105,7 @@
                 && collapsedQs && isQsExpansionEnabled();
         if (action == MotionEvent.ACTION_DOWN && expandedShadeCollapsedQs) {
             // Down in the empty area while fully expanded - go to QS.
+            mShadeLog.logMotionEvent(event, "handleQsTouch: down action, QS tracking enabled");
             mQsTracking = true;
             traceQsJank(true /* startTracing */, false /* wasCancelled */);
             mConflictingQsExpansionGesture = true;
@@ -2077,6 +2120,8 @@
         if (!mQsExpandImmediate && mQsTracking) {
             onQsTouch(event);
             if (!mConflictingQsExpansionGesture && !mSplitShadeEnabled) {
+                mShadeLog.logMotionEvent(event,
+                        "handleQsTouch: not immediate expand or conflicting gesture");
                 return true;
             }
         }
@@ -2089,7 +2134,7 @@
         if (mTwoFingerQsExpandPossible && isOpenQsEvent(event) && event.getY(event.getActionIndex())
                 < mStatusBarMinHeight) {
             mMetricsLogger.count(COUNTER_PANEL_OPEN_QS, 1);
-            mQsExpandImmediate = true;
+            setQsExpandImmediate(true);
             setShowShelfOnly(true);
             requestPanelHeightUpdate();
 
@@ -2144,6 +2189,7 @@
                 event.getX(), event.getY(), -1)) {
             if (DEBUG_LOGCAT) Log.d(TAG, "handleQsDown");
             mFalsingCollector.onQsDown();
+            mShadeLog.logMotionEvent(event, "handleQsDown: down action, QS tracking enabled");
             mQsTracking = true;
             onQsExpansionStarted();
             mInitialHeightOnTouch = mQsExpansionHeight;
@@ -2226,6 +2272,7 @@
 
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_DOWN:
+                mShadeLog.logMotionEvent(event, "onQsTouch: down action, QS tracking enabled");
                 mQsTracking = true;
                 traceQsJank(true /* startTracing */, false /* wasCancelled */);
                 mInitialTouchY = y;
@@ -2252,6 +2299,7 @@
 
             case MotionEvent.ACTION_MOVE:
                 if (DEBUG_LOGCAT) Log.d(TAG, "onQSTouch move");
+                mShadeLog.logMotionEvent(event, "onQsTouch: move action, setting QS expansion");
                 setQsExpansion(h + mInitialHeightOnTouch);
                 if (h >= getFalsingThreshold()) {
                     mQsTouchAboveFalsingThreshold = true;
@@ -2261,6 +2309,8 @@
 
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
+                mShadeLog.logMotionEvent(event,
+                        "onQsTouch: up/cancel action, QS tracking disabled");
                 mQsTracking = false;
                 mTrackingPointer = -1;
                 trackMovement(event);
@@ -3083,8 +3133,8 @@
                 positionClockAndNotifications();
             }
         }
-        if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
-                && !mQsExpansionFromOverscroll) {
+        if (mQsExpandImmediate || (mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
+                && !mQsExpansionFromOverscroll)) {
             float t;
             if (mKeyguardShowing) {
 
@@ -3249,7 +3299,7 @@
         float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
         alpha *= mBottomAreaShadeAlpha;
         mKeyguardBottomArea.setComponentAlphas(alpha);
-        mSetKeyguardBottomAreaAlphaUseCaseProvider.get().invoke(alpha);
+        mKeyguardBottomAreaInteractorProvider.get().setAlpha(alpha);
         mLockIconViewController.setAlpha(alpha);
     }
 
@@ -3291,7 +3341,7 @@
         } else {
             setListening(true);
         }
-        mQsExpandImmediate = false;
+        setQsExpandImmediate(false);
         setShowShelfOnly(false);
         mTwoFingerQsExpandPossible = false;
         updateTrackingHeadsUp(null);
@@ -3349,7 +3399,7 @@
         super.onTrackingStarted();
         mScrimController.onTrackingStarted();
         if (mQsFullyExpanded) {
-            mQsExpandImmediate = true;
+            setQsExpandImmediate(true);
             setShowShelfOnly(true);
         }
         mNotificationStackScrollLayoutController.onPanelTrackingStarted();
@@ -3449,7 +3499,7 @@
 
     private void updateDozingVisibilities(boolean animate) {
         mKeyguardBottomArea.setDozing(mDozing, animate);
-        mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
+        mKeyguardBottomAreaInteractorProvider.get().setAnimateDozingTransitions(animate);
         if (!mDozing && animate) {
             mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
         }
@@ -3744,15 +3794,14 @@
      *
      * @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);
-        mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
+        mKeyguardBottomAreaInteractorProvider.get().setAnimateDozingTransitions(animate);
         mKeyguardStatusBarViewController.setDozing(mDozing);
 
         if (dozing) {
@@ -3783,6 +3832,8 @@
             mAnimateNextPositionUpdate = false;
         }
         mNotificationStackScrollLayoutController.setPulsing(pulsing, animatePulse);
+
+        updateKeyguardStatusViewAlignment(/* animate= */ true);
     }
 
     public void setAmbientIndicationTop(int ambientIndicationTop, boolean ambientTextVisible) {
@@ -4174,6 +4225,7 @@
                         || mPulseExpansionHandler.isExpanding();
                 if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) {
                     // We're expanding all the other ones shouldn't get this anymore
+                    mShadeLog.logMotionEvent(event, "onTouch: PulseExpansionHandler handled event");
                     return true;
                 }
                 if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
@@ -4181,14 +4233,10 @@
                         && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
                     mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
                 }
-                boolean handled = false;
-                if (mOnlyAffordanceInThisMotion) {
-                    return true;
-                }
-                handled |= mHeadsUpTouchHelper.onTouchEvent(event);
+                boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
 
                 if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
-                    if (DEBUG_LOGCAT) Log.d(TAG, "handleQsTouch true");
+                    mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
                     return true;
                 }
                 if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
@@ -4678,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);
@@ -4756,6 +4804,8 @@
                 }
             } else if (!mQsExpanded && mQsExpansionAnimator == null) {
                 setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
+            } else {
+                mShadeLog.v("onLayoutChange: qs expansion not set");
             }
             updateExpandedHeight(getExpandedHeight());
             updateHeader();
@@ -4910,7 +4960,7 @@
             // to locked will trigger this event and we're not actually in the process of opening
             // the shade, lockscreen is just always expanded
             if (mSplitShadeEnabled && !isOnKeyguard()) {
-                mQsExpandImmediate = true;
+                setQsExpandImmediate(true);
             }
             mCentralSurfaces.makeExpandedVisible(false);
         }
@@ -4977,5 +5027,11 @@
                 cb.onPanelCollapsingChanged(isCollapsing);
             }
         }
+
+        private void notifyExpandImmediateChange(boolean expandImmediateEnabled) {
+            for (NotifPanelEvents.Listener cb : mListeners) {
+                cb.onExpandImmediateChanged(expandImmediateEnabled);
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
index 4aad245..73eaa85 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
@@ -202,6 +202,8 @@
     private final InteractionJankMonitor mInteractionJankMonitor;
     protected final SystemClock mSystemClock;
 
+    protected final ShadeLogger mShadeLog;
+
     protected abstract void onExpandingFinished();
 
     protected void onExpandingStarted() {
@@ -242,6 +244,7 @@
             PanelExpansionStateManager panelExpansionStateManager,
             AmbientState ambientState,
             InteractionJankMonitor interactionJankMonitor,
+            ShadeLogger shadeLogger,
             SystemClock systemClock) {
         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
@@ -254,6 +257,7 @@
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mLockscreenGestureLogger = lockscreenGestureLogger;
         mPanelExpansionStateManager = panelExpansionStateManager;
+        mShadeLog = shadeLogger;
         TouchHandler touchHandler = createTouchHandler();
         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
             @Override
@@ -1275,9 +1279,16 @@
 
         @Override
         public boolean onTouch(View v, MotionEvent event) {
-            if (mInstantExpanding || (mTouchDisabled
-                    && event.getActionMasked() != MotionEvent.ACTION_CANCEL) || (mMotionAborted
-                    && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
+            if (mInstantExpanding) {
+                mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding");
+                return false;
+            }
+            if (mTouchDisabled  && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
+                mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled");
+                return false;
+            }
+            if (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
+                mShadeLog.logMotionEvent(event, "onTouch: non-down action, motion was aborted");
                 return false;
             }
 
@@ -1287,6 +1298,7 @@
                     // Turn off tracking if it's on or the shade can get stuck in the down position.
                     onTrackingStopped(true /* expand */);
                 }
+                mShadeLog.logMotionEvent(event, "onTouch: drag not enabled");
                 return false;
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
new file mode 100644
index 0000000..f1e44ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -0,0 +1,48 @@
+package com.android.systemui.shade
+
+import android.view.MotionEvent
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogMessage
+import com.android.systemui.log.dagger.ShadeLog
+import com.google.errorprone.annotations.CompileTimeConstant
+import javax.inject.Inject
+
+private const val TAG = "systemui.shade"
+
+/** Lightweight logging utility for the Shade. */
+class ShadeLogger @Inject constructor(
+    @ShadeLog
+    private val buffer: LogBuffer
+) {
+    fun v(@CompileTimeConstant msg: String) {
+        buffer.log(TAG, LogLevel.VERBOSE, msg)
+    }
+
+    private inline fun log(
+        logLevel: LogLevel,
+        initializer: LogMessage.() -> Unit,
+        noinline printer: LogMessage.() -> String
+    ) {
+        buffer.log(TAG, logLevel, initializer, printer)
+    }
+
+    fun onQsInterceptMoveQsTrackingEnabled(h: Float) {
+        log(LogLevel.VERBOSE,
+            { double1 = h.toDouble() },
+            { "onQsIn[tercept: move action, QS tracking enabled. h = $double1" })
+    }
+
+    fun logMotionEvent(event: MotionEvent, message: String) {
+        log(LogLevel.VERBOSE, {
+            str1 = message
+            long1 = event.eventTime
+            long2 = event.downTime
+            int1 = event.action
+            int2 = event.classification
+            double1 = event.y.toDouble()
+        }, {
+            "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,classification=$int2"
+        })
+    }
+}
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/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index ca14728..c983644 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -27,6 +27,7 @@
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
@@ -36,7 +37,6 @@
 import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
 import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
 
-import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -123,6 +123,8 @@
     private static final int MSG_SHOW_ACTION_TO_UNLOCK = 2;
     private static final int MSG_HIDE_BIOMETRIC_MESSAGE = 3;
     private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300;
+    public static final long DEFAULT_HIDE_DELAY_MS =
+            3500 + KeyguardIndicationTextView.Y_IN_DURATION;
 
     private final Context mContext;
     private final BroadcastDispatcher mBroadcastDispatcher;
@@ -140,7 +142,6 @@
     protected final @Main DelayableExecutor mExecutor;
     protected final @Background DelayableExecutor mBackgroundExecutor;
     private final LockPatternUtils mLockPatternUtils;
-    private final IActivityManager mIActivityManager;
     private final FalsingManager mFalsingManager;
     private final KeyguardBypassController mKeyguardBypassController;
     private final AccessibilityManager mAccessibilityManager;
@@ -155,6 +156,7 @@
     private CharSequence mTrustGrantedIndication;
     private CharSequence mTransientIndication;
     private CharSequence mBiometricMessage;
+    private CharSequence mBiometricMessageFollowUp;
     protected ColorStateList mInitialTextColorState;
     private boolean mVisible;
     private boolean mOrganizationOwnedDevice;
@@ -171,7 +173,7 @@
     private int mBatteryLevel;
     private boolean mBatteryPresent = true;
     private long mChargingTimeRemaining;
-    private String mMessageToShowOnScreenOn;
+    private String mBiometricErrorMessageToShowOnScreenOn;
     private final Set<Integer> mCoExFaceHelpMsgIdsToShow;
     private boolean mInited;
 
@@ -189,11 +191,11 @@
     private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
         @Override
         public void onScreenTurnedOn() {
-            if (mMessageToShowOnScreenOn != null) {
-                showBiometricMessage(mMessageToShowOnScreenOn);
+            if (mBiometricErrorMessageToShowOnScreenOn != null) {
+                showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn);
                 // We want to keep this message around in case the screen was off
-                hideBiometricMessageDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
-                mMessageToShowOnScreenOn = null;
+                hideBiometricMessageDelayed(DEFAULT_HIDE_DELAY_MS);
+                mBiometricErrorMessageToShowOnScreenOn = null;
             }
         }
     };
@@ -219,7 +221,6 @@
             FalsingManager falsingManager,
             LockPatternUtils lockPatternUtils,
             ScreenLifecycle screenLifecycle,
-            IActivityManager iActivityManager,
             KeyguardBypassController keyguardBypassController,
             AccessibilityManager accessibilityManager) {
         mContext = context;
@@ -236,7 +237,6 @@
         mExecutor = executor;
         mBackgroundExecutor = bgExecutor;
         mLockPatternUtils = lockPatternUtils;
-        mIActivityManager = iActivityManager;
         mFalsingManager = falsingManager;
         mKeyguardBypassController = keyguardBypassController;
         mAccessibilityManager = accessibilityManager;
@@ -498,8 +498,23 @@
                             .build(),
                     true
             );
+            if (!TextUtils.isEmpty(mBiometricMessageFollowUp)) {
+                mRotateTextViewController.updateIndication(
+                        INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                        new KeyguardIndication.Builder()
+                                .setMessage(mBiometricMessageFollowUp)
+                                .setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION)
+                                .setTextColor(mInitialTextColorState)
+                                .build(),
+                        true
+                );
+            } else {
+                mRotateTextViewController.hideIndication(
+                        INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP);
+            }
         } else {
             mRotateTextViewController.hideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+            mRotateTextViewController.hideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP);
         }
     }
 
@@ -719,38 +734,45 @@
     private void showTransientIndication(CharSequence transientIndication) {
         mTransientIndication = transientIndication;
         mHandler.removeMessages(MSG_HIDE_TRANSIENT);
-        hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
+        hideTransientIndicationDelayed(DEFAULT_HIDE_DELAY_MS);
 
         updateTransient();
     }
 
-    /**
-     * Shows {@param biometricMessage} until it is hidden by {@link #hideBiometricMessage}.
-     */
-    public void showBiometricMessage(int biometricMessage) {
-        showBiometricMessage(mContext.getResources().getString(biometricMessage));
+    private void showBiometricMessage(CharSequence biometricMessage) {
+        showBiometricMessage(biometricMessage, null);
     }
 
     /**
-     * Shows {@param biometricMessage} until it is hidden by {@link #hideBiometricMessage}.
+     * Shows {@param biometricMessage} and {@param biometricMessageFollowUp}
+     * until they are hidden by {@link #hideBiometricMessage}. Messages are rotated through
+     * by {@link KeyguardIndicationRotateTextViewController}, see class for rotating message
+     * logic.
      */
-    private void showBiometricMessage(CharSequence biometricMessage) {
+    private void showBiometricMessage(CharSequence biometricMessage,
+            CharSequence biometricMessageFollowUp) {
         if (TextUtils.equals(biometricMessage, mBiometricMessage)) {
             return;
         }
 
         mBiometricMessage = biometricMessage;
+        mBiometricMessageFollowUp = biometricMessageFollowUp;
 
         mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK);
         mHandler.removeMessages(MSG_HIDE_BIOMETRIC_MESSAGE);
-        hideBiometricMessageDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
+        hideBiometricMessageDelayed(
+                mBiometricMessageFollowUp != null
+                        ? DEFAULT_HIDE_DELAY_MS * 2
+                        : DEFAULT_HIDE_DELAY_MS
+        );
 
         updateBiometricMessage();
     }
 
     private void hideBiometricMessage() {
-        if (mBiometricMessage != null) {
+        if (mBiometricMessage != null || mBiometricMessageFollowUp != null) {
             mBiometricMessage = null;
+            mBiometricMessageFollowUp = null;
             mHandler.removeMessages(MSG_HIDE_BIOMETRIC_MESSAGE);
             updateBiometricMessage();
         }
@@ -789,9 +811,9 @@
             // colors can be hard to read in low brightness.
             mTopIndicationView.setTextColor(Color.WHITE);
 
-            CharSequence newIndication = null;
+            CharSequence newIndication;
             if (!TextUtils.isEmpty(mBiometricMessage)) {
-                newIndication = mBiometricMessage;
+                newIndication = mBiometricMessage; // note: doesn't show mBiometricMessageFollowUp
             } else if (!TextUtils.isEmpty(mTransientIndication)) {
                 newIndication = mTransientIndication;
             } else if (!mBatteryPresent) {
@@ -909,15 +931,21 @@
                         || mAccessibilityManager.isTouchExplorationEnabled();
                 if (udfpsSupported && faceAuthenticated) { // co-ex
                     if (a11yEnabled) {
-                        showBiometricMessage(mContext.getString(
-                                R.string.keyguard_face_successful_unlock_swipe));
+                        showBiometricMessage(
+                                mContext.getString(R.string.keyguard_face_successful_unlock),
+                                mContext.getString(R.string.keyguard_unlock)
+                        );
                     } else {
-                        showBiometricMessage(mContext.getString(
-                                R.string.keyguard_face_successful_unlock_press));
+                        showBiometricMessage(
+                                mContext.getString(R.string.keyguard_face_successful_unlock),
+                                mContext.getString(R.string.keyguard_unlock_press)
+                        );
                     }
                 } else if (faceAuthenticated) { // face-only
-                    showBiometricMessage(mContext.getString(
-                            R.string.keyguard_face_successful_unlock_swipe));
+                    showBiometricMessage(
+                            mContext.getString(R.string.keyguard_face_successful_unlock),
+                            mContext.getString(R.string.keyguard_unlock)
+                    );
                 } else if (udfpsSupported) { // udfps-only
                     if (a11yEnabled) {
                         showBiometricMessage(mContext.getString(R.string.keyguard_unlock));
@@ -943,10 +971,11 @@
         pw.println("  mPowerCharged: " + mPowerCharged);
         pw.println("  mChargingSpeed: " + mChargingSpeed);
         pw.println("  mChargingWattage: " + mChargingWattage);
-        pw.println("  mMessageToShowOnScreenOn: " + mMessageToShowOnScreenOn);
+        pw.println("  mMessageToShowOnScreenOn: " + mBiometricErrorMessageToShowOnScreenOn);
         pw.println("  mDozing: " + mDozing);
         pw.println("  mTransientIndication: " + mTransientIndication);
         pw.println("  mBiometricMessage: " + mBiometricMessage);
+        pw.println("  mBiometricMessageFollowUp: " + mBiometricMessageFollowUp);
         pw.println("  mBatteryLevel: " + mBatteryLevel);
         pw.println("  mBatteryPresent: " + mBatteryPresent);
         pw.println("  AOD text: " + (
@@ -958,8 +987,6 @@
     }
 
     protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback {
-        public static final int HIDE_DELAY_MS = 5000;
-
         @Override
         public void onTimeChanged() {
             if (mVisible) {
@@ -1077,7 +1104,7 @@
             } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
                 showBiometricMessage(errString);
             } else {
-                mMessageToShowOnScreenOn = errString;
+                mBiometricErrorMessageToShowOnScreenOn = errString;
             }
         }
 
@@ -1139,7 +1166,7 @@
                 // Let's hide any previous messages when authentication starts, otherwise
                 // multiple auth attempts would overlap.
                 hideBiometricMessage();
-                mMessageToShowOnScreenOn = null;
+                mBiometricErrorMessageToShowOnScreenOn = null;
             }
         }
 
@@ -1179,7 +1206,7 @@
         @Override
         public void onRequireUnlockForNfc() {
             showTransientIndication(mContext.getString(R.string.require_unlock_for_nfc));
-            hideTransientIndicationDelayed(HIDE_DELAY_MS);
+            hideTransientIndicationDelayed(DEFAULT_HIDE_DELAY_MS);
         }
     }
 
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/collection/TargetSdkResolver.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
index c71eade..0c49713 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.statusbar.notification.collection
 
+import android.app.Notification
 import android.content.Context
+import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.service.notification.StatusBarNotification
 import android.util.Log
@@ -39,17 +41,28 @@
     }
 
     private fun resolveNotificationSdk(sbn: StatusBarNotification): Int {
+        val applicationInfo = getApplicationInfoFromExtras(sbn.notification)
+                ?: getApplicationInfoFromPackageManager(sbn)
+
+        return applicationInfo?.targetSdkVersion ?: 0
+    }
+
+    private fun getApplicationInfoFromExtras(notification: Notification): ApplicationInfo? =
+            notification.extras.getParcelable(
+                    Notification.EXTRA_BUILDER_APPLICATION_INFO,
+                    ApplicationInfo::class.java
+            )
+
+    private fun getApplicationInfoFromPackageManager(sbn: StatusBarNotification): ApplicationInfo? {
         val pmUser = CentralSurfaces.getPackageManagerForUser(context, sbn.user.identifier)
-        var targetSdk = 0
-        // Extract target SDK version.
-        try {
-            val info = pmUser.getApplicationInfo(sbn.packageName, 0)
-            targetSdk = info.targetSdkVersion
+
+        return try {
+            pmUser.getApplicationInfo(sbn.packageName, 0)
         } catch (ex: PackageManager.NameNotFoundException) {
             Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.packageName, ex)
+            null
         }
-        return targetSdk
     }
 
     private val TAG = "TargetSdkResolver"
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index 0b6b929..c956a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -1,6 +1,7 @@
 package com.android.systemui.statusbar.notification.interruption
 
 import android.app.Notification
+import android.app.Notification.VISIBILITY_SECRET
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
@@ -172,6 +173,8 @@
         !lockscreenUserManager.shouldShowLockscreenNotifications() -> true
         // User settings do not allow this notification on the lockscreen, so hide it.
         userSettingsDisallowNotification(entry) -> true
+        // Entry is explicitly marked SECRET, so hide it.
+        entry.sbn.notification.visibility == VISIBILITY_SECRET -> true
         // if entry is silent, apply custom logic to see if should hide
         shouldHideIfEntrySilent(entry) -> true
         else -> false
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/row/FeedbackInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
index 2b782b6..3f4fd50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
@@ -165,7 +165,7 @@
     }
 
     private void positiveFeedback(View v) {
-        mGutsContainer.closeControls(v, false);
+        mGutsContainer.closeControls(v, /* save= */ false);
         handleFeedback(true);
     }
 
@@ -176,7 +176,7 @@
             menuItem = mMenuRowPlugin.getLongpressMenuItem(mContext);
         }
 
-        mGutsContainer.closeControls(v, false);
+        mGutsContainer.closeControls(v, /* save= */ false);
         mNotificationGutsManager.openGuts(mExpandableNotificationRow, 0, 0, menuItem);
         handleFeedback(false);
     }
@@ -203,7 +203,7 @@
     }
 
     private void closeControls(View v) {
-        mGutsContainer.closeControls(v, false);
+        mGutsContainer.closeControls(v, /* save= */ false);
     }
 
     @Override
@@ -232,7 +232,7 @@
     }
 
     @Override
-    public boolean shouldBeSaved() {
+    public boolean shouldBeSavedOnClose() {
         return false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 7120fe5..0ce9656 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -157,7 +157,7 @@
             mShadeController.animateCollapsePanels();
             mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
         }
-        mGutsContainer.closeControls(v, true);
+        mGutsContainer.closeControls(v, /* save= */ true);
     };
 
     public NotificationConversationInfo(Context context, AttributeSet attrs) {
@@ -186,7 +186,6 @@
     }
 
     public void bindNotification(
-            @Action int selectedAction,
             ShortcutManager shortcutManager,
             PackageManager pm,
             PeopleSpaceWidgetManager peopleSpaceWidgetManager,
@@ -205,8 +204,6 @@
             OnConversationSettingsClickListener onConversationSettingsClickListener,
             Optional<BubblesManager> bubblesManagerOptional,
             ShadeController shadeController) {
-        mPressedApply = false;
-        mSelectedAction = selectedAction;
         mINotificationManager = iNotificationManager;
         mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
         mOnUserInteractionCallback = onUserInteractionCallback;
@@ -417,9 +414,7 @@
     }
 
     @Override
-    public void onFinishedClosing() {
-        mSelectedAction = -1;
-    }
+    public void onFinishedClosing() { }
 
     @Override
     public boolean needsFalsingProtection() {
@@ -564,7 +559,7 @@
     }
 
     @Override
-    public boolean shouldBeSaved() {
+    public boolean shouldBeSavedOnClose() {
         return mPressedApply;
     }
 
@@ -578,6 +573,12 @@
         if (save && mSelectedAction > -1) {
             updateChannel();
         }
+
+        // Clear the selected importance when closing, so when when we open again,
+        // we starts from a clean state.
+        mSelectedAction = -1;
+        mPressedApply = false;
+
         return false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
index fc296e1..93f0812 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
@@ -76,7 +76,7 @@
 
                     switch (action) {
                         case AccessibilityNodeInfo.ACTION_LONG_CLICK:
-                            closeControls(host, false);
+                            closeControls(host, /* save= */ false);
                             return true;
                     }
 
@@ -123,7 +123,7 @@
         /**
          * Return whether something changed and needs to be saved, possibly requiring a bouncer.
          */
-        boolean shouldBeSaved();
+        boolean shouldBeSavedOnClose();
 
         /**
          * Called when the guts view has finished its close animation.
@@ -259,7 +259,7 @@
         if (mGutsContent != null) {
             if ((mGutsContent.isLeavebehind() && leavebehinds)
                     || (!mGutsContent.isLeavebehind() && controls)) {
-                closeControls(x, y, mGutsContent.shouldBeSaved(), force);
+                closeControls(x, y, mGutsContent.shouldBeSavedOnClose(), force);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 7b0b0ce..ea12b82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -463,7 +463,6 @@
                         R.dimen.notification_guts_conversation_icon_size));
 
         notificationInfoView.bindNotification(
-                notificationInfoView.getSelectedAction(),
                 mShortcutManager,
                 pmUser,
                 mPeopleSpaceWidgetManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 8b01a47..ea0060a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -158,7 +158,7 @@
     // used by standard ui
     private OnClickListener mOnDismissSettings = v -> {
         mPressedApply = true;
-        mGutsContainer.closeControls(v, true);
+        mGutsContainer.closeControls(v, /* save= */ true);
     };
 
     public NotificationInfo(Context context, AttributeSet attrs) {
@@ -541,10 +541,6 @@
 
     @Override
     public void onFinishedClosing() {
-        if (mChosenImportance != null) {
-            mStartingChannelImportance = mChosenImportance;
-        }
-
         bindInlineControls();
 
         logUiEvent(NotificationControlsEvent.NOTIFICATION_CONTROLS_CLOSE);
@@ -604,7 +600,7 @@
     }
 
     @Override
-    public boolean shouldBeSaved() {
+    public boolean shouldBeSavedOnClose() {
         return mPressedApply;
     }
 
@@ -627,6 +623,12 @@
         if (save) {
             saveImportance();
         }
+
+        // Clear the selected importance when closing, so when when we open again,
+        // we starts from a clean state.
+        mChosenImportance = null;
+        mPressedApply = false;
+
         return false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index 512b049..adbfa75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -384,7 +384,7 @@
     private void undoSnooze(View v) {
         mSelectedOption = null;
         showSnoozeOptions(false);
-        mGutsContainer.closeControls(v, false);
+        mGutsContainer.closeControls(v, /* save= */ false);
     }
 
     @Override
@@ -433,7 +433,7 @@
     }
 
     @Override
-    public boolean shouldBeSaved() {
+    public boolean shouldBeSavedOnClose() {
         return true;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
index 186ffa6..ac97e77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
@@ -16,22 +16,13 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.IntDef;
 import android.app.INotificationManager;
-import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
-import android.os.Parcelable;
 import android.os.RemoteException;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
@@ -46,8 +37,6 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
-import java.lang.annotation.Retention;
-import java.util.List;
 import java.util.Set;
 
 /**
@@ -71,8 +60,6 @@
     private Set<NotificationChannel> mUniqueChannelsInRow;
     private Drawable mPkgIcon;
 
-    private @Action int mSelectedAction = -1;
-    private boolean mPressedApply;
     private boolean mPresentingChannelEditorDialog = false;
 
     private NotificationInfo.OnSettingsClickListener mOnSettingsClickListener;
@@ -82,14 +69,8 @@
     @VisibleForTesting
     boolean mSkipPost = false;
 
-    @Retention(SOURCE)
-    @IntDef({ACTION_SETTINGS})
-    private @interface Action {}
-    static final int ACTION_SETTINGS = 5;
-
     private OnClickListener mOnDone = v -> {
-        mPressedApply = true;
-        mGutsContainer.closeControls(v, true);
+        mGutsContainer.closeControls(v, /* save= */ false);
     };
 
     public PartialConversationInfo(Context context, AttributeSet attrs) {
@@ -107,7 +88,6 @@
             NotificationInfo.OnSettingsClickListener onSettingsClick,
             boolean isDeviceProvisioned,
             boolean isNonBlockable) {
-        mSelectedAction = -1;
         mINotificationManager = iNotificationManager;
         mPackageName = pkg;
         mSbn = entry.getSbn();
@@ -286,8 +266,8 @@
     }
 
     @Override
-    public boolean shouldBeSaved() {
-        return mPressedApply;
+    public boolean shouldBeSavedOnClose() {
+        return false;
     }
 
     @Override
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/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index 339f371..d24469e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -39,6 +39,8 @@
  * A view to show hints on Keyguard ("Swipe up to unlock", "Tap again to open").
  */
 public class KeyguardIndicationTextView extends TextView {
+    public static final long Y_IN_DURATION = 600L;
+
     @StyleRes
     private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea;
     @StyleRes
@@ -259,7 +261,7 @@
 
     private long getYInDuration() {
         if (!mAnimationsEnabled) return 0L;
-        return 600L;
+        return Y_IN_DURATION;
     }
 
     private long getFadeOutDuration() {
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/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index d058b75..53e08ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -10,6 +10,7 @@
 import android.provider.Settings
 import android.view.Surface
 import android.view.View
+import android.view.WindowManager.fixScale
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF
 import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
@@ -138,8 +139,8 @@
     }
 
     fun updateAnimatorDurationScale() {
-        animatorDurationScale =
-                globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f)
+        animatorDurationScale = fixScale(
+                globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f))
     }
 
     override fun shouldDelayKeyguardShow(): Boolean =
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 6c02b0d..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.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 8d69422..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.repository.NetworkCapabilitiesRepo
-import kotlinx.coroutines.CoroutineScope
-import javax.inject.Inject
-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 1aae250..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,58 +20,36 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.statusbar.pipeline.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.emptyFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.collect
+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.
         @Application private val scope: CoroutineScope,
-        statusBarPipelineFlags: StatusBarPipelineFlags,
+        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.
-    val processedInfoFlow: Flow<ProcessedConnectivityInfo> =
-            if (!statusBarPipelineFlags.isNewPipelineEnabled())
-                emptyFlow()
-            else connectivityInfoCollector.rawConnectivityInfoFlow
-                    .map { it.process() }
-                    .stateIn(
-                            scope,
-                            started = Lazily,
-                            initialValue = ProcessedConnectivityInfo()
-                    )
-
     override fun start() {
-    }
-
-    private fun RawConnectivityInfo.process(): ProcessedConnectivityInfo {
-        // TODO(b/238425913): Actually process the raw info into meaningful data.
-        return ProcessedConnectivityInfo(this.networkCapabilityInfo)
+        if (!statusBarPipelineFlags.isNewPipelineBackendEnabled()) {
+            return
+        }
+        // TODO(b/238425913): The view binder should do this instead. For now, do it here so we can
+        // see the logs.
+        scope.launch {
+            wifiViewModelProvider.get().isActivityInVisible.collect { }
+        }
     }
 }
-
-/**
- * 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/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt
deleted file mode 100644
index f88e9d6..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt
+++ /dev/null
@@ -1,59 +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.Network
-import android.net.NetworkCapabilities
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.dagger.StatusBarConnectivityLog
-import javax.inject.Inject
-
-@SysUISingleton
-class ConnectivityPipelineLogger @Inject constructor(
-    @StatusBarConnectivityLog private val buffer: LogBuffer,
-) {
-    fun logOnCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
-        buffer.log(
-            TAG,
-            LogLevel.INFO,
-            {
-                int1 = network.getNetId()
-                str1 = networkCapabilities.toString()
-            },
-            {
-                "onCapabilitiesChanged: net=$int1 capabilities=$str1"
-            }
-        )
-    }
-
-    fun logOnLost(network: Network) {
-        buffer.log(
-            TAG,
-            LogLevel.INFO,
-            {
-                int1 = network.getNetId()
-            },
-            {
-                "onLost: net=$int1"
-            }
-        )
-    }
-}
-
-private const val TAG = "SbConnectivityPipeline"
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 c4e2b73..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,9 +17,9 @@
 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
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -34,7 +34,5 @@
     abstract fun bindConnectivityInfoProcessor(cip: ConnectivityInfoProcessor): CoreStartable
 
     @Binds
-    abstract fun provideConnectivityInfoCollector(
-            impl: ConnectivityInfoCollectorImpl
-    ): ConnectivityInfoCollector
+    abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt
deleted file mode 100644
index e5980c3..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt
+++ /dev/null
@@ -1,92 +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.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.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 [NetworkCapabilites] for the current networks */
-@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,
-)
-
-private const val TAG = "ConnectivityRepository"
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
new file mode 100644
index 0000000..2a89309
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared
+
+import android.net.Network
+import android.net.NetworkCapabilities
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.StatusBarConnectivityLog
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onEach
+
+@SysUISingleton
+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,
+                LogLevel.INFO,
+                {
+                    str1 = callbackName
+                    str2 = changeInfo
+                },
+                {
+                    "Input: $str1: $str2"
+                }
+        )
+    }
+
+    /**
+     * 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,
+                LogLevel.INFO,
+                {
+                    str1 = outputParamName
+                    str2 = changeInfo
+                },
+                {
+                    "Output: $str1: $str2"
+                }
+        )
+    }
+
+    fun logOnCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
+        buffer.log(
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
+            {
+                int1 = network.getNetId()
+                str1 = networkCapabilities.toString()
+            },
+            {
+                "onCapabilitiesChanged: net=$int1 capabilities=$str1"
+            }
+        )
+    }
+
+    fun logOnLost(network: Network) {
+        buffer.log(
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
+            {
+                int1 = network.getNetId()
+            },
+            {
+                "onLost: net=$int1"
+            }
+        )
+    }
+
+    companion object {
+        const val SB_LOGGING_TAG = "SbConnectivity"
+
+        /**
+         * Log a change in one of the **outputs** to the connectivity pipeline.
+         *
+         * @param prettyPrint an optional function to transform the value into a readable string.
+         *   [toString] is used if no custom function is provided.
+         */
+        fun <T : Any> Flow<T>.logOutputChange(
+                logger: ConnectivityPipelineLogger,
+                outputParamName: String,
+                prettyPrint: (T) -> String = { it.toString() }
+        ): Flow<T> {
+            return this.onEach { logger.logOutputChange(outputParamName, prettyPrint(it)) }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
index 7c9df10..44c0496 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
@@ -14,10 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.common.data.model
+package com.android.systemui.statusbar.pipeline.wifi.data.model
 
-/** Models a two-dimensional position */
-data class Position(
-    val x: Int,
-    val y: Int,
+/**
+ * Provides information on the current wifi activity.
+ */
+data class WifiActivityModel(
+    /** True if the wifi has activity in (download). */
+    val hasActivityIn: Boolean,
+    /** True if the wifi has activity out (upload). */
+    val hasActivityOut: Boolean,
 )
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/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
new file mode 100644
index 0000000..43fbabc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.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.WifiNetworkModel
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * Provides data related to the wifi state.
+ */
+interface WifiRepository {
+    /**
+     * Observable for the current wifi network.
+     */
+    val wifiNetwork: Flow<WifiNetworkModel>
+
+    /**
+     * Observable for the current wifi network activity.
+     */
+    val wifiActivity: Flow<WifiActivityModel>
+}
+
+/** 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
+
+        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) {
+                Log.w(SB_LOGGING_TAG, "Null WifiManager; skipping activity callback")
+                flowOf(ACTIVITY_DEFAULT)
+            } else {
+                conflatedCallbackFlow {
+                    val callback = TrafficStateCallback { state ->
+                        logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state))
+                        trySend(trafficStateToWifiActivityModel(state))
+                    }
+
+                    trySend(ACTIVITY_DEFAULT)
+                    wifiManager.registerTrafficStateCallback(mainExecutor, callback)
+
+                    awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
+                }
+            }
+
+    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(
+                hasActivityIn = state == TrafficStateCallback.DATA_ACTIVITY_IN ||
+                    state == TrafficStateCallback.DATA_ACTIVITY_INOUT,
+                hasActivityOut = state == TrafficStateCallback.DATA_ACTIVITY_OUT ||
+                    state == TrafficStateCallback.DATA_ACTIVITY_INOUT,
+            )
+        }
+
+        private fun networkCapabilitiesToWifiInfo(
+            networkCapabilities: NetworkCapabilities
+        ): WifiInfo? {
+            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"
+                TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
+                TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
+                TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
+                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
new file mode 100644
index 0000000..a9da63b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.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.domain.interactor
+
+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
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+
+/**
+ * The business logic layer for the wifi icon.
+ *
+ * This interactor processes information from our data layer into information that the UI layer can
+ * use.
+ */
+@SysUISingleton
+class WifiInteractor @Inject constructor(
+        repository: WifiRepository,
+) {
+    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
+            }
+        }
+    }
+
+    val hasActivityIn: Flow<Boolean> = combine(repository.wifiActivity, ssid) { activity, ssid ->
+            activity.hasActivityIn && ssid != null
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
new file mode 100644
index 0000000..a19d1bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.shared
+
+import android.content.Context
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * An object storing constants that we use for calculating the wifi icon. Stored in a class for
+ * logging purposes.
+ */
+@SysUISingleton
+class WifiConstants @Inject constructor(
+        context: Context,
+        dumpManager: DumpManager,
+) : Dumpable {
+    init {
+        dumpManager.registerDumpable("$SB_LOGGING_TAG:WifiConstants", this)
+    }
+
+    /** True if we should show the activityIn/activityOut icons and false otherwise. */
+    val shouldShowActivityConfig = context.resources.getBoolean(R.bool.config_showActivity)
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.apply {
+            println("shouldShowActivityConfig=$shouldShowActivityConfig")
+        }
+    }
+}
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
new file mode 100644
index 0000000..7a26260
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.ui.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
+
+/**
+ * Models the UI state for the status bar wifi icon.
+ *
+ * TODO(b/238425913): Hook this up to the real status bar wifi view using a view binder.
+ */
+class WifiViewModel @Inject constructor(
+    statusBarPipelineFlags: StatusBarPipelineFlags,
+    private val constants: WifiConstants,
+    private val logger: ConnectivityPipelineLogger,
+    private val interactor: WifiInteractor,
+) {
+    val isActivityInVisible: Flow<Boolean>
+        get() =
+            if (!constants.shouldShowActivityConfig) {
+                flowOf(false)
+            } else {
+                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/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 8f2a432..fc20ac2 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -138,7 +138,7 @@
 
         ensureOverlayRemoved()
 
-        val newRoot = SurfaceControlViewHost(context, context.display!!, wwm, false)
+        val newRoot = SurfaceControlViewHost(context, context.display!!, wwm)
         val newView =
             LightRevealScrim(context, null).apply {
                 revealEffect = createLightRevealEffect()
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/CoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
index 05d087e..0a44bda 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
@@ -37,5 +37,5 @@
     @Provides
     @SysUISingleton
     @Background
-    fun bgDispatcher(): CoroutineDispatcher = Dispatchers.Default
+    fun bgDispatcher(): CoroutineDispatcher = Dispatchers.IO
 }
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 c677371..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;
@@ -581,6 +582,7 @@
     @Test
     public void testTriesToAuthenticate_whenBouncer() {
         fingerprintIsNotEnrolled();
+        faceAuthEnabled();
         setKeyguardBouncerVisibility(true);
 
         verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
@@ -1005,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();
@@ -1218,6 +1220,7 @@
     public void testShouldListenForFace_whenFaceIsAlreadyAuthenticated_returnsFalse()
             throws RemoteException {
         // Face auth should run when the following is true.
+        faceAuthEnabled();
         bouncerFullyVisibleAndNotGoingToSleep();
         fingerprintIsNotEnrolled();
         keyguardNotGoingAway();
@@ -1284,6 +1287,7 @@
     public void testShouldListenForFace_whenBiometricsDisabledForUser_returnsFalse()
             throws RemoteException {
         // Preconditions for face auth to run
+        faceAuthEnabled();
         keyguardNotGoingAway();
         bouncerFullyVisibleAndNotGoingToSleep();
         fingerprintIsNotEnrolled();
@@ -1307,6 +1311,7 @@
     public void testShouldListenForFace_whenUserCurrentlySwitching_returnsFalse()
             throws RemoteException {
         // Preconditions for face auth to run
+        faceAuthEnabled();
         keyguardNotGoingAway();
         bouncerFullyVisibleAndNotGoingToSleep();
         fingerprintIsNotEnrolled();
@@ -1329,6 +1334,7 @@
     public void testShouldListenForFace_whenSecureCameraLaunched_returnsFalse()
             throws RemoteException {
         // Preconditions for face auth to run
+        faceAuthEnabled();
         keyguardNotGoingAway();
         bouncerFullyVisibleAndNotGoingToSleep();
         fingerprintIsNotEnrolled();
@@ -1374,6 +1380,7 @@
     public void testShouldListenForFace_whenBouncerShowingAndDeviceIsAwake_returnsTrue()
             throws RemoteException {
         // Preconditions for face auth to run
+        faceAuthEnabled();
         keyguardNotGoingAway();
         currentUserIsPrimary();
         currentUserDoesNotHaveTrust();
@@ -1539,8 +1546,19 @@
         assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(anyBoolean())).isEqualTo(true);
     }
 
+    private void faceAuthEnabled() {
+        // this ensures KeyguardUpdateMonitor updates the cached mIsFaceEnrolled flag using the
+        // face manager mock wire-up in setup()
+        mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(mCurrentUserId);
+    }
+
     private void fingerprintIsNotEnrolled() {
         when(mFingerprintManager.hasEnrolledTemplates(mCurrentUserId)).thenReturn(false);
+        // This updates the cached fingerprint state.
+        // There is no straightforward API to update the fingerprint state.
+        // It currently works updates after enrollment changes because something else invokes
+        // startListeningForFingerprint(), which internally calls this method.
+        mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(mCurrentUserId);
     }
 
     private void statusBarShadeIsNotLocked() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
index 6f4846a..36ae3c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
@@ -19,6 +19,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -27,6 +28,7 @@
 import android.os.SystemClock;
 import android.testing.AndroidTestingRunner;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.ViewConfiguration;
 
 import androidx.test.filters.SmallTest;
@@ -52,6 +54,7 @@
     private int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
     private MagnificationGestureDetector mGestureDetector;
     private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+    private View mSpyView;
     @Mock
     private MagnificationGestureDetector.OnGestureListener mListener;
     @Mock
@@ -66,6 +69,7 @@
             return null;
         }).when(mHandler).postAtTime(any(Runnable.class), anyLong());
         mGestureDetector = new MagnificationGestureDetector(mContext, mHandler, mListener);
+        mSpyView = Mockito.spy(new View(mContext));
     }
 
     @After
@@ -79,7 +83,7 @@
         final MotionEvent downEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
                 MotionEvent.ACTION_DOWN, ACTION_DOWN_X, ACTION_DOWN_Y);
 
-        mGestureDetector.onTouch(downEvent);
+        mGestureDetector.onTouch(mSpyView, downEvent);
 
         mListener.onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
     }
@@ -92,14 +96,14 @@
         final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
                 MotionEvent.ACTION_UP, ACTION_DOWN_X, ACTION_DOWN_Y);
 
-        mGestureDetector.onTouch(downEvent);
-        mGestureDetector.onTouch(upEvent);
+        mGestureDetector.onTouch(mSpyView, downEvent);
+        mGestureDetector.onTouch(mSpyView, upEvent);
 
         InOrder inOrder = Mockito.inOrder(mListener);
         inOrder.verify(mListener).onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
-        inOrder.verify(mListener).onSingleTap();
+        inOrder.verify(mListener).onSingleTap(mSpyView);
         inOrder.verify(mListener).onFinish(ACTION_DOWN_X, ACTION_DOWN_Y);
-        verify(mListener, never()).onDrag(anyFloat(), anyFloat());
+        verify(mListener, never()).onDrag(eq(mSpyView), anyFloat(), anyFloat());
     }
 
     @Test
@@ -110,10 +114,10 @@
         final MotionEvent cancelEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
                 MotionEvent.ACTION_CANCEL, ACTION_DOWN_X, ACTION_DOWN_Y);
 
-        mGestureDetector.onTouch(downEvent);
-        mGestureDetector.onTouch(cancelEvent);
+        mGestureDetector.onTouch(mSpyView, downEvent);
+        mGestureDetector.onTouch(mSpyView, cancelEvent);
 
-        verify(mListener, never()).onSingleTap();
+        verify(mListener, never()).onSingleTap(mSpyView);
     }
 
     @Test
@@ -124,10 +128,10 @@
         final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
                 MotionEvent.ACTION_POINTER_DOWN, ACTION_DOWN_X, ACTION_DOWN_Y);
 
-        mGestureDetector.onTouch(downEvent);
-        mGestureDetector.onTouch(upEvent);
+        mGestureDetector.onTouch(mSpyView, downEvent);
+        mGestureDetector.onTouch(mSpyView, upEvent);
 
-        verify(mListener, never()).onSingleTap();
+        verify(mListener, never()).onSingleTap(mSpyView);
     }
 
     @Test
@@ -138,15 +142,15 @@
         final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
                 MotionEvent.ACTION_UP, ACTION_DOWN_X, ACTION_DOWN_Y);
 
-        mGestureDetector.onTouch(downEvent);
+        mGestureDetector.onTouch(mSpyView, downEvent);
         // Execute the pending message for stopping single-tap detection.
         mCancelSingleTapRunnable.run();
-        mGestureDetector.onTouch(upEvent);
+        mGestureDetector.onTouch(mSpyView, upEvent);
 
         InOrder inOrder = Mockito.inOrder(mListener);
         inOrder.verify(mListener).onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
         inOrder.verify(mListener).onFinish(ACTION_DOWN_X, ACTION_DOWN_Y);
-        verify(mListener, never()).onSingleTap();
+        verify(mListener, never()).onSingleTap(mSpyView);
     }
 
     @Test
@@ -160,14 +164,14 @@
         final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
                 MotionEvent.ACTION_UP, ACTION_DOWN_X, ACTION_DOWN_Y);
 
-        mGestureDetector.onTouch(downEvent);
-        mGestureDetector.onTouch(moveEvent);
-        mGestureDetector.onTouch(upEvent);
+        mGestureDetector.onTouch(mSpyView, downEvent);
+        mGestureDetector.onTouch(mSpyView, moveEvent);
+        mGestureDetector.onTouch(mSpyView, upEvent);
 
         InOrder inOrder = Mockito.inOrder(mListener);
         inOrder.verify(mListener).onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
-        inOrder.verify(mListener).onDrag(dragOffset, 0);
+        inOrder.verify(mListener).onDrag(mSpyView, dragOffset, 0);
         inOrder.verify(mListener).onFinish(ACTION_DOWN_X, ACTION_DOWN_Y);
-        verify(mListener, never()).onSingleTap();
+        verify(mListener, never()).onSingleTap(mSpyView);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
index bc89da7..00cb491 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
@@ -145,7 +145,7 @@
 
     @Test
     public void removeButton_buttonIsShowing_removeViewAndUnregisterComponentCallbacks() {
-        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
 
         mMagnificationModeSwitch.removeButton();
 
@@ -167,7 +167,7 @@
 
     @Test
     public void showButton_excludeSystemGestureArea() {
-        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
 
         verify(mSpyImageView).setSystemGestureExclusionRects(any(List.class));
     }
@@ -178,7 +178,7 @@
         when(mAccessibilityManager.getRecommendedTimeoutMillis(anyInt(), anyInt())).thenReturn(
                 a11yTimeout);
 
-        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
 
         verify(mAccessibilityManager).getRecommendedTimeoutMillis(
                 DEFAULT_FADE_OUT_ANIMATION_DELAY_MS, AccessibilityManager.FLAG_CONTENT_ICONS
@@ -188,7 +188,7 @@
 
     @Test
     public void showMagnificationButton_windowModeAndFadingOut_verifyAnimationEndAction() {
-        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
         executeFadeOutAnimation();
 
         // Verify the end action after fade-out.
@@ -389,15 +389,15 @@
 
     @Test
     public void initializeA11yNode_showWindowModeButton_expectedValues() {
-        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
         final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
 
         mSpyImageView.onInitializeAccessibilityNodeInfo(nodeInfo);
 
         assertEquals(mContext.getString(R.string.magnification_mode_switch_description),
                 nodeInfo.getContentDescription());
-        assertEquals(mContext.getString(R.string.magnification_mode_switch_state_window),
-                nodeInfo.getStateDescription());
+        assertEquals(mContext.getString(R.string.magnification_mode_switch_state_full_screen),
+                nodeInfo.getStateDescription().toString());
         assertThat(nodeInfo.getActionList(),
                 hasItems(new AccessibilityNodeInfo.AccessibilityAction(
                         ACTION_CLICK.getId(), mContext.getResources().getString(
@@ -422,18 +422,18 @@
 
     @Test
     public void performClickA11yActions_showWindowModeButton_verifyTapAction() {
-        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
         resetAndStubMockImageViewAndAnimator();
 
         mSpyImageView.performAccessibilityAction(
                 ACTION_CLICK.getId(), null);
 
-        verifyTapAction(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+        verifyTapAction(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
     }
 
     @Test
     public void performMoveLeftA11yAction_showButtonAtRightEdge_moveToLeftEdge() {
-        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
 
         mSpyImageView.performAccessibilityAction(
                 R.id.accessibility_action_move_left, null);
@@ -456,7 +456,7 @@
 
     @Test
     public void showButton_hasAccessibilityWindowTitle() {
-        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
 
         final WindowManager.LayoutParams layoutPrams =
                 mWindowManager.getLayoutParamsFromAttachedView();
@@ -468,7 +468,7 @@
 
     @Test
     public void showButton_registerComponentCallbacks() {
-        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
 
         verify(mContext).registerComponentCallbacks(mMagnificationModeSwitch);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
index a56218b..82ae6ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
@@ -24,6 +24,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.Display;
+import android.view.View;
 
 import androidx.test.filters.SmallTest;
 
@@ -46,6 +47,7 @@
     private FakeSwitchSupplier mSupplier;
     private MagnificationModeSwitch mModeSwitch;
     private ModeSwitchesController mModeSwitchesController;
+    private View mSpyView;
     @Mock
     private MagnificationModeSwitch.SwitchListener mListener;
 
@@ -57,6 +59,7 @@
         mModeSwitchesController = new ModeSwitchesController(mSupplier);
         mModeSwitchesController.setSwitchListenerDelegate(mListener);
         mModeSwitch = Mockito.spy(new MagnificationModeSwitch(mContext, mModeSwitchesController));
+        mSpyView = Mockito.spy(new View(mContext));
     }
 
     @After
@@ -94,12 +97,12 @@
     @Test
     public void testOnSwitchClick_showWindowModeButton_invokeListener() {
         mModeSwitchesController.showButton(Display.DEFAULT_DISPLAY,
-                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
 
-        mModeSwitch.onSingleTap();
+        mModeSwitch.onSingleTap(mSpyView);
 
         verify(mListener).onSwitch(mContext.getDisplayId(),
-                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
     }
 
     private class FakeSwitchSupplier extends DisplayIdIndexSupplier<MagnificationModeSwitch> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 3d77d64..69ccc8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -19,6 +19,8 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.Choreographer.FrameCallback;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
 import static android.view.WindowInsets.Type.systemGestures;
 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
@@ -46,6 +48,7 @@
 import static org.mockito.Mockito.when;
 
 import android.animation.ValueAnimator;
+import android.annotation.IdRes;
 import android.app.Instrumentation;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -65,6 +68,7 @@
 import android.text.TextUtils;
 import android.view.Display;
 import android.view.IWindowSession;
+import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.View;
@@ -126,8 +130,13 @@
     private WindowMagnificationController mWindowMagnificationController;
     private Instrumentation mInstrumentation;
     private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0);
+
     private IWindowSession mWindowSessionSpy;
 
+    private View mSpyView;
+    private View.OnTouchListener mTouchListener;
+    private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+
     @Before
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
@@ -165,6 +174,12 @@
 
         verify(mMirrorWindowControl).setWindowDelegate(
                 any(MirrorWindowControl.MirrorWindowDelegate.class));
+        mSpyView = Mockito.spy(new View(mContext));
+        doAnswer((invocation) -> {
+            mTouchListener = invocation.getArgument(0);
+            return null;
+        }).when(mSpyView).setOnTouchListener(
+                any(View.OnTouchListener.class));
     }
 
     @After
@@ -702,7 +717,7 @@
         });
 
         mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onSingleTap();
+            mWindowMagnificationController.onSingleTap(mSpyView);
         });
 
         final View mirrorView = mWindowManager.getAttachedView();
@@ -910,6 +925,38 @@
         assertTrue(magnificationCenterY.get() < bounds.bottom);
     }
 
+    @Test
+    public void performSingleTap_DragHandle() {
+        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController.enableWindowMagnificationInternal(
+                            1.5f, bounds.centerX(), bounds.centerY());
+                });
+        View dragButton = getInternalView(R.id.drag_handle);
+
+        // Perform a single-tap
+        final long downTime = SystemClock.uptimeMillis();
+        dragButton.dispatchTouchEvent(
+                obtainMotionEvent(downTime, 0, ACTION_DOWN, 100, 100));
+        dragButton.dispatchTouchEvent(
+                obtainMotionEvent(downTime, downTime, ACTION_UP, 100, 100));
+
+        verify(mWindowManager).addView(any(View.class), any());
+    }
+
+    private <T extends View> T getInternalView(@IdRes int idRes) {
+        View mirrorView = mWindowManager.getAttachedView();
+        T view = mirrorView.findViewById(idRes);
+        assertNotNull(view);
+        return view;
+    }
+
+    private MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, float x,
+                                          float y) {
+        return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y);
+    }
+
     private CharSequence getAccessibilityWindowTitle() {
         final View mirrorView = mWindowManager.getAttachedView();
         if (mirrorView == null) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
new file mode 100644
index 0000000..2f94b69
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.IdRes;
+import android.content.Context;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.CompoundButton;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class WindowMagnificationSettingsTest extends SysuiTestCase {
+
+    private static final int MAGNIFICATION_SIZE_SMALL = 1;
+    private static final int MAGNIFICATION_SIZE_MEDIUM = 2;
+    private static final int MAGNIFICATION_SIZE_LARGE = 3;
+
+    private ViewGroup mSettingView;
+    @Mock
+    private AccessibilityManager mAccessibilityManager;
+    @Mock
+    private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
+    @Mock
+    private WindowMagnificationSettingsCallback mWindowMagnificationSettingsCallback;
+    private TestableWindowManager mWindowManager;
+    private WindowMagnificationSettings mWindowMagnificationSettings;
+    private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = getContext();
+        mContext.setTheme(android.R.style.Theme_DeviceDefault_DayNight);
+        final WindowManager wm = mContext.getSystemService(WindowManager.class);
+        mWindowManager = spy(new TestableWindowManager(wm));
+        mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+        mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
+
+        mWindowMagnificationSettings = new WindowMagnificationSettings(mContext,
+                mWindowMagnificationSettingsCallback, mSfVsyncFrameProvider);
+
+        mSettingView = mWindowMagnificationSettings.getSettingView();
+    }
+
+    @After
+    public void tearDown() {
+        mMotionEventHelper.recycleEvents();
+        mWindowMagnificationSettings.hideSettingPanel();
+    }
+
+    @Test
+    public void showSettingPanel_hasAccessibilityWindowTitle() {
+        mWindowMagnificationSettings.showSettingPanel();
+
+        final WindowManager.LayoutParams layoutPrams =
+                mWindowManager.getLayoutParamsFromAttachedView();
+        assertNotNull(layoutPrams);
+        assertEquals(getContext().getResources()
+                        .getString(com.android.internal.R.string.android_system_label),
+                layoutPrams.accessibilityTitle.toString());
+    }
+
+    @Test
+    public void performClick_smallSizeButton_changeMagnifierSizeSmall() {
+        // Open view
+        mWindowMagnificationSettings.showSettingPanel();
+
+        verifyOnSetMagnifierSize(R.id.magnifier_small_button, MAGNIFICATION_SIZE_SMALL);
+    }
+
+    @Test
+    public void performClick_mediumSizeButton_changeMagnifierSizeMedium() {
+        // Open view
+        mWindowMagnificationSettings.showSettingPanel();
+
+        verifyOnSetMagnifierSize(R.id.magnifier_medium_button, MAGNIFICATION_SIZE_MEDIUM);
+    }
+
+    @Test
+    public void performClick_largeSizeButton_changeMagnifierSizeLarge() {
+        // Open view
+        mWindowMagnificationSettings.showSettingPanel();
+
+        verifyOnSetMagnifierSize(R.id.magnifier_large_button, MAGNIFICATION_SIZE_LARGE);
+    }
+
+    private void verifyOnSetMagnifierSize(@IdRes int viewId, int expectedSizeIndex) {
+        View changeSizeButton = getInternalView(viewId);
+
+        // Perform click
+        changeSizeButton.performClick();
+
+        verify(mWindowMagnificationSettingsCallback).onSetMagnifierSize(expectedSizeIndex);
+    }
+
+
+    @Test
+    public void performClick_fullScreenModeButton_setEditMagnifierSizeMode() {
+        View fullScreenModeButton = getInternalView(R.id.magnifier_full_button);
+        getInternalView(R.id.magnifier_panel_view);
+
+        // Open view
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // Perform click
+        fullScreenModeButton.performClick();
+
+        verify(mWindowManager).removeView(mSettingView);
+        verify(mWindowMagnificationSettingsCallback)
+                .onModeSwitch(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+    }
+
+    @Test
+    public void performClick_editButton_setEditMagnifierSizeMode() {
+        View editButton = getInternalView(R.id.magnifier_edit_button);
+
+        // Open view
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // Perform click
+        editButton.performClick();
+
+        verify(mWindowMagnificationSettingsCallback).onEditMagnifierSizeMode(true);
+        verify(mWindowManager).removeView(mSettingView);
+    }
+
+    @Test
+    public void performClick_setDiagonalScrollingSwitch_toggleDiagonalScrollingSwitchMode() {
+        CompoundButton diagonalScrollingSwitch =
+                getInternalView(R.id.magnifier_horizontal_lock_switch);
+        final boolean currentCheckedState = diagonalScrollingSwitch.isChecked();
+
+        // Open view
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // Perform click
+        diagonalScrollingSwitch.performClick();
+
+        verify(mWindowMagnificationSettingsCallback).onSetDiagonalScrolling(!currentCheckedState);
+    }
+
+    private <T extends View> T getInternalView(@IdRes int idRes) {
+        T view = mSettingView.findViewById(idRes);
+        assertNotNull(view);
+        return view;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
index ccf2f8b..e1bd25b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
@@ -158,6 +158,18 @@
     }
 
     @Test
+    public void onModeSwitch_enabled_notifyCallback() throws RemoteException {
+        final int magnificationModeFullScreen = 1;
+        mCommandQueue.requestWindowMagnificationConnection(true);
+        waitForIdleSync();
+
+        mWindowMagnification.onModeSwitch(TEST_DISPLAY, magnificationModeFullScreen);
+
+        verify(mConnectionCallback).onChangeMagnificationMode(TEST_DISPLAY,
+                magnificationModeFullScreen);
+    }
+
+    @Test
     public void overviewProxyIsConnected_noController_resetFlag() {
         mOverviewProxyListener.onConnectionChanged(true);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index 273786d..8fc0489 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -664,6 +664,60 @@
     }
 
     @Test
+    fun animateAddition_runnableRunsWhenAnimationEnds() {
+        var runnableRun = false
+        val onAnimationEndRunnable = { runnableRun = true }
+
+        ViewHierarchyAnimator.animateAddition(
+                rootView,
+                origin = ViewHierarchyAnimator.Hotspot.CENTER,
+                includeMargins = true,
+                onAnimationEnd = onAnimationEndRunnable
+        )
+        rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+        endAnimation(rootView)
+
+        assertEquals(true, runnableRun)
+    }
+
+    @Test
+    fun animateAddition_runnableDoesNotRunWhenAnimationCancelled() {
+        var runnableRun = false
+        val onAnimationEndRunnable = { runnableRun = true }
+
+        ViewHierarchyAnimator.animateAddition(
+            rootView,
+            origin = ViewHierarchyAnimator.Hotspot.CENTER,
+            includeMargins = true,
+            onAnimationEnd = onAnimationEndRunnable
+        )
+        rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+        cancelAnimation(rootView)
+
+        assertEquals(false, runnableRun)
+    }
+
+    @Test
+    fun animationAddition_runnableDoesNotRunWhenOnlyPartwayThroughAnimation() {
+        var runnableRun = false
+        val onAnimationEndRunnable = { runnableRun = true }
+
+        ViewHierarchyAnimator.animateAddition(
+            rootView,
+            origin = ViewHierarchyAnimator.Hotspot.CENTER,
+            includeMargins = true,
+            onAnimationEnd = onAnimationEndRunnable
+        )
+        rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+        advanceAnimation(rootView, 0.5f)
+
+        assertEquals(false, runnableRun)
+    }
+
+    @Test
     fun animatesViewRemovalFromStartToEnd() {
         setUpRootWithChildren()
 
@@ -1158,6 +1212,16 @@
         }
     }
 
+    private fun cancelAnimation(rootView: View) {
+        (rootView.getTag(R.id.tag_animator) as? ObjectAnimator)?.cancel()
+
+        if (rootView is ViewGroup) {
+            for (i in 0 until rootView.childCount) {
+                cancelAnimation(rootView.getChildAt(i))
+            }
+        }
+    }
+
     private fun endFadeInAnimation(rootView: View) {
         (rootView.getTag(R.id.tag_alpha_animator) as? ObjectAnimator)?.end()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt
index 328ad39..58d9069 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt
@@ -41,11 +41,13 @@
 @SmallTest
 class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {
 
-    @JvmField @Rule
+    @JvmField
+    @Rule
     var mockitoRule = MockitoJUnit.rule()
 
     @Mock
     private lateinit var callback: AuthBiometricView.Callback
+
     @Mock
     private lateinit var panelController: AuthPanelController
 
@@ -67,6 +69,7 @@
     fun fingerprintSuccessDoesNotRequireExplicitConfirmation() {
         biometricView.onDialogAnimatedIn()
         biometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT)
+        TestableLooper.get(this).moveTimeForward(1000)
         waitForIdleSync()
 
         assertThat(biometricView.isAuthenticated).isTrue()
@@ -86,6 +89,7 @@
 
         // icon acts as confirm button
         biometricView.mIconView.performClick()
+        TestableLooper.get(this).moveTimeForward(1000)
         waitForIdleSync()
 
         assertThat(biometricView.isAuthenticated).isTrue()
@@ -102,6 +106,7 @@
         verify(callback, never()).onAction(AuthBiometricView.Callback.ACTION_ERROR)
 
         biometricView.onError(TYPE_FINGERPRINT, "that's a nope")
+        TestableLooper.get(this).moveTimeForward(1000)
         waitForIdleSync()
 
         verify(callback).onAction(AuthBiometricView.Callback.ACTION_ERROR)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
index 687cb517..bce98cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
@@ -42,20 +42,23 @@
 @SmallTest
 class AuthBiometricFingerprintViewTest : SysuiTestCase() {
 
-    @JvmField @Rule
+    @JvmField
+    @Rule
     val mockitoRule = MockitoJUnit.rule()
 
     @Mock
     private lateinit var callback: AuthBiometricView.Callback
+
     @Mock
     private lateinit var panelController: AuthPanelController
 
     private lateinit var biometricView: AuthBiometricView
 
     private fun createView(allowDeviceCredential: Boolean = false): AuthBiometricFingerprintView {
-        val view = R.layout.auth_biometric_fingerprint_view.asTestAuthBiometricView(
+        val view: AuthBiometricFingerprintView =
+                R.layout.auth_biometric_fingerprint_view.asTestAuthBiometricView(
                 mContext, callback, panelController, allowDeviceCredential = allowDeviceCredential
-        ) as AuthBiometricFingerprintView
+        )
         waitForIdleSync()
         return view
     }
@@ -73,6 +76,7 @@
     @Test
     fun testOnAuthenticationSucceeded_noConfirmationRequired_sendsActionAuthenticated() {
         biometricView.onAuthenticationSucceeded(BiometricAuthenticator.TYPE_FINGERPRINT)
+        TestableLooper.get(this).moveTimeForward(1000)
         waitForIdleSync()
 
         assertThat(biometricView.isAuthenticated).isTrue()
@@ -83,6 +87,7 @@
     fun testOnAuthenticationSucceeded_confirmationRequired_updatesDialogContents() {
         biometricView.setRequireConfirmation(true)
         biometricView.onAuthenticationSucceeded(BiometricAuthenticator.TYPE_FINGERPRINT)
+        TestableLooper.get(this).moveTimeForward(1000)
         waitForIdleSync()
 
         // TODO: this should be tested in the subclasses
@@ -104,6 +109,7 @@
     @Test
     fun testPositiveButton_sendsActionAuthenticated() {
         biometricView.mConfirmButton.performClick()
+        TestableLooper.get(this).moveTimeForward(1000)
         waitForIdleSync()
 
         verify(callback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED)
@@ -114,6 +120,7 @@
     fun testNegativeButton_beforeAuthentication_sendsActionButtonNegative() {
         biometricView.onDialogAnimatedIn()
         biometricView.mNegativeButton.performClick()
+        TestableLooper.get(this).moveTimeForward(1000)
         waitForIdleSync()
 
         verify(callback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE)
@@ -126,6 +133,7 @@
 
         assertThat(biometricView.mNegativeButton.visibility).isEqualTo(View.GONE)
         biometricView.mCancelButton.performClick()
+        TestableLooper.get(this).moveTimeForward(1000)
         waitForIdleSync()
 
         verify(callback).onAction(AuthBiometricView.Callback.ACTION_USER_CANCELED)
@@ -134,6 +142,7 @@
     @Test
     fun testTryAgainButton_sendsActionTryAgain() {
         biometricView.mTryAgainButton.performClick()
+        TestableLooper.get(this).moveTimeForward(1000)
         waitForIdleSync()
 
         verify(callback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN)
@@ -144,6 +153,7 @@
     @Test
     fun testOnErrorSendsActionError() {
         biometricView.onError(BiometricAuthenticator.TYPE_FACE, "testError")
+        TestableLooper.get(this).moveTimeForward(1000)
         waitForIdleSync()
 
         verify(callback).onAction(eq(AuthBiometricView.Callback.ACTION_ERROR))
@@ -156,6 +166,7 @@
 
         val message = "another error"
         biometricView.onError(BiometricAuthenticator.TYPE_FACE, message)
+        TestableLooper.get(this).moveTimeForward(1000)
         waitForIdleSync()
 
         assertThat(biometricView.isAuthenticating).isFalse()
@@ -178,6 +189,7 @@
         val view = View(mContext)
         biometricView.setBackgroundView(view)
         biometricView.onAuthenticationSucceeded(BiometricAuthenticator.TYPE_FINGERPRINT)
+        waitForIdleSync()
         view.performClick()
 
         verify(callback, never())
@@ -225,14 +237,14 @@
         biometricView.onSaveState(state)
         assertThat(biometricView.mTryAgainButton.visibility).isEqualTo(View.GONE)
         assertThat(state.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY))
-            .isEqualTo(View.GONE)
+                .isEqualTo(View.GONE)
         assertThat(state.getInt(AuthDialog.KEY_BIOMETRIC_STATE))
-            .isEqualTo(AuthBiometricView.STATE_ERROR)
+                .isEqualTo(AuthBiometricView.STATE_ERROR)
         assertThat(biometricView.mIndicatorView.visibility).isEqualTo(View.VISIBLE)
         assertThat(state.getBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING)).isTrue()
         assertThat(biometricView.mIndicatorView.text).isEqualTo(failureMessage)
         assertThat(state.getString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING))
-            .isEqualTo(failureMessage)
+                .isEqualTo(failureMessage)
 
         // TODO: Test dialog size. Should move requireConfirmation to buildBiometricPromptBundle
 
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/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 21c018a..6e89bb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -48,6 +48,7 @@
 import com.android.keyguard.KeyguardDisplayManager;
 import com.android.keyguard.KeyguardSecurityView;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.logging.KeyguardViewMediatorLogger;
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -106,6 +107,7 @@
     private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
     private @Mock DreamOverlayStateController mDreamOverlayStateController;
     private @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
+    private @Mock KeyguardViewMediatorLogger mLogger;
     private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -262,7 +264,8 @@
                 mInteractionJankMonitor,
                 mDreamOverlayStateController,
                 mNotificationShadeWindowControllerLazy,
-                () -> mActivityLaunchAnimator);
+                () -> mActivityLaunchAnimator,
+                mLogger);
         mViewMediator.start();
     }
 }
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/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 38a3375..11eb4e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -16,11 +16,10 @@
 
 package com.android.systemui.keyguard.data.repository
 
-import com.android.systemui.common.data.model.Position
+import com.android.systemui.common.shared.model.Position
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.yield
 
 /** Fake implementation of [KeyguardRepository] */
 class FakeKeyguardRepository : KeyguardRepository {
@@ -56,30 +55,15 @@
         _clockPosition.value = Position(x, y)
     }
 
-    suspend fun setKeyguardShowing(isShowing: Boolean) {
+    fun setKeyguardShowing(isShowing: Boolean) {
         _isKeyguardShowing.value = isShowing
-        // Yield to allow the test's collection coroutine to "catch up" and collect this value
-        // before the test continues to the next line.
-        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
-        // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
-        yield()
     }
 
-    suspend fun setDozing(isDozing: Boolean) {
+    fun setDozing(isDozing: Boolean) {
         _isDozing.value = isDozing
-        // Yield to allow the test's collection coroutine to "catch up" and collect this value
-        // before the test continues to the next line.
-        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
-        // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
-        yield()
     }
 
-    suspend fun setDozeAmount(dozeAmount: Float) {
+    fun setDozeAmount(dozeAmount: Float) {
         _dozeAmount.value = dozeAmount
-        // Yield to allow the test's collection coroutine to "catch up" and collect this value
-        // before the test continues to the next line.
-        // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
-        // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
-        yield()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 3d2c51a..3aa2266 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.data.model.Position
+import com.android.systemui.common.shared.model.Position
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.argumentCaptor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
index 1c9902b..e68c43f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
@@ -23,18 +23,18 @@
 /** Fake implementation of [FakeKeyguardQuickAffordanceRegistry], for tests. */
 class FakeKeyguardQuickAffordanceRegistry(
     private val configsByPosition:
-        Map<KeyguardQuickAffordancePosition, List<KeyguardQuickAffordanceConfig>>,
-) : KeyguardQuickAffordanceRegistry {
+        Map<KeyguardQuickAffordancePosition, List<FakeKeyguardQuickAffordanceConfig>>,
+) : KeyguardQuickAffordanceRegistry<FakeKeyguardQuickAffordanceConfig> {
 
     override fun getAll(
         position: KeyguardQuickAffordancePosition
-    ): List<KeyguardQuickAffordanceConfig> {
+    ): List<FakeKeyguardQuickAffordanceConfig> {
         return configsByPosition.getValue(position)
     }
 
     override fun get(
-        configClass: KClass<out KeyguardQuickAffordanceConfig>
-    ): KeyguardQuickAffordanceConfig {
+        configClass: KClass<out FakeKeyguardQuickAffordanceConfig>
+    ): FakeKeyguardQuickAffordanceConfig {
         return configsByPosition.values
             .flatten()
             .associateBy { config -> config::class }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt
deleted file mode 100644
index ba0c31f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import android.content.Intent
-import com.android.systemui.animation.ActivityLaunchAnimator
-
-/** Fake implementation of [LaunchKeyguardQuickAffordanceUseCase], for tests. */
-class FakeLaunchKeyguardQuickAffordanceUseCase : LaunchKeyguardQuickAffordanceUseCase {
-
-    data class Invocation(
-        val intent: Intent,
-        val canShowWhileLocked: Boolean,
-        val animationController: ActivityLaunchAnimator.Controller?
-    )
-
-    private val _invocations = mutableListOf<Invocation>()
-    val invocations: List<Invocation> = _invocations
-
-    override fun invoke(
-        intent: Intent,
-        canShowWhileLocked: Boolean,
-        animationController: ActivityLaunchAnimator.Controller?
-    ) {
-        _invocations.add(
-            Invocation(
-                intent = intent,
-                canShowWhileLocked = canShowWhileLocked,
-                animationController = animationController,
-            )
-        )
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeObserveKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeObserveKeyguardQuickAffordanceUseCase.kt
deleted file mode 100644
index 8982752..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeObserveKeyguardQuickAffordanceUseCase.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.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-
-class FakeObserveKeyguardQuickAffordanceUseCase : ObserveKeyguardQuickAffordanceUseCase {
-
-    private val affordanceByPosition =
-        mutableMapOf<
-            KeyguardQuickAffordancePosition, MutableStateFlow<KeyguardQuickAffordanceModel>>()
-
-    init {
-        KeyguardQuickAffordancePosition.values().forEach { position ->
-            affordanceByPosition[position] = MutableStateFlow(KeyguardQuickAffordanceModel.Hidden)
-        }
-    }
-
-    override fun invoke(
-        position: KeyguardQuickAffordancePosition
-    ): Flow<KeyguardQuickAffordanceModel> {
-        return affordanceByPosition[position] ?: error("Flow unexpectedly missing!")
-    }
-
-    fun setModel(position: KeyguardQuickAffordancePosition, model: KeyguardQuickAffordanceModel) {
-        affordanceByPosition[position]?.value = model
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
new file mode 100644
index 0000000..c5e828e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.same
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
+
+    companion object {
+        private val INTENT = Intent("some.intent.action")
+        private val DRAWABLE = mock<ContainedDrawable>()
+        private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+
+        @Parameters(
+            name =
+                "needStrongAuthAfterBoot={0}, canShowWhileLocked={1}," +
+                    " keyguardIsUnlocked={2}, needsToUnlockFirst={3}, startActivity={4}"
+        )
+        @JvmStatic
+        fun data() =
+            listOf(
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ false,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ false,
+                    /* startActivity= */ false,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ false,
+                    /* startActivity= */ false,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ false,
+                    /* startActivity= */ false,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ false,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ false,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ false,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ false,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ true,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ false,
+                    /* startActivity= */ true,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ false,
+                    /* startActivity= */ true,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ false,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ false,
+                    /* startActivity= */ true,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ true,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ false,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ true,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ false,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ true,
+                ),
+                arrayOf(
+                    /* needStrongAuthAfterBoot= */ true,
+                    /* canShowWhileLocked= */ true,
+                    /* keyguardIsUnlocked= */ true,
+                    /* needsToUnlockFirst= */ true,
+                    /* startActivity= */ true,
+                ),
+            )
+    }
+
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+
+    private lateinit var underTest: KeyguardQuickAffordanceInteractor
+
+    @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
+    @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
+    @JvmField @Parameter(2) var keyguardIsUnlocked: Boolean = false
+    @JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
+    @JvmField @Parameter(4) var startActivity: Boolean = false
+    private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
+        underTest =
+            KeyguardQuickAffordanceInteractor(
+                keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()),
+                registry =
+                    FakeKeyguardQuickAffordanceRegistry(
+                        mapOf(
+                            KeyguardQuickAffordancePosition.BOTTOM_START to
+                                listOf(
+                                    homeControls,
+                                ),
+                            KeyguardQuickAffordancePosition.BOTTOM_END to
+                                listOf(
+                                    object : FakeKeyguardQuickAffordanceConfig() {},
+                                    object : FakeKeyguardQuickAffordanceConfig() {},
+                                ),
+                        ),
+                    ),
+                lockPatternUtils = lockPatternUtils,
+                keyguardStateController = keyguardStateController,
+                userTracker = userTracker,
+                activityStarter = activityStarter,
+            )
+    }
+
+    @Test
+    fun onQuickAffordanceClicked() = runBlockingTest {
+        setUpMocks(
+            needStrongAuthAfterBoot = needStrongAuthAfterBoot,
+            keyguardIsUnlocked = keyguardIsUnlocked,
+        )
+
+        homeControls.setState(
+            state =
+                KeyguardQuickAffordanceConfig.State.Visible(
+                    icon = DRAWABLE,
+                    contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+                )
+        )
+        homeControls.onClickedResult =
+            if (startActivity) {
+                KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+                    intent = INTENT,
+                    canShowWhileLocked = canShowWhileLocked,
+                )
+            } else {
+                KeyguardQuickAffordanceConfig.OnClickedResult.Handled
+            }
+
+        underTest.onQuickAffordanceClicked(
+            configKey = homeControls::class,
+            animationController = animationController,
+        )
+
+        if (startActivity) {
+            if (needsToUnlockFirst) {
+                verify(activityStarter)
+                    .postStartActivityDismissingKeyguard(
+                        any(),
+                        /* delay= */ eq(0),
+                        same(animationController),
+                    )
+            } else {
+                verify(activityStarter)
+                    .startActivity(
+                        any(),
+                        /* dismissShade= */ eq(true),
+                        same(animationController),
+                        /* showOverLockscreenWhenLocked= */ eq(true),
+                    )
+            }
+        } else {
+            verifyZeroInteractions(activityStarter)
+        }
+    }
+
+    private fun setUpMocks(
+        needStrongAuthAfterBoot: Boolean = true,
+        keyguardIsUnlocked: Boolean = false,
+    ) {
+        whenever(userTracker.userHandle).thenReturn(mock())
+        whenever(lockPatternUtils.getStrongAuthForUser(any()))
+            .thenReturn(
+                if (needStrongAuthAfterBoot) {
+                    LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+                } else {
+                    LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+                }
+            )
+        whenever(keyguardStateController.isUnlocked).thenReturn(keyguardIsUnlocked)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
similarity index 74%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseImplTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
index 63eb68f..d3fc29f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
@@ -17,14 +17,20 @@
 package com.android.systemui.keyguard.domain.usecase
 
 import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.containeddrawable.ContainedDrawable
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
 import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
@@ -34,33 +40,39 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(JUnit4::class)
-class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
+class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
 
-    private lateinit var underTest: ObserveKeyguardQuickAffordanceUseCase
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var activityStarter: ActivityStarter
+
+    private lateinit var underTest: KeyguardQuickAffordanceInteractor
 
     private lateinit var repository: FakeKeyguardRepository
-    private lateinit var isDozingUseCase: ObserveIsDozingUseCase
-    private lateinit var isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase
     private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
     private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
     private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
 
     @Before
-    fun setUp() = runBlockingTest {
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
         repository = FakeKeyguardRepository()
         repository.setKeyguardShowing(true)
-        isDozingUseCase = ObserveIsDozingUseCase(repository)
-        isKeyguardShowingUseCase = ObserveIsKeyguardShowingUseCase(repository)
 
         homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
         quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
         qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
 
         underTest =
-            ObserveKeyguardQuickAffordanceUseCaseImpl(
+            KeyguardQuickAffordanceInteractor(
+                keyguardInteractor = KeyguardInteractor(repository = repository),
                 registry =
                     FakeKeyguardQuickAffordanceRegistry(
                         mapOf(
@@ -75,13 +87,15 @@
                                 ),
                         ),
                     ),
-                isDozingUseCase = isDozingUseCase,
-                isKeyguardShowingUseCase = isKeyguardShowingUseCase,
+                lockPatternUtils = lockPatternUtils,
+                keyguardStateController = keyguardStateController,
+                userTracker = userTracker,
+                activityStarter = activityStarter,
             )
     }
 
     @Test
-    fun `invoke - bottom start affordance is visible`() = runBlockingTest {
+    fun `quickAffordance - bottom start affordance is visible`() = runBlockingTest {
         val configKey = homeControls::class
         homeControls.setState(
             KeyguardQuickAffordanceConfig.State.Visible(
@@ -92,7 +106,8 @@
 
         var latest: KeyguardQuickAffordanceModel? = null
         val job =
-            underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+            underTest
+                .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
                 .onEach { latest = it }
                 .launchIn(this)
 
@@ -106,7 +121,7 @@
     }
 
     @Test
-    fun `invoke - bottom end affordance is visible`() = runBlockingTest {
+    fun `quickAffordance - bottom end affordance is visible`() = runBlockingTest {
         val configKey = quickAccessWallet::class
         quickAccessWallet.setState(
             KeyguardQuickAffordanceConfig.State.Visible(
@@ -117,7 +132,8 @@
 
         var latest: KeyguardQuickAffordanceModel? = null
         val job =
-            underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
+            underTest
+                .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
                 .onEach { latest = it }
                 .launchIn(this)
 
@@ -131,7 +147,7 @@
     }
 
     @Test
-    fun `invoke - bottom start affordance hidden while dozing`() = runBlockingTest {
+    fun `quickAffordance - bottom start affordance hidden while dozing`() = runBlockingTest {
         repository.setDozing(true)
         homeControls.setState(
             KeyguardQuickAffordanceConfig.State.Visible(
@@ -142,7 +158,8 @@
 
         var latest: KeyguardQuickAffordanceModel? = null
         val job =
-            underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+            underTest
+                .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
                 .onEach { latest = it }
                 .launchIn(this)
         assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
@@ -150,7 +167,7 @@
     }
 
     @Test
-    fun `invoke - bottom start affordance hidden when lockscreen is not showing`() =
+    fun `quickAffordance - bottom start affordance hidden when lockscreen is not showing`() =
         runBlockingTest {
             repository.setKeyguardShowing(false)
             homeControls.setState(
@@ -162,7 +179,8 @@
 
             var latest: KeyguardQuickAffordanceModel? = null
             val job =
-                underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+                underTest
+                    .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
                     .onEach { latest = it }
                     .launchIn(this)
             assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt
deleted file mode 100644
index b3c1ae0..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import android.content.Intent
-import androidx.test.filters.SmallTest
-import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.junit.runners.Parameterized.Parameter
-import org.junit.runners.Parameterized.Parameters
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(Parameterized::class)
-class LaunchKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
-
-    companion object {
-        private val INTENT = Intent("some.intent.action")
-
-        @Parameters(
-            name =
-                "needStrongAuthAfterBoot={0}, canShowWhileLocked={1}," +
-                    " keyguardIsUnlocked={2}, needsToUnlockFirst={3}"
-        )
-        @JvmStatic
-        fun data() =
-            listOf(
-                arrayOf(
-                    /* needStrongAuthAfterBoot= */ false,
-                    /* canShowWhileLocked= */ false,
-                    /* keyguardIsUnlocked= */ false,
-                    /* needsToUnlockFirst= */ true,
-                ),
-                arrayOf(
-                    /* needStrongAuthAfterBoot= */ false,
-                    /* canShowWhileLocked= */ false,
-                    /* keyguardIsUnlocked= */ true,
-                    /* needsToUnlockFirst= */ false,
-                ),
-                arrayOf(
-                    /* needStrongAuthAfterBoot= */ false,
-                    /* canShowWhileLocked= */ true,
-                    /* keyguardIsUnlocked= */ false,
-                    /* needsToUnlockFirst= */ false,
-                ),
-                arrayOf(
-                    /* needStrongAuthAfterBoot= */ false,
-                    /* canShowWhileLocked= */ true,
-                    /* keyguardIsUnlocked= */ true,
-                    /* needsToUnlockFirst= */ false,
-                ),
-                arrayOf(
-                    /* needStrongAuthAfterBoot= */ true,
-                    /* canShowWhileLocked= */ false,
-                    /* keyguardIsUnlocked= */ false,
-                    /* needsToUnlockFirst= */ true,
-                ),
-                arrayOf(
-                    /* needStrongAuthAfterBoot= */ true,
-                    /* canShowWhileLocked= */ false,
-                    /* keyguardIsUnlocked= */ true,
-                    /* needsToUnlockFirst= */ true,
-                ),
-                arrayOf(
-                    /* needStrongAuthAfterBoot= */ true,
-                    /* canShowWhileLocked= */ true,
-                    /* keyguardIsUnlocked= */ false,
-                    /* needsToUnlockFirst= */ true,
-                ),
-                arrayOf(
-                    /* needStrongAuthAfterBoot= */ true,
-                    /* canShowWhileLocked= */ true,
-                    /* keyguardIsUnlocked= */ true,
-                    /* needsToUnlockFirst= */ true,
-                ),
-            )
-    }
-
-    @Mock private lateinit var lockPatternUtils: LockPatternUtils
-    @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var userTracker: UserTracker
-    @Mock private lateinit var activityStarter: ActivityStarter
-    @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
-
-    private lateinit var underTest: LaunchKeyguardQuickAffordanceUseCase
-
-    @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
-    @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
-    @JvmField @Parameter(2) var keyguardIsUnlocked: Boolean = false
-    @JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        underTest =
-            LaunchKeyguardQuickAffordanceUseCaseImpl(
-                lockPatternUtils = lockPatternUtils,
-                keyguardStateController = keyguardStateController,
-                userTracker = userTracker,
-                activityStarter = activityStarter,
-            )
-    }
-
-    @Test
-    fun invoke() {
-        setUpMocks(
-            needStrongAuthAfterBoot = needStrongAuthAfterBoot,
-            keyguardIsUnlocked = keyguardIsUnlocked,
-        )
-
-        underTest(
-            intent = INTENT,
-            canShowWhileLocked = canShowWhileLocked,
-            animationController = animationController,
-        )
-
-        if (needsToUnlockFirst) {
-            verify(activityStarter)
-                .postStartActivityDismissingKeyguard(
-                    INTENT,
-                    /* delay= */ 0,
-                    animationController,
-                )
-        } else {
-            verify(activityStarter)
-                .startActivity(
-                    INTENT,
-                    /* dismissShade= */ true,
-                    animationController,
-                    /* showOverLockscreenWhenLocked= */ true,
-                )
-        }
-    }
-
-    private fun setUpMocks(
-        needStrongAuthAfterBoot: Boolean = true,
-        keyguardIsUnlocked: Boolean = false,
-    ) {
-        whenever(userTracker.userHandle).thenReturn(mock())
-        whenever(lockPatternUtils.getStrongAuthForUser(any()))
-            .thenReturn(
-                if (needStrongAuthAfterBoot) {
-                    LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
-                } else {
-                    LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
-                }
-            )
-        whenever(keyguardStateController.isUnlocked).thenReturn(keyguardIsUnlocked)
-    }
-}
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 8758ce5..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
@@ -18,37 +18,40 @@
 
 import android.content.Intent
 import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.containeddrawable.ContainedDrawable
 import com.android.systemui.doze.util.BurnInHelperWrapper
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
 import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.domain.usecase.FakeLaunchKeyguardQuickAffordanceUseCase
-import com.android.systemui.keyguard.domain.usecase.FakeObserveKeyguardQuickAffordanceUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
-import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
 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
 import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.yield
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
@@ -58,17 +61,18 @@
 
     @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
     @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var activityStarter: ActivityStarter
 
     private lateinit var underTest: KeyguardBottomAreaViewModel
 
     private lateinit var repository: FakeKeyguardRepository
     private lateinit var registry: FakeKeyguardQuickAffordanceRegistry
-    private lateinit var isDozingUseCase: ObserveIsDozingUseCase
-    private lateinit var launchQuickAffordanceUseCase: FakeLaunchKeyguardQuickAffordanceUseCase
     private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
     private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
     private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
-    private lateinit var observeQuickAffordanceUseCase: FakeObserveKeyguardQuickAffordanceUseCase
 
     @Before
     fun setUp() {
@@ -94,63 +98,38 @@
                 ),
             )
         repository = FakeKeyguardRepository()
-        isDozingUseCase =
-            ObserveIsDozingUseCase(
-                repository = repository,
-            )
-        launchQuickAffordanceUseCase = FakeLaunchKeyguardQuickAffordanceUseCase()
-        observeQuickAffordanceUseCase = FakeObserveKeyguardQuickAffordanceUseCase()
 
+        val keyguardInteractor = KeyguardInteractor(repository = repository)
+        whenever(userTracker.userHandle).thenReturn(mock())
+        whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
+            .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
         underTest =
             KeyguardBottomAreaViewModel(
-                observeQuickAffordanceUseCase = observeQuickAffordanceUseCase,
-                onQuickAffordanceClickedUseCase =
-                    OnKeyguardQuickAffordanceClickedUseCase(
-                        registry =
-                            FakeKeyguardQuickAffordanceRegistry(
-                                mapOf(
-                                    KeyguardQuickAffordancePosition.BOTTOM_START to
-                                        listOf(
-                                            homeControlsQuickAffordanceConfig,
-                                        ),
-                                    KeyguardQuickAffordancePosition.BOTTOM_END to
-                                        listOf(
-                                            quickAccessWalletAffordanceConfig,
-                                            qrCodeScannerAffordanceConfig,
-                                        ),
-                                ),
-                            ),
-                        launchAffordanceUseCase = launchQuickAffordanceUseCase,
+                keyguardInteractor = keyguardInteractor,
+                quickAffordanceInteractor =
+                    KeyguardQuickAffordanceInteractor(
+                        keyguardInteractor = keyguardInteractor,
+                        registry = registry,
+                        lockPatternUtils = lockPatternUtils,
+                        keyguardStateController = keyguardStateController,
+                        userTracker = userTracker,
+                        activityStarter = activityStarter,
                     ),
-                observeBottomAreaAlphaUseCase =
-                    ObserveBottomAreaAlphaUseCase(
-                        repository = repository,
-                    ),
-                observeIsDozingUseCase = isDozingUseCase,
-                observeAnimateBottomAreaTransitionsUseCase =
-                    ObserveAnimateBottomAreaTransitionsUseCase(
-                        repository = repository,
-                    ),
-                observeDozeAmountUseCase =
-                    ObserveDozeAmountUseCase(
-                        repository = repository,
-                    ),
-                observeClockPositionUseCase =
-                    ObserveClockPositionUseCase(
-                        repository = repository,
-                    ),
+                bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
                 burnInHelperWrapper = burnInHelperWrapper,
             )
     }
 
     @Test
     fun `startButton - present - visible model - starts activity on click`() = runBlockingTest {
+        repository.setKeyguardShowing(true)
         var latest: KeyguardQuickAffordanceViewModel? = null
         val job = underTest.startButton.onEach { latest = it }.launchIn(this)
 
         val testConfig =
             TestConfig(
                 isVisible = true,
+                isClickable = true,
                 icon = mock(),
                 canShowWhileLocked = false,
                 intent = Intent("action"),
@@ -171,12 +150,14 @@
 
     @Test
     fun `endButton - present - visible model - do nothing on click`() = runBlockingTest {
+        repository.setKeyguardShowing(true)
         var latest: KeyguardQuickAffordanceViewModel? = null
         val job = underTest.endButton.onEach { latest = it }.launchIn(this)
 
         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.
@@ -220,11 +201,28 @@
 
     @Test
     fun animateButtonReveal() = runBlockingTest {
+        repository.setKeyguardShowing(true)
+        val testConfig =
+            TestConfig(
+                isVisible = true,
+                isClickable = true,
+                icon = mock(),
+                canShowWhileLocked = false,
+                intent = Intent("action"),
+            )
+
+        setUpQuickAffordanceModel(
+            position = KeyguardQuickAffordancePosition.BOTTOM_START,
+            testConfig = testConfig,
+        )
+
         val values = mutableListOf<Boolean>()
-        val job = underTest.animateButtonReveal.onEach(values::add).launchIn(this)
+        val job = underTest.startButton.onEach { values.add(it.animateReveal) }.launchIn(this)
 
         repository.setAnimateDozingTransitions(true)
+        yield()
         repository.setAnimateDozingTransitions(false)
+        yield()
 
         assertThat(values).isEqualTo(listOf(false, true, false))
         job.cancel()
@@ -267,6 +265,7 @@
             testConfig =
                 TestConfig(
                     isVisible = true,
+                    isClickable = true,
                     icon = mock(),
                     canShowWhileLocked = true,
                 )
@@ -276,6 +275,7 @@
             testConfig =
                 TestConfig(
                     isVisible = true,
+                    isClickable = true,
                     icon = mock(),
                     canShowWhileLocked = false,
                 )
@@ -349,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)
@@ -357,7 +480,7 @@
     private suspend fun setUpQuickAffordanceModel(
         position: KeyguardQuickAffordancePosition,
         testConfig: TestConfig,
-    ): KClass<*> {
+    ): KClass<out FakeKeyguardQuickAffordanceConfig> {
         val config =
             when (position) {
                 KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
@@ -381,23 +504,17 @@
                 KeyguardQuickAffordanceConfig.State.Hidden
             }
         config.setState(state)
-
-        val configKey = config::class
-        observeQuickAffordanceUseCase.setModel(
-            position,
-            KeyguardQuickAffordanceModel.from(state, configKey)
-        )
-
-        return configKey
+        return config::class
     }
 
     private fun assertQuickAffordanceViewModel(
         viewModel: KeyguardQuickAffordanceViewModel?,
         testConfig: TestConfig,
-        configKey: KClass<*>,
+        configKey: KClass<out FakeKeyguardQuickAffordanceConfig>,
     ) {
         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(
@@ -406,19 +523,11 @@
                     animationController = animationController,
                 )
             )
-            testConfig.intent?.let { intent ->
-                assertThat(launchQuickAffordanceUseCase.invocations)
-                    .isEqualTo(
-                        listOf(
-                            FakeLaunchKeyguardQuickAffordanceUseCase.Invocation(
-                                intent = intent,
-                                canShowWhileLocked = testConfig.canShowWhileLocked,
-                                animationController = animationController,
-                            )
-                        )
-                    )
+            if (testConfig.intent != null) {
+                assertThat(Mockito.mockingDetails(activityStarter).invocations).hasSize(1)
+            } else {
+                verifyZeroInteractions(activityStarter)
             }
-                ?: run { assertThat(launchQuickAffordanceUseCase.invocations).isEmpty() }
         } else {
             assertThat(viewModel.isVisible).isFalse()
         }
@@ -426,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/log/LogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
index 56aff3c..7b12eb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
@@ -41,6 +41,18 @@
     }
 
     @Test
+    fun log_shouldSaveLogToBufferWithException() {
+        val exception = createTestException("Some exception test message", "SomeExceptionTestClass")
+        buffer.log("Test", LogLevel.INFO, "Some test message", exception)
+
+        val dumpedString = dumpBuffer()
+
+        assertThat(dumpedString).contains("Some test message")
+        assertThat(dumpedString).contains("Some exception test message")
+        assertThat(dumpedString).contains("SomeExceptionTestClass")
+    }
+
+    @Test
     fun log_shouldRotateIfLogBufferIsFull() {
         buffer.log("Test", LogLevel.INFO, "This should be rotated")
         buffer.log("Test", LogLevel.INFO, "New test message")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
index fcfef4a4..c41fac7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
@@ -16,19 +16,22 @@
 
 package com.android.systemui.media
 
+import android.provider.Settings
 import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
 import android.view.View.GONE
 import android.view.View.VISIBLE
 import android.widget.FrameLayout
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.stack.MediaContainerView
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.animation.UniqueObjectHostView
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.utils.os.FakeHandler
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertTrue
 import org.junit.Before
@@ -37,11 +40,12 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
 class KeyguardMediaControllerTest : SysuiTestCase() {
 
     @Mock
@@ -53,31 +57,33 @@
     @Mock
     private lateinit var configurationController: ConfigurationController
 
-    @Mock
-    private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager
     @JvmField @Rule
     val mockito = MockitoJUnit.rule()
 
     private val mediaContainerView: MediaContainerView = MediaContainerView(context, null)
     private val hostView = UniqueObjectHostView(context)
+    private val settings = FakeSettings()
     private lateinit var keyguardMediaController: KeyguardMediaController
+    private lateinit var testableLooper: TestableLooper
+    private lateinit var fakeHandler: FakeHandler
 
     @Before
     fun setup() {
         // default state is positive, media should show up
         whenever(mediaHost.visible).thenReturn(true)
         whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
-        whenever(notificationLockscreenUserManager.shouldShowLockscreenNotifications())
-                .thenReturn(true)
         whenever(mediaHost.hostView).thenReturn(hostView)
         hostView.layoutParams = FrameLayout.LayoutParams(100, 100)
+        testableLooper = TestableLooper.get(this)
+        fakeHandler = FakeHandler(testableLooper.looper)
         keyguardMediaController = KeyguardMediaController(
             mediaHost,
             bypassController,
             statusBarStateController,
-            notificationLockscreenUserManager,
             context,
-            configurationController
+            settings,
+            fakeHandler,
+            configurationController,
         )
         keyguardMediaController.attachSinglePaneContainer(mediaContainerView)
         keyguardMediaController.useSplitShade = false
@@ -106,9 +112,8 @@
     }
 
     @Test
-    fun testHiddenOnKeyguard_whenNotificationsAreHidden() {
-        whenever(notificationLockscreenUserManager.shouldShowLockscreenNotifications())
-                .thenReturn(false)
+    fun testHiddenOnKeyguard_whenMediaOnLockScreenDisabled() {
+        settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 0)
 
         keyguardMediaController.refreshMediaPosition()
 
@@ -116,6 +121,15 @@
     }
 
     @Test
+    fun testAvailableOnKeyguard_whenMediaOnLockScreenEnabled() {
+        settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 1)
+
+        keyguardMediaController.refreshMediaPosition()
+
+        assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE)
+    }
+
+    @Test
     fun testActivatesSplitShadeContainerInSplitShadeMode() {
         val splitShadeContainer = FrameLayout(context)
         keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
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/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index d65b6b3..18bfd04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -16,8 +16,8 @@
 
 package com.android.systemui.media
 
-import org.mockito.Mockito.`when` as whenever
 import android.graphics.Rect
+import android.provider.Settings
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.ViewGroup
@@ -30,7 +30,7 @@
 import com.android.systemui.dreams.DreamOverlayStateController
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.shade.testing.FakeNotifPanelEvents
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.phone.KeyguardBypassController
@@ -38,6 +38,8 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.animation.UniqueObjectHostView
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.utils.os.FakeHandler
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertNotNull
 import org.junit.Before
@@ -50,10 +52,10 @@
 import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
 
 @SmallTest
@@ -61,32 +63,18 @@
 @TestableLooper.RunWithLooper
 class MediaHierarchyManagerTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var lockHost: MediaHost
-    @Mock
-    private lateinit var qsHost: MediaHost
-    @Mock
-    private lateinit var qqsHost: MediaHost
-    @Mock
-    private lateinit var bypassController: KeyguardBypassController
-    @Mock
-    private lateinit var keyguardStateController: KeyguardStateController
-    @Mock
-    private lateinit var statusBarStateController: SysuiStatusBarStateController
-    @Mock
-    private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager
-    @Mock
-    private lateinit var mediaCarouselController: MediaCarouselController
-    @Mock
-    private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler
-    @Mock
-    private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
-    @Mock
-    private lateinit var keyguardViewController: KeyguardViewController
-    @Mock
-    private lateinit var uniqueObjectHostView: UniqueObjectHostView
-    @Mock
-    private lateinit var dreamOverlayStateController: DreamOverlayStateController
+    @Mock private lateinit var lockHost: MediaHost
+    @Mock private lateinit var qsHost: MediaHost
+    @Mock private lateinit var qqsHost: MediaHost
+    @Mock private lateinit var bypassController: KeyguardBypassController
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+    @Mock private lateinit var mediaCarouselController: MediaCarouselController
+    @Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler
+    @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+    @Mock private lateinit var keyguardViewController: KeyguardViewController
+    @Mock private lateinit var uniqueObjectHostView: UniqueObjectHostView
+    @Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController
     @Captor
     private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
     @Captor
@@ -94,34 +82,42 @@
     @JvmField
     @Rule
     val mockito = MockitoJUnit.rule()
-    private lateinit var mediaHiearchyManager: MediaHierarchyManager
+    private lateinit var mediaHierarchyManager: MediaHierarchyManager
     private lateinit var mediaFrame: ViewGroup
     private val configurationController = FakeConfigurationController()
+    private val notifPanelEvents = FakeNotifPanelEvents()
+    private val settings = FakeSettings()
+    private lateinit var testableLooper: TestableLooper
+    private lateinit var fakeHandler: FakeHandler
 
     @Before
     fun setup() {
         context.getOrCreateTestableResources().addOverride(
                 R.bool.config_use_split_notification_shade, false)
         mediaFrame = FrameLayout(context)
-        `when`(mediaCarouselController.mediaFrame).thenReturn(mediaFrame)
-        mediaHiearchyManager = MediaHierarchyManager(
+        testableLooper = TestableLooper.get(this)
+        fakeHandler = FakeHandler(testableLooper.looper)
+        whenever(mediaCarouselController.mediaFrame).thenReturn(mediaFrame)
+        mediaHierarchyManager = MediaHierarchyManager(
                 context,
                 statusBarStateController,
                 keyguardStateController,
                 bypassController,
                 mediaCarouselController,
-                notificationLockscreenUserManager,
+                keyguardViewController,
+                dreamOverlayStateController,
                 configurationController,
                 wakefulnessLifecycle,
-                keyguardViewController,
-                dreamOverlayStateController)
+                notifPanelEvents,
+                settings,
+                fakeHandler,)
         verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
         verify(statusBarStateController).addCallback(statusBarCallback.capture())
         setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN, LOCKSCREEN_TOP)
         setupHost(qsHost, MediaHierarchyManager.LOCATION_QS, QS_TOP)
         setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS, QQS_TOP)
-        `when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
-        `when`(mediaCarouselController.mediaCarouselScrollHandler)
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+        whenever(mediaCarouselController.mediaCarouselScrollHandler)
                 .thenReturn(mediaCarouselScrollHandler)
         val observer = wakefullnessObserver.value
         assertNotNull("lifecycle observer wasn't registered", observer)
@@ -131,30 +127,30 @@
     }
 
     private fun setupHost(host: MediaHost, location: Int, top: Int) {
-        `when`(host.location).thenReturn(location)
-        `when`(host.currentBounds).thenReturn(Rect(0, top, 0, top))
-        `when`(host.hostView).thenReturn(uniqueObjectHostView)
-        `when`(host.visible).thenReturn(true)
-        mediaHiearchyManager.register(host)
+        whenever(host.location).thenReturn(location)
+        whenever(host.currentBounds).thenReturn(Rect(0, top, 0, top))
+        whenever(host.hostView).thenReturn(uniqueObjectHostView)
+        whenever(host.visible).thenReturn(true)
+        mediaHierarchyManager.register(host)
     }
 
     @Test
     fun testHostViewSetOnRegister() {
-        val host = mediaHiearchyManager.register(lockHost)
+        val host = mediaHierarchyManager.register(lockHost)
         verify(lockHost).hostView = eq(host)
     }
 
     @Test
     fun testBlockedWhenScreenTurningOff() {
         // Let's set it onto QS:
-        mediaHiearchyManager.qsExpansion = 1.0f
+        mediaHierarchyManager.qsExpansion = 1.0f
         verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
                 any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
         val observer = wakefullnessObserver.value
         assertNotNull("lifecycle observer wasn't registered", observer)
         observer.onStartedGoingToSleep()
         clearInvocations(mediaCarouselController)
-        mediaHiearchyManager.qsExpansion = 0.0f
+        mediaHierarchyManager.qsExpansion = 0.0f
         verify(mediaCarouselController, times(0))
                 .onDesiredLocationChanged(ArgumentMatchers.anyInt(),
                 any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
@@ -163,13 +159,13 @@
     @Test
     fun testAllowedWhenNotTurningOff() {
         // Let's set it onto QS:
-        mediaHiearchyManager.qsExpansion = 1.0f
+        mediaHierarchyManager.qsExpansion = 1.0f
         verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
                 any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
         val observer = wakefullnessObserver.value
         assertNotNull("lifecycle observer wasn't registered", observer)
         clearInvocations(mediaCarouselController)
-        mediaHiearchyManager.qsExpansion = 0.0f
+        mediaHierarchyManager.qsExpansion = 0.0f
         verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
                 any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
     }
@@ -179,7 +175,7 @@
         goToLockscreen()
 
         // Let's transition all the way to full shade
-        mediaHiearchyManager.setTransitionToFullShadeAmount(100000f)
+        mediaHierarchyManager.setTransitionToFullShadeAmount(100000f)
         verify(mediaCarouselController).onDesiredLocationChanged(
             eq(MediaHierarchyManager.LOCATION_QQS),
             any(MediaHostState::class.java),
@@ -189,7 +185,7 @@
         clearInvocations(mediaCarouselController)
 
         // Let's go back to the lock screen
-        mediaHiearchyManager.setTransitionToFullShadeAmount(0.0f)
+        mediaHierarchyManager.setTransitionToFullShadeAmount(0.0f)
         verify(mediaCarouselController).onDesiredLocationChanged(
             eq(MediaHierarchyManager.LOCATION_LOCKSCREEN),
             any(MediaHostState::class.java),
@@ -198,7 +194,7 @@
             anyLong())
 
         // Let's make sure alpha is set
-        mediaHiearchyManager.setTransitionToFullShadeAmount(2.0f)
+        mediaHierarchyManager.setTransitionToFullShadeAmount(2.0f)
         assertThat(mediaFrame.alpha).isNotEqualTo(1.0f)
     }
 
@@ -207,7 +203,26 @@
         goToLockscreen()
         expandQS()
 
-        val transformType = mediaHiearchyManager.calculateTransformationType()
+        val transformType = mediaHierarchyManager.calculateTransformationType()
+        assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
+    }
+
+    @Test
+    fun calculateTransformationType_notOnLockscreen_returnsTransition() {
+        expandQS()
+
+        val transformType = mediaHierarchyManager.calculateTransformationType()
+
+        assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION)
+    }
+
+    @Test
+    fun calculateTransformationType_onLockscreen_returnsTransition() {
+        goToLockscreen()
+        expandQS()
+
+        val transformType = mediaHierarchyManager.calculateTransformationType()
+
         assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
     }
 
@@ -216,9 +231,9 @@
         enableSplitShade()
         goToLockscreen()
         expandQS()
-        mediaHiearchyManager.setTransitionToFullShadeAmount(10000f)
+        mediaHierarchyManager.setTransitionToFullShadeAmount(10000f)
 
-        val transformType = mediaHiearchyManager.calculateTransformationType()
+        val transformType = mediaHierarchyManager.calculateTransformationType()
         assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION)
     }
 
@@ -228,9 +243,9 @@
         goToLockscreen()
         expandQS()
         whenever(lockHost.visible).thenReturn(false)
-        mediaHiearchyManager.setTransitionToFullShadeAmount(10000f)
+        mediaHierarchyManager.setTransitionToFullShadeAmount(10000f)
 
-        val transformType = mediaHiearchyManager.calculateTransformationType()
+        val transformType = mediaHierarchyManager.calculateTransformationType()
         assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
     }
 
@@ -240,9 +255,9 @@
         goToLockscreen()
         goToLockedShade()
         expandQS()
-        mediaHiearchyManager.setTransitionToFullShadeAmount(0f)
+        mediaHierarchyManager.setTransitionToFullShadeAmount(0f)
 
-        val transformType = mediaHiearchyManager.calculateTransformationType()
+        val transformType = mediaHierarchyManager.calculateTransformationType()
         assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
     }
 
@@ -251,13 +266,13 @@
         goToLockscreen()
         goToLockedShade()
 
-        val transformType = mediaHiearchyManager.calculateTransformationType()
+        val transformType = mediaHierarchyManager.calculateTransformationType()
         assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
     }
 
     @Test
     fun testCloseGutsRelayToCarousel() {
-        mediaHiearchyManager.closeGuts()
+        mediaHierarchyManager.closeGuts()
 
         verify(mediaCarouselController).closeGuts()
     }
@@ -271,7 +286,7 @@
 
     @Test
     fun getGuidedTransformationTranslationY_notInGuidedTransformation_returnsNegativeNumber() {
-        assertThat(mediaHiearchyManager.getGuidedTransformationTranslationY()).isLessThan(0)
+        assertThat(mediaHierarchyManager.getGuidedTransformationTranslationY()).isLessThan(0)
     }
 
     @Test
@@ -279,7 +294,7 @@
         enterGuidedTransformation()
 
         val expectedTranslation = LOCKSCREEN_TOP - QS_TOP
-        assertThat(mediaHiearchyManager.getGuidedTransformationTranslationY())
+        assertThat(mediaHierarchyManager.getGuidedTransformationTranslationY())
                 .isEqualTo(expectedTranslation)
     }
 
@@ -291,7 +306,19 @@
         whenever(qsHost.visible).thenReturn(true)
         whenever(qqsHost.visible).thenReturn(true)
 
-        assertThat(mediaHiearchyManager.isCurrentlyInGuidedTransformation()).isTrue()
+        assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isTrue()
+    }
+
+    @Test
+    fun isCurrentlyInGuidedTransformation_hostsVisible_expandImmediateEnabled_returnsFalse() {
+        notifPanelEvents.changeExpandImmediate(expandImmediate = true)
+        goToLockscreen()
+        enterGuidedTransformation()
+        whenever(lockHost.visible).thenReturn(true)
+        whenever(qsHost.visible).thenReturn(true)
+        whenever(qqsHost.visible).thenReturn(true)
+
+        assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isFalse()
     }
 
     @Test
@@ -302,7 +329,7 @@
         whenever(qsHost.visible).thenReturn(true)
         whenever(qqsHost.visible).thenReturn(true)
 
-        assertThat(mediaHiearchyManager.isCurrentlyInGuidedTransformation()).isFalse()
+        assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isFalse()
     }
 
     private fun enableSplitShade() {
@@ -314,9 +341,7 @@
 
     private fun goToLockscreen() {
         whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
-        whenever(notificationLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(
-            true
-        )
+        settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 1)
         statusBarCallback.value.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD)
         clearInvocations(mediaCarouselController)
     }
@@ -330,13 +355,13 @@
     }
 
     private fun expandQS() {
-        mediaHiearchyManager.qsExpansion = 1.0f
+        mediaHierarchyManager.qsExpansion = 1.0f
     }
 
     private fun enterGuidedTransformation() {
-        mediaHiearchyManager.qsExpansion = 1.0f
+        mediaHierarchyManager.qsExpansion = 1.0f
         goToLockscreen()
-        mediaHiearchyManager.setTransitionToFullShadeAmount(123f)
+        mediaHierarchyManager.setTransitionToFullShadeAmount(123f)
     }
 
     companion object {
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/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 314997d..6173692 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -24,9 +24,9 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.KeyguardManager;
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
 import android.media.session.MediaController;
 import android.media.session.MediaSessionManager;
@@ -88,6 +88,7 @@
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
     private final AudioManager mAudioManager = mock(AudioManager.class);
     private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
+    private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
 
     private List<MediaController> mMediaControllers = new ArrayList<>();
     private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
@@ -119,7 +120,8 @@
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mKeyguardManager);
         mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
                 mMediaOutputController);
         mMediaOutputBaseDialogImpl.onCreate(new Bundle());
@@ -276,7 +278,7 @@
         }
 
         @Override
-        Drawable getAppSourceIcon() {
+        IconCompat getAppSourceIcon() {
             return null;
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 751c895..6dcf802 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -31,6 +31,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.KeyguardManager;
 import android.app.Notification;
 import android.content.Context;
 import android.graphics.drawable.Icon;
@@ -47,6 +48,7 @@
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.text.TextUtils;
+import android.view.View;
 
 import androidx.core.graphics.drawable.IconCompat;
 import androidx.test.filters.SmallTest;
@@ -57,6 +59,7 @@
 import com.android.settingslib.media.MediaDevice;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.plugins.ActivityStarter;
@@ -102,11 +105,16 @@
     private RoutingSessionInfo mRemoteSessionInfo = mock(RoutingSessionInfo.class);
     private ActivityStarter mStarter = mock(ActivityStarter.class);
     private AudioManager mAudioManager = mock(AudioManager.class);
+    private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
     private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
     private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+    private final ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController = mock(
+            ActivityLaunchAnimator.Controller.class);
     private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
             NearbyMediaDevicesManager.class);
+    private View mDialogLaunchView = mock(View.class);
+    private MediaOutputController.Callback mCallback = mock(MediaOutputController.Callback.class);
 
     private Context mSpyContext;
     private MediaOutputController mMediaOutputController;
@@ -131,7 +139,8 @@
         mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mKeyguardManager);
         mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         MediaDescription.Builder builder = new MediaDescription.Builder();
@@ -183,7 +192,8 @@
         mMediaOutputController = new MediaOutputController(mSpyContext, null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mKeyguardManager);
 
         mMediaOutputController.start(mCb);
 
@@ -212,7 +222,8 @@
         mMediaOutputController = new MediaOutputController(mSpyContext, null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mKeyguardManager);
 
         mMediaOutputController.start(mCb);
 
@@ -461,7 +472,8 @@
         mMediaOutputController = new MediaOutputController(mSpyContext, null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mKeyguardManager);
 
         assertThat(mMediaOutputController.getNotificationIcon()).isNull();
     }
@@ -557,4 +569,16 @@
         verify(mPowerExemptionManager).addToTemporaryAllowList(anyString(), anyInt(), anyString(),
                 anyLong());
     }
+
+    @Test
+    public void launchBluetoothPairing_isKeyguardLocked_dismissDialog() {
+        when(mDialogLaunchAnimator.createActivityLaunchController(mDialogLaunchView)).thenReturn(
+                mActivityLaunchAnimatorController);
+        when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
+        mMediaOutputController.mCallback = this.mCallback;
+
+        mMediaOutputController.launchBluetoothPairing(mDialogLaunchView);
+
+        verify(mCallback).dismissDialog();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 4779d32..9557513 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.KeyguardManager;
 import android.media.AudioManager;
 import android.media.MediaRoute2Info;
 import android.media.session.MediaController;
@@ -85,6 +86,7 @@
             NearbyMediaDevicesManager.class);
     private final AudioManager mAudioManager = mock(AudioManager.class);
     private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
+    private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
 
     private List<MediaController> mMediaControllers = new ArrayList<>();
     private MediaOutputDialog mMediaOutputDialog;
@@ -104,7 +106,8 @@
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mKeyguardManager);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
                 mMediaOutputController, mUiEventLogger);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
index 247316a..c101b9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.media.dream;
 
+import static com.android.systemui.flags.Flags.MEDIA_DREAM_COMPLICATION;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
@@ -28,6 +30,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.MediaData;
 import com.android.systemui.media.MediaDataManager;
 
@@ -50,6 +53,9 @@
     @Mock
     MediaDreamComplication mComplication;
 
+    @Mock
+    FeatureFlags mFeatureFlags;
+
     final String mKey = "key";
     final String mOldKey = "old_key";
 
@@ -59,21 +65,18 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+
+        when(mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)).thenReturn(true);
     }
 
     @Test
     public void testComplicationAddition() {
         final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager,
-                mDreamOverlayStateController, mComplication);
+                mDreamOverlayStateController, mComplication, mFeatureFlags);
 
         sentinel.start();
 
-        ArgumentCaptor<MediaDataManager.Listener> listenerCaptor =
-                ArgumentCaptor.forClass(MediaDataManager.Listener.class);
-        verify(mMediaDataManager).addListener(listenerCaptor.capture());
-
-        final MediaDataManager.Listener listener = listenerCaptor.getValue();
-
+        final MediaDataManager.Listener listener = captureMediaDataListener();
         when(mMediaDataManager.hasActiveMedia()).thenReturn(false);
         listener.onMediaDataLoaded(mKey, mOldKey, mData, /* immediately= */ true,
                 /* receivedSmartspaceCardLatency= */ 0, /* isSsReactived= */ false);
@@ -92,4 +95,27 @@
         verify(mDreamOverlayStateController).removeComplication(eq(mComplication));
     }
 
+    @Test
+    public void testMediaDreamSentinel_mediaComplicationDisabled_doNotAddComplication() {
+        when(mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)).thenReturn(false);
+
+        final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager,
+                mDreamOverlayStateController, mComplication, mFeatureFlags);
+
+        sentinel.start();
+
+        final MediaDataManager.Listener listener = captureMediaDataListener();
+        when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
+        listener.onMediaDataLoaded(mKey, mOldKey, mData, /* immediately= */true,
+                /* receivedSmartspaceCardLatency= */0, /* isSsReactived= */ false);
+        verify(mDreamOverlayStateController, never()).addComplication(any());
+    }
+
+    private MediaDataManager.Listener captureMediaDataListener() {
+        final ArgumentCaptor<MediaDataManager.Listener> listenerCaptor =
+                ArgumentCaptor.forClass(MediaDataManager.Listener.class);
+        verify(mMediaDataManager).addListener(listenerCaptor.capture());
+
+        return listenerCaptor.getValue();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index dbc5f7c..171d893 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -241,5 +241,5 @@
 
 private val routeInfo = MediaRoute2Info.Builder("id", "Test route name")
     .addFeature("feature")
-    .setPackageName(PACKAGE_NAME)
+    .setClientPackageName(PACKAGE_NAME)
     .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index cd8ee73..1061e3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -686,5 +686,5 @@
 
 private val routeInfo = MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME)
     .addFeature("feature")
-    .setPackageName(PACKAGE_NAME)
+    .setClientPackageName(PACKAGE_NAME)
     .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 1f28210..e4f47fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.qs
 
 import android.content.res.Configuration
-import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.SysuiTestCase
@@ -38,38 +38,32 @@
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.any
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class QuickQSPanelControllerTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var quickQSPanel: QuickQSPanel
-    @Mock
-    private lateinit var qsTileHost: QSTileHost
-    @Mock
-    private lateinit var qsCustomizerController: QSCustomizerController
-    @Mock
-    private lateinit var mediaHost: MediaHost
-    @Mock
-    private lateinit var metricsLogger: MetricsLogger
+    @Mock private lateinit var quickQSPanel: QuickQSPanel
+    @Mock private lateinit var qsTileHost: QSTileHost
+    @Mock private lateinit var qsCustomizerController: QSCustomizerController
+    @Mock private lateinit var mediaHost: MediaHost
+    @Mock private lateinit var metricsLogger: MetricsLogger
+    @Mock private lateinit var qsLogger: QSLogger
+    @Mock private lateinit var tile: QSTile
+    @Mock private lateinit var tileLayout: TileLayout
+    @Mock private lateinit var tileView: QSTileView
+    @Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
+
     private val uiEventLogger = UiEventLoggerFake()
-    @Mock
-    private lateinit var qsLogger: QSLogger
     private val dumpManager = DumpManager()
-    @Mock
-    private lateinit var tile: QSTile
-    @Mock
-    private lateinit var tileLayout: TileLayout
-    @Mock
-    private lateinit var tileView: QSTileView
-    @Captor
-    private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
+
+    private var usingCollapsedLandscapeMedia = true
 
     private lateinit var controller: TestQuickQSPanelController
 
@@ -77,24 +71,24 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        `when`(quickQSPanel.tileLayout).thenReturn(tileLayout)
-        `when`(quickQSPanel.isAttachedToWindow).thenReturn(true)
-        `when`(quickQSPanel.dumpableTag).thenReturn("")
-        `when`(quickQSPanel.resources).thenReturn(mContext.resources)
-        `when`(qsTileHost.createTileView(any(), any(), anyBoolean())).thenReturn(tileView)
+        whenever(quickQSPanel.tileLayout).thenReturn(tileLayout)
+        whenever(quickQSPanel.isAttachedToWindow).thenReturn(true)
+        whenever(quickQSPanel.dumpableTag).thenReturn("")
+        whenever(quickQSPanel.resources).thenReturn(mContext.resources)
+        whenever(qsTileHost.createTileView(any(), any(), anyBoolean())).thenReturn(tileView)
 
-        controller = TestQuickQSPanelController(
+        controller =
+            TestQuickQSPanelController(
                 quickQSPanel,
                 qsTileHost,
                 qsCustomizerController,
-                false,
+                /* usingMediaPlayer = */ false,
                 mediaHost,
-                true,
+                { usingCollapsedLandscapeMedia },
                 metricsLogger,
                 uiEventLogger,
                 qsLogger,
-                dumpManager
-        )
+                dumpManager)
 
         controller.init()
     }
@@ -106,9 +100,9 @@
 
     @Test
     fun testTileSublistWithFewerTiles_noCrash() {
-        `when`(quickQSPanel.numQuickTiles).thenReturn(3)
+        whenever(quickQSPanel.numQuickTiles).thenReturn(3)
 
-        `when`(qsTileHost.tiles).thenReturn(listOf(tile, tile))
+        whenever(qsTileHost.tiles).thenReturn(listOf(tile, tile))
 
         controller.setTiles()
     }
@@ -116,8 +110,8 @@
     @Test
     fun testTileSublistWithTooManyTiles() {
         val limit = 3
-        `when`(quickQSPanel.numQuickTiles).thenReturn(limit)
-        `when`(qsTileHost.tiles).thenReturn(listOf(tile, tile, tile, tile))
+        whenever(quickQSPanel.numQuickTiles).thenReturn(limit)
+        whenever(qsTileHost.tiles).thenReturn(listOf(tile, tile, tile, tile))
 
         controller.setTiles()
 
@@ -125,39 +119,61 @@
     }
 
     @Test
-    fun testMediaExpansionUpdatedWhenConfigurationChanged() {
+    fun mediaExpansion_afterConfigChange_inLandscape_collapsedInLandscapeTrue_updatesToCollapsed() {
         // times(2) because both controller and base controller are registering their listeners
         verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture())
 
         // verify that media starts in the expanded state by default
         verify(mediaHost).expansion = MediaHostState.EXPANDED
 
-        // Rotate device, verify media size updated
+        // Rotate device, verify media size updated to collapsed
+        usingCollapsedLandscapeMedia = true
         controller.setRotation(RotationUtils.ROTATION_LANDSCAPE)
         captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) }
 
         verify(mediaHost).expansion = MediaHostState.COLLAPSED
     }
 
+    @Test
+    fun mediaExpansion_afterConfigChange_landscape_collapsedInLandscapeFalse_remainsExpanded() {
+        // times(2) because both controller and base controller are registering their listeners
+        verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture())
+        reset(mediaHost)
+
+        usingCollapsedLandscapeMedia = false
+        controller.setRotation(RotationUtils.ROTATION_LANDSCAPE)
+        captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) }
+
+        verify(mediaHost).expansion = MediaHostState.EXPANDED
+    }
+
     class TestQuickQSPanelController(
         view: QuickQSPanel,
         qsTileHost: QSTileHost,
         qsCustomizerController: QSCustomizerController,
         usingMediaPlayer: Boolean,
         mediaHost: MediaHost,
-        usingCollapsedLandscapeMedia: Boolean,
+        usingCollapsedLandscapeMedia: () -> Boolean,
         metricsLogger: MetricsLogger,
         uiEventLogger: UiEventLoggerFake,
         qsLogger: QSLogger,
         dumpManager: DumpManager
-    ) : QuickQSPanelController(view, qsTileHost, qsCustomizerController, usingMediaPlayer,
-        mediaHost, usingCollapsedLandscapeMedia, metricsLogger, uiEventLogger, qsLogger,
-        dumpManager) {
+    ) :
+        QuickQSPanelController(
+            view,
+            qsTileHost,
+            qsCustomizerController,
+            usingMediaPlayer,
+            mediaHost,
+            usingCollapsedLandscapeMedia,
+            metricsLogger,
+            uiEventLogger,
+            qsLogger,
+            dumpManager) {
 
         private var rotation = RotationUtils.ROTATION_NONE
 
-        @Override
-        override fun getRotation(): Int = rotation
+        @Override override fun getRotation(): Int = rotation
 
         fun setRotation(newRotation: Int) {
             rotation = newRotation
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 002f23a..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,85 +22,253 @@
 import android.graphics.Insets
 import android.graphics.Rect
 import android.hardware.HardwareBuffer
-import android.net.Uri
-import android.view.WindowManager
-import android.view.WindowManager.ScreenshotSource
+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_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() {
-        val request = ScreenshotRequest(ScreenshotSource.SCREENSHOT_KEY_CHORD)
-        val onSavedListener = mock<Consumer<Uri>>()
-        val callback = mock<RequestCallback>()
-        val processor = RequestProcessor(controller)
+    fun testProcessAsync() {
+        flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
 
-        processor.processRequest(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, onSavedListener,
-            request, callback)
+        val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+        val processor = RequestProcessor(imageCapture, policy, flags, scope)
 
-        verify(controller).takeScreenshotFullscreen(/* topComponent */ isNull(),
-            eq(onSavedListener), eq(callback))
+        var result: ScreenshotRequest? = null
+        var callbackCount = 0
+        val callback: (ScreenshotRequest) -> Unit = { processedRequest: ScreenshotRequest ->
+            result = processedRequest
+            callbackCount++
+        }
+
+        // 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() {
-        val request = ScreenshotRequest(ScreenshotSource.SCREENSHOT_KEY_CHORD)
-        val onSavedListener = mock<Consumer<Uri>>()
-        val callback = mock<RequestCallback>()
-        val processor = RequestProcessor(controller)
+    fun testFullScreenshot_workProfilePolicyDisabled() = runBlocking {
+        flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
 
-        processor.processRequest(WindowManager.TAKE_SCREENSHOT_SELECTED_REGION, onSavedListener,
-            request, callback)
+        val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+        val processor = RequestProcessor(imageCapture, policy, flags, scope)
 
-        verify(controller).takeScreenshotPartial(/* topComponent */ isNull(),
-            eq(onSavedListener), eq(callback))
+        val processedRequest = processor.process(request)
+
+        // No changes
+        assertThat(processedRequest).isEqualTo(request)
     }
 
     @Test
-    fun testProvidedImageScreenshot() {
-        val taskId = 1111
-        val userId = 2222
+    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 processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+        val processedRequest = processor.process(request)
+
+        // 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 testSelectedRegionScreenshot_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
+
+        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 topComponent = ComponentName("test", "test")
-        val processor = RequestProcessor(controller)
+        val processor = RequestProcessor(imageCapture, policy, flags, scope)
 
-        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))!!
+        val bitmap = makeHardwareBitmap(100, 100)
         val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
 
-        val request = ScreenshotRequest(ScreenshotSource.SCREENSHOT_OTHER, bitmapBundle,
-            bounds, Insets.NONE, taskId, userId, topComponent)
+        val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
+            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(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, onSavedListener,
-            request, 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 fc28349..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;
@@ -98,9 +97,7 @@
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase;
-import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
-import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
 import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.media.MediaDataManager;
@@ -240,6 +237,8 @@
     @Mock
     private DozeLog mDozeLog;
     @Mock
+    private ShadeLogger mShadeLog;
+    @Mock
     private CommandQueue mCommandQueue;
     @Mock
     private VibratorHelper mVibratorHelper;
@@ -379,10 +378,7 @@
     @Mock
     private ViewTreeObserver mViewTreeObserver;
     @Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
-    @Mock private SetClockPositionUseCase mSetClockPositionUseCase;
-    @Mock private SetKeyguardBottomAreaAlphaUseCase mSetKeyguardBottomAreaAlphaUseCase;
-    @Mock private SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
-            mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+    @Mock private KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
     private NotificationPanelViewController.PanelEventsEmitter mPanelEventsEmitter;
     private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
     private SysuiStatusBarStateController mStatusBarStateController;
@@ -529,7 +525,9 @@
                 mNotificationShadeWindowController,
                 mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper,
                 mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
-                mMetricsLogger, mConfigurationController,
+                mMetricsLogger,
+                mShadeLog,
+                mConfigurationController,
                 () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
                 mConversationNotificationManager, mMediaHiearchyManager,
                 mStatusBarKeyguardViewManager,
@@ -577,9 +575,7 @@
                 mSystemClock,
                 mock(CameraGestureHelper.class),
                 () -> mKeyguardBottomAreaViewModel,
-                () -> mSetClockPositionUseCase,
-                () -> mSetKeyguardBottomAreaAlphaUseCase,
-                () -> mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase);
+                () -> mKeyguardBottomAreaInteractor);
         mNotificationPanelViewController.initDependencies(
                 mCentralSurfaces,
                 () -> {},
@@ -767,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);
     }
 
@@ -911,7 +905,7 @@
 
         setDozing(/* dozing= */ true, /* dozingAlwaysOn= */ true);
 
-        assertThat(isKeyguardStatusViewCentered()).isTrue();
+        assertKeyguardStatusViewCentered();
     }
 
     @Test
@@ -922,7 +916,7 @@
 
         setDozing(/* dozing= */ true, /* dozingAlwaysOn= */ false);
 
-        assertThat(isKeyguardStatusViewCentered()).isFalse();
+        assertKeyguardStatusViewNotCentered();
     }
 
     @Test
@@ -933,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
@@ -955,9 +949,9 @@
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
 
-        setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ true);
+        setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
 
-        assertThat(isKeyguardStatusViewCentered()).isFalse();
+        assertKeyguardStatusViewNotCentered();
     }
 
     @Test
@@ -970,7 +964,7 @@
         mStatusBarStateController.setState(KEYGUARD);
         setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
 
-        assertThat(isKeyguardStatusViewCentered()).isFalse();
+        assertKeyguardStatusViewCentered();
     }
 
     @Test
@@ -1215,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
@@ -1236,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);
     }
@@ -1550,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/shade/testing/FakeNotifPanelEvents.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt
new file mode 100644
index 0000000..d052138
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.testing
+
+import com.android.systemui.shade.NotifPanelEvents
+
+/** Fake implementation of [NotifPanelEvents] for testing. */
+class FakeNotifPanelEvents : NotifPanelEvents {
+
+    private val listeners = mutableListOf<NotifPanelEvents.Listener>()
+
+    override fun registerListener(listener: NotifPanelEvents.Listener) {
+        listeners.add(listener)
+    }
+
+    override fun unregisterListener(listener: NotifPanelEvents.Listener) {
+        listeners.remove(listener)
+    }
+
+    fun changeExpandImmediate(expandImmediate: Boolean) {
+        listeners.forEach { it.onExpandImmediateChanged(expandImmediate) }
+    }
+}
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/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index c5beee8..22d61a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -23,6 +23,7 @@
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
@@ -50,7 +51,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.IActivityManager;
 import android.app.Instrumentation;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyResourcesManager;
@@ -161,8 +161,6 @@
     @Mock
     private LockPatternUtils mLockPatternUtils;
     @Mock
-    private IActivityManager mIActivityManager;
-    @Mock
     private KeyguardBypassController mKeyguardBypassController;
     @Mock
     private AccessibilityManager mAccessibilityManager;
@@ -256,8 +254,7 @@
                 mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor,
                 mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats,
                 mUserManager, mExecutor, mExecutor,  mFalsingManager, mLockPatternUtils,
-                mScreenLifecycle, mIActivityManager, mKeyguardBypassController,
-                mAccessibilityManager);
+                mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager);
         mController.init();
         mController.setIndicationArea(mIndicationArea);
         verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
@@ -974,11 +971,11 @@
         mController.getKeyguardCallback().onBiometricAuthenticated(0,
                 BiometricSourceType.FACE, false);
 
-        // THEN 'face unlocked. press unlock icon to open' message shows
-        String pressToOpen = mContext.getString(R.string.keyguard_face_successful_unlock_press);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, pressToOpen);
-
-        assertThat(mTextView.getText()).isNotEqualTo(pressToOpen);
+        // THEN 'face unlocked' then 'press unlock icon to open' message show
+        String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
+        String pressToOpen = mContext.getString(R.string.keyguard_unlock_press);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, pressToOpen);
     }
 
 
@@ -999,10 +996,11 @@
         mController.getKeyguardCallback().onBiometricAuthenticated(0,
                 BiometricSourceType.FACE, false);
 
-        // THEN show 'face unlocked. swipe up to open' message
-        String faceUnlockedSwipeToOpen =
-                mContext.getString(R.string.keyguard_face_successful_unlock_swipe);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, faceUnlockedSwipeToOpen);
+        // THEN show 'face unlocked' and 'swipe up to open' messages
+        String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
+        String swipeUpToOpen = mContext.getString(R.string.keyguard_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, swipeUpToOpen);
     }
 
     @Test
@@ -1021,10 +1019,11 @@
         mController.getKeyguardCallback().onBiometricAuthenticated(0,
                 BiometricSourceType.FACE, false);
 
-        // THEN show 'swipe up to open' message
-        String faceUnlockedSwipeToOpen =
-                mContext.getString(R.string.keyguard_face_successful_unlock_swipe);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, faceUnlockedSwipeToOpen);
+        // THEN show 'face unlocked' and 'swipe up to open' messages
+        String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
+        String swipeUpToOpen = mContext.getString(R.string.keyguard_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, swipeUpToOpen);
     }
 
     @Test
@@ -1042,10 +1041,11 @@
         mController.getKeyguardCallback().onBiometricAuthenticated(0,
                 BiometricSourceType.FACE, false);
 
-        // THEN show 'swipe up to open' message
-        String faceUnlockedSwipeToOpen =
-                mContext.getString(R.string.keyguard_face_successful_unlock_swipe);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, faceUnlockedSwipeToOpen);
+        // THEN show 'face unlocked' and 'swipe up to open' messages
+        String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
+        String swipeUpToOpen = mContext.getString(R.string.keyguard_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, swipeUpToOpen);
     }
 
     @Test
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/collection/TargetSdkResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
new file mode 100644
index 0000000..8275c0c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
@@ -0,0 +1,132 @@
+/*
+ *  Copyright (C) 2022 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.collection
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.os.UserHandle
+import android.service.notification.NotificationListenerService.Ranking
+import android.service.notification.StatusBarNotification
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
+
+private const val SDK_VERSION = 33
+private const val PACKAGE = "pkg"
+private const val USER_ID = -1
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TargetSdkResolverTest : SysuiTestCase() {
+    private val packageManager: PackageManager = mock()
+    private val applicationInfo = ApplicationInfo().apply { targetSdkVersion = SDK_VERSION }
+
+    private lateinit var targetSdkResolver: TargetSdkResolver
+    private lateinit var notifListener: NotifCollectionListener
+
+    @Before
+    fun setUp() {
+        targetSdkResolver = TargetSdkResolver(mContext)
+        mContext.setMockPackageManager(packageManager)
+
+        val notifCollection: CommonNotifCollection = mock()
+        targetSdkResolver.initialize(notifCollection)
+        notifListener = withArgCaptor {
+            verify(notifCollection).addCollectionListener(capture())
+        }
+    }
+
+    @Test
+    fun resolveFromNotificationExtras() {
+        val extras = Bundle().apply {
+            putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, applicationInfo)
+        }
+        val notification = Notification().apply { this.extras = extras }
+        val sbn = createSbn(notification)
+        val entry = createNotificationEntry(sbn)
+
+        notifListener.onEntryBind(entry, sbn)
+
+        assertEquals(SDK_VERSION, entry.targetSdk)
+        verifyZeroInteractions(packageManager)
+    }
+
+    @Test
+    fun resolveFromPackageManager() {
+        val sbn = createSbn(Notification())
+        val entry = createNotificationEntry(sbn)
+        whenever(packageManager.getApplicationInfo(anyString(), anyInt()))
+                .thenReturn(applicationInfo)
+
+        notifListener.onEntryBind(entry, sbn)
+
+        assertEquals(SDK_VERSION, entry.targetSdk)
+        verify(packageManager).getApplicationInfo(eq(PACKAGE), anyInt())
+    }
+
+    @Test
+    fun resolveFromPackageManager_andPackageManagerCrashes() {
+        val sbn = createSbn(Notification())
+        val entry = createNotificationEntry(sbn)
+        whenever(packageManager.getApplicationInfo(anyString(), anyInt()))
+                .thenThrow(PackageManager.NameNotFoundException())
+
+        notifListener.onEntryBind(entry, sbn)
+
+        assertEquals(0, entry.targetSdk)
+        verify(packageManager).getApplicationInfo(eq(PACKAGE), anyInt())
+    }
+
+    private fun createSbn(notification: Notification) = StatusBarNotification(
+            PACKAGE, "opPkg", 0, "tag", 0, 0,
+            notification, UserHandle(USER_ID), "", 0
+    )
+
+    private fun createNotificationEntry(sbn: StatusBarNotification) =
+            NotificationEntry(sbn, createRanking(sbn.key), 0)
+
+    private fun createRanking(key: String) = Ranking().apply {
+        populate(
+                key,
+                0,
+                false,
+                0,
+                0,
+                NotificationManager.IMPORTANCE_DEFAULT,
+                null, null,
+                null, null, null, true, 0, false, -1, false, null, null, false, false,
+                false, null, 0, false)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index 541749b4..d59cc54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.interruption;
 
+import static android.app.Notification.VISIBILITY_PRIVATE;
 import static android.app.Notification.VISIBILITY_PUBLIC;
 import static android.app.Notification.VISIBILITY_SECRET;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
@@ -449,6 +450,54 @@
     }
 
     @Test
+    public void notificationVisibilityPublic() {
+        // GIVEN a VISIBILITY_PUBLIC notification
+        NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID));
+        entryBuilder.modifyNotification(mContext)
+                .setVisibility(VISIBILITY_PUBLIC);
+        mEntry = entryBuilder.build();
+
+        // WHEN we're in an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState(mEntry);
+
+        // THEN don't hide the entry based on visibility.
+        assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
+    public void notificationVisibilityPrivate() {
+        // GIVEN a VISIBILITY_PRIVATE notification
+        NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID));
+        entryBuilder.modifyNotification(mContext)
+                .setVisibility(VISIBILITY_PRIVATE);
+        mEntry = entryBuilder.build();
+
+        // WHEN we're in an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState(mEntry);
+
+        // THEN don't hide the entry based on visibility. (Redaction is handled elsewhere.)
+        assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
+    public void notificationVisibilitySecret() {
+        // GIVEN a VISIBILITY_SECRET notification
+        NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID));
+        entryBuilder.modifyNotification(mContext)
+                .setVisibility(VISIBILITY_SECRET);
+        mEntry = entryBuilder.build();
+
+        // WHEN we're in an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState(mEntry);
+
+        // THEN hide the entry based on visibility.
+        assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
     public void summaryExceedsThresholdToShow() {
         // GIVEN the notification doesn't exceed the threshold to show on the lockscreen
         // but it's part of a group (has a parent)
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/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 381d72f..90adabf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -236,7 +236,6 @@
     @Test
     public void testBindNotification_SetsShortcutIcon() {
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -261,7 +260,6 @@
     public void testBindNotification_SetsTextApplicationName() {
         when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -314,7 +312,6 @@
         mConversationChannel.setGroup(group.getId());
 
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -340,7 +337,6 @@
     @Test
     public void testBindNotification_GroupNameHiddenIfNoGroup() {
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -365,7 +361,6 @@
     @Test
     public void testBindNotification_noDelegate() {
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -401,7 +396,6 @@
                 .setShortcutInfo(mShortcutInfo)
                 .build();
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -427,7 +421,6 @@
     public void testBindNotification_SetsOnClickListenerForSettings() {
         final CountDownLatch latch = new CountDownLatch(1);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -457,7 +450,6 @@
     @Test
     public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() {
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -482,7 +474,6 @@
     public void testBindNotification_SettingsButtonInvisibleWhenDeviceUnprovisioned() {
         final CountDownLatch latch = new CountDownLatch(1);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -511,7 +502,6 @@
         mConversationChannel.setImportance(IMPORTANCE_LOW);
         mConversationChannel.setImportantConversation(true);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -540,7 +530,6 @@
         mConversationChannel.setImportantConversation(false);
         mConversationChannel.setAllowBubbles(true);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -572,7 +561,6 @@
         mConversationChannel.setImportantConversation(false);
         mConversationChannel.setAllowBubbles(true);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -610,7 +598,6 @@
         mConversationChannel.setImportantConversation(false);
         mConversationChannel.setAllowBubbles(false);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -639,7 +626,6 @@
         mConversationChannel.setImportantConversation(false);
         mConversationChannel.setAllowBubbles(false);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -675,7 +661,6 @@
         mConversationChannel.setImportantConversation(false);
         mConversationChannel.setAllowBubbles(true);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -704,7 +689,6 @@
         mConversationChannel.setImportantConversation(false);
 
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -735,7 +719,7 @@
                 .isEqualTo(GONE);
 
         // no changes until hit done
-        assertFalse(mNotificationInfo.shouldBeSaved());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 anyString(), anyInt(), any());
         assertFalse(mConversationChannel.isImportantConversation());
@@ -749,7 +733,6 @@
         mConversationChannel.setImportance(IMPORTANCE_LOW);
         mConversationChannel.setImportantConversation(false);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -779,7 +762,7 @@
                 .isEqualTo(GONE);
 
         // no changes until hit done
-        assertFalse(mNotificationInfo.shouldBeSaved());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 anyString(), anyInt(), any());
         assertFalse(mConversationChannel.isImportantConversation());
@@ -793,7 +776,6 @@
         mConversationChannel.setImportantConversation(false);
 
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -825,7 +807,7 @@
                 .isEqualTo(VISIBLE);
 
         // no changes until save
-        assertFalse(mNotificationInfo.shouldBeSaved());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 anyString(), anyInt(), any());
         assertEquals(IMPORTANCE_DEFAULT, mConversationChannel.getImportance());
@@ -838,7 +820,6 @@
         mConversationChannel.setImportantConversation(false);
 
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -868,6 +849,7 @@
         assertTrue(captor.getValue().isImportantConversation());
         assertTrue(captor.getValue().canBubble());
         assertEquals(IMPORTANCE_DEFAULT, captor.getValue().getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -876,7 +858,6 @@
         mConversationChannel.setImportance(9);
 
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -913,7 +894,6 @@
         mConversationChannel.setImportantConversation(true);
 
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -954,7 +934,6 @@
 
         // WHEN we indicate no selected action
         mNotificationInfo.bindNotification(
-                -1, // no action selected by default
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -984,8 +963,8 @@
         mConversationChannel.setImportantConversation(false);
 
         // WHEN we indicate the selected action should be "Favorite"
+        mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
         mNotificationInfo.bindNotification(
-                NotificationConversationInfo.ACTION_FAVORITE, // "Favorite" selected by default
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -1015,7 +994,6 @@
         mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
         mConversationChannel.setImportantConversation(true);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -1044,6 +1022,7 @@
         assertFalse(captor.getValue().isImportantConversation());
         assertFalse(captor.getValue().canBubble());
         assertEquals(IMPORTANCE_HIGH, captor.getValue().getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -1052,7 +1031,6 @@
         mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
         mConversationChannel.setImportantConversation(false);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -1089,7 +1067,6 @@
         mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
 
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -1125,7 +1102,6 @@
         mConversationChannel.setAllowBubbles(true);
 
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -1155,12 +1131,46 @@
         assertFalse(captor.getValue().isImportantConversation());
         assertFalse(captor.getValue().canBubble());
         assertEquals(IMPORTANCE_LOW, captor.getValue().getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
+    }
+
+    @Test
+    public void testSilence_closeGutsThenTryToSave() {
+        mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
+        mConversationChannel.setImportantConversation(true);
+        mConversationChannel.setAllowBubbles(true);
+
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mMockPackageManager,
+                mPeopleSpaceWidgetManager,
+                mMockINotificationManager,
+                mOnUserInteractionCallback,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                mBubbleMetadata,
+                null,
+                mIconFactory,
+                mContext,
+                true,
+                mTestHandler,
+                mTestHandler, null, Optional.of(mBubblesManager),
+                mShadeController);
+
+        mNotificationInfo.findViewById(R.id.silence).performClick();
+        mNotificationInfo.handleCloseControls(false, false);
+        mNotificationInfo.findViewById(R.id.done).performClick();
+
+        mTestableLooper.processAllMessages();
+
+        assertEquals(IMPORTANCE_DEFAULT, mConversationChannel.getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
     public void testBindNotification_createsNewChannel() throws Exception {
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -1186,7 +1196,6 @@
     public void testBindNotification_doesNotCreateNewChannelIfExists() throws Exception {
         mNotificationChannel.setConversationId("", CONVERSATION_ID);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -1213,7 +1222,6 @@
         //WHEN channel is default importance
         mNotificationChannel.setImportantConversation(false);
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -1244,7 +1252,6 @@
     @Test
     public void testSelectDefaultDoesNotRequestPinPeopleTile() {
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
@@ -1279,7 +1286,6 @@
         mConversationChannel.setImportantConversation(true);
 
         mNotificationInfo.bindNotification(
-                -1,
                 mShortcutManager,
                 mMockPackageManager,
                 mPeopleSpaceWidgetManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
new file mode 100644
index 0000000..e696c87
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.LayoutInflater
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationGutsTest : SysuiTestCase() {
+
+    private lateinit var guts: NotificationGuts
+    private lateinit var gutsContentView: View
+
+    @Mock
+    private lateinit var gutsContent: NotificationGuts.GutsContent
+
+    @Mock
+    private lateinit var gutsClosedListener: NotificationGuts.OnGutsClosedListener
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        val layoutInflater = LayoutInflater.from(mContext)
+        guts = layoutInflater.inflate(R.layout.notification_guts, null) as NotificationGuts
+        gutsContentView = View(mContext)
+
+        whenever(gutsContent.contentView).thenReturn(gutsContentView)
+
+        ViewUtils.attachView(guts)
+    }
+
+    @After
+    fun tearDown() {
+        ViewUtils.detachView(guts)
+    }
+
+    @Test
+    fun setGutsContent() {
+        guts.gutsContent = gutsContent
+
+        verify(gutsContent).setGutsParent(guts)
+    }
+
+    @Test
+    fun openControls() {
+        guts.gutsContent = gutsContent
+
+        guts.openControls(true, 0, 0, false, null)
+    }
+
+    @Test
+    fun closeControlsWithSave() {
+        guts.gutsContent = gutsContent
+        guts.setClosedListener(gutsClosedListener)
+
+        guts.closeControls(gutsContentView, true)
+
+        verify(gutsContent).handleCloseControls(true, false)
+        verify(gutsClosedListener).onGutsClosed(guts)
+    }
+
+    @Test
+    fun closeControlsWithoutSave() {
+        guts.gutsContent = gutsContent
+        guts.setClosedListener(gutsClosedListener)
+
+        guts.closeControls(gutsContentView, false)
+
+        verify(gutsContent).handleCloseControls(false, false)
+        verify(gutsClosedListener).onGutsClosed(guts)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index b1f1075..80a81a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -50,6 +50,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.telecom.TelecomManager;
@@ -1090,6 +1091,7 @@
                 mUiEventLogger.eventId(0));
         assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_SAVE_IMPORTANCE.getId(),
                 mUiEventLogger.eventId(1));
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -1124,6 +1126,7 @@
         assertTrue((updated.getValue().getUserLockedFields()
                 & USER_LOCKED_IMPORTANCE) != 0);
         assertEquals(IMPORTANCE_DEFAULT, updated.getValue().getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -1156,6 +1159,7 @@
         verify(mMockINotificationManager, times(1)).unlockNotificationChannel(
                 anyString(), eq(TEST_UID), any());
         assertEquals(IMPORTANCE_DEFAULT, mNotificationChannel.getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -1191,6 +1195,7 @@
         assertTrue((updated.getValue().getUserLockedFields()
                 & USER_LOCKED_IMPORTANCE) != 0);
         assertEquals(IMPORTANCE_LOW, updated.getValue().getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -1229,6 +1234,37 @@
                 anyString(), eq(TEST_UID), updated.capture());
         assertTrue((updated.getValue().getUserLockedFields() & USER_LOCKED_IMPORTANCE) != 0);
         assertEquals(IMPORTANCE_MIN, updated.getValue().getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
+    }
+
+    @Test
+    public void testSilence_closeGutsThenTryToSave() throws RemoteException {
+        mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+        mNotificationInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mOnUserInteractionCallback,
+                mChannelEditorDialogController,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mEntry,
+                null,
+                null,
+                mUiEventLogger,
+                true,
+                false,
+                false,
+                mAssistantFeedbackController);
+
+        mNotificationInfo.findViewById(R.id.silence).performClick();
+        mNotificationInfo.handleCloseControls(false, false);
+        mNotificationInfo.handleCloseControls(true, false);
+
+        mTestableLooper.processAllMessages();
+
+        assertEquals(IMPORTANCE_DEFAULT, mNotificationChannel.getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -1267,6 +1303,7 @@
                 anyString(), eq(TEST_UID), updated.capture());
         assertTrue((updated.getValue().getUserLockedFields() & USER_LOCKED_IMPORTANCE) != 0);
         assertEquals(IMPORTANCE_DEFAULT, updated.getValue().getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -1294,6 +1331,7 @@
         mNotificationInfo.handleCloseControls(true, false);
 
         verify(mOnUserInteractionCallback).onImportanceChanged(mEntry);
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -1360,6 +1398,7 @@
         assertTrue((updated.getValue().getUserLockedFields()
                 & USER_LOCKED_IMPORTANCE) != 0);
         assertEquals(IMPORTANCE_DEFAULT, updated.getValue().getImportance());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
@@ -1450,7 +1489,7 @@
 
         mNotificationInfo.findViewById(R.id.alert).performClick();
 
-        assertFalse(mNotificationInfo.shouldBeSaved());
+        assertFalse(mNotificationInfo.shouldBeSavedOnClose());
     }
 
     @Test
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/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
index 43aa8fe..12c8fd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME;
 import static android.view.View.GONE;
@@ -25,7 +24,6 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.ArgumentMatchers.anyObject;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
@@ -36,8 +34,6 @@
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
-import android.app.NotificationChannelGroup;
-import android.app.Person;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
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 515a7c9..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt
+++ /dev/null
@@ -1,87 +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.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,
-        )
-
-        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/repository/NetworkCapabilitiesRepoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt
deleted file mode 100644
index 40f8fbf..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/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.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.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/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
index 2915ae8..36be1be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.pipeline
+package com.android.systemui.statusbar.pipeline.shared
 
 import android.net.Network
 import android.net.NetworkCapabilities
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
new file mode 100644
index 0000000..6b8d4aa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.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.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 _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 setWifiNetwork(wifiNetworkModel: WifiNetworkModel) {
+        _wifiNetwork.value = wifiNetworkModel
+    }
+
+    fun setWifiActivity(activity: WifiActivityModel) {
+        _wifiActivity.value = activity
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
new file mode 100644
index 0000000..d0a3808
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
@@ -0,0 +1,523 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.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
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+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)
+@SmallTest
+class WifiRepositoryImplTest : SysuiTestCase() {
+
+    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
+
+    @Before
+    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,
+        )
+
+        var latest: WifiActivityModel? = null
+        val job = underTest
+                .wifiActivity
+                .onEach { latest = it }
+                .launchIn(this)
+
+        assertThat(latest).isEqualTo(ACTIVITY_DEFAULT)
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiActivity_callbackGivesNone_activityFlowHasNone() = runBlocking(IMMEDIATE) {
+        var latest: WifiActivityModel? = null
+        val job = underTest
+                .wifiActivity
+                .onEach { latest = it }
+                .launchIn(this)
+
+        getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_NONE)
+
+        assertThat(latest).isEqualTo(
+            WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+        )
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiActivity_callbackGivesIn_activityFlowHasIn() = runBlocking(IMMEDIATE) {
+        var latest: WifiActivityModel? = null
+        val job = underTest
+                .wifiActivity
+                .onEach { latest = it }
+                .launchIn(this)
+
+        getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN)
+
+        assertThat(latest).isEqualTo(
+            WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+        )
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiActivity_callbackGivesOut_activityFlowHasOut() = runBlocking(IMMEDIATE) {
+        var latest: WifiActivityModel? = null
+        val job = underTest
+                .wifiActivity
+                .onEach { latest = it }
+                .launchIn(this)
+
+        getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT)
+
+        assertThat(latest).isEqualTo(
+            WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+        )
+
+        job.cancel()
+    }
+
+    @Test
+    fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = runBlocking(IMMEDIATE) {
+        var latest: WifiActivityModel? = null
+        val job = underTest
+                .wifiActivity
+                .onEach { latest = it }
+                .launchIn(this)
+
+        getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT)
+
+        assertThat(latest).isEqualTo(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+
+        job.cancel()
+    }
+
+    private fun getTrafficStateCallback(): TrafficStateCallback {
+        val callbackCaptor = argumentCaptor<TrafficStateCallback>()
+        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
new file mode 100644
index 0000000..5f1b1db
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.domain.interactor
+
+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.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.Before
+import org.junit.Test
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class WifiInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: WifiInteractor
+
+    private lateinit var repository: FakeWifiRepository
+
+    @Before
+    fun setUp() {
+        repository = FakeWifiRepository()
+        underTest = WifiInteractor(repository)
+    }
+
+    @Test
+    fun hasActivityIn_noInOrOut_outputsFalse() = runBlocking(IMMEDIATE) {
+        repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
+        repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = false))
+
+        var latest: Boolean? = null
+        val job = underTest
+                .hasActivityIn
+                .onEach { latest = it }
+                .launchIn(this)
+
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun hasActivityIn_onlyOut_outputsFalse() = runBlocking(IMMEDIATE) {
+        repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
+        repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = true))
+
+        var latest: Boolean? = null
+        val job = underTest
+                .hasActivityIn
+                .onEach { latest = it }
+                .launchIn(this)
+
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun hasActivityIn_onlyIn_outputsTrue() = runBlocking(IMMEDIATE) {
+        repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
+        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+
+        var latest: Boolean? = null
+        val job = underTest
+                .hasActivityIn
+                .onEach { latest = it }
+                .launchIn(this)
+
+        assertThat(latest).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun hasActivityIn_inAndOut_outputsTrue() = runBlocking(IMMEDIATE) {
+        repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
+        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+
+        var latest: Boolean? = null
+        val job = underTest
+                .hasActivityIn
+                .onEach { latest = it }
+                .launchIn(this)
+
+        assertThat(latest).isTrue()
+
+        job.cancel()
+    }
+
+    @Test
+    fun hasActivityIn_ssidNull_outputsFalse() = runBlocking(IMMEDIATE) {
+        repository.setWifiNetwork(WifiNetworkModel.Active(networkId = 1, ssid = null))
+        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+
+        var latest: Boolean? = null
+        val job = underTest
+                .hasActivityIn
+                .onEach { latest = it }
+                .launchIn(this)
+
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun hasActivityIn_multipleChanges_multipleOutputChanges() = runBlocking(IMMEDIATE) {
+        repository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
+
+        var latest: Boolean? = null
+        val job = underTest
+                .hasActivityIn
+                .onEach { latest = it }
+                .launchIn(this)
+
+        // Conduct a series of changes and verify we catch each of them in succession
+        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+        yield()
+        assertThat(latest).isTrue()
+
+        repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = true))
+        yield()
+        assertThat(latest).isFalse()
+
+        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+        yield()
+        assertThat(latest).isTrue()
+
+        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+        yield()
+        assertThat(latest).isTrue()
+
+        repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = false))
+        yield()
+        assertThat(latest).isFalse()
+
+        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
new file mode 100644
index 0000000..c790734
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.viewmodel
+
+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.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
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class WifiViewModelTest : SysuiTestCase() {
+
+    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
+    private lateinit var interactor: WifiInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        repository = FakeWifiRepository()
+        interactor = WifiInteractor(repository)
+
+        underTest = WifiViewModel(
+            statusBarPipelineFlags,
+            constants,
+            logger,
+            interactor
+        )
+
+        // Set up with a valid SSID
+        repository.setWifiNetwork(WifiNetworkModel.Active(networkId = 1, ssid = "AB"))
+    }
+
+    @Test
+    fun activityInVisible_showActivityConfigFalse_receivesFalse() = runBlocking(IMMEDIATE) {
+        whenever(constants.shouldShowActivityConfig).thenReturn(false)
+
+        var latest: Boolean? = null
+        val job = underTest
+                .isActivityInVisible
+                .onEach { latest = it }
+                .launchIn(this)
+
+        // Verify that on launch, we receive a false.
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun activityInVisible_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
+        whenever(constants.shouldShowActivityConfig).thenReturn(false)
+
+        var latest: Boolean? = null
+        val job = underTest
+                .isActivityInVisible
+                .onEach { latest = it }
+                .launchIn(this)
+
+        // Update the repo to have activityIn
+        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+        yield()
+
+        // Verify that we didn't update to activityIn=true (because our config is false)
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun activityInVisible_showActivityConfigTrue_receivesUpdate() = runBlocking(IMMEDIATE) {
+        whenever(constants.shouldShowActivityConfig).thenReturn(true)
+
+        var latest: Boolean? = null
+        val job = underTest
+                .isActivityInVisible
+                .onEach { latest = it }
+                .launchIn(this)
+
+        // Update the repo to have activityIn
+        repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+        yield()
+
+        // Verify that we updated to activityIn=true
+        assertThat(latest).isTrue()
+
+        job.cancel()
+    }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
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/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
index 309acdf..f539dbd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
@@ -91,6 +91,8 @@
     fun capture(): T = wrapped.capture()
     val value: T
         get() = wrapped.value
+    val allValues: List<T>
+        get() = wrapped.allValues
 }
 
 /**
diff --git a/packages/VpnDialogs/res/values-es/strings.xml b/packages/VpnDialogs/res/values-es/strings.xml
index 0eaf359..9bf86f5 100644
--- a/packages/VpnDialogs/res/values-es/strings.xml
+++ b/packages/VpnDialogs/res/values-es/strings.xml
@@ -19,7 +19,7 @@
     <string name="prompt" msgid="3183836924226407828">"Solicitud de conexión"</string>
     <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> quiere configurar una conexión VPN para controlar el tráfico de red. Solo debes aceptarla si confías en la fuente. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; aparece en la parte superior de la pantalla cuando se active la conexión VPN."</string>
     <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> quiere configurar una conexión VPN que le permita monitorizar el tráfico de red. Acéptalo solo si confías en la fuente. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; aparecerá en la pantalla cuando la VPN esté activa."</string>
-    <string name="legacy_title" msgid="192936250066580964">"VPN conectada"</string>
+    <string name="legacy_title" msgid="192936250066580964">"La VPN está conectada"</string>
     <string name="session" msgid="6470628549473641030">"Sesión:"</string>
     <string name="duration" msgid="3584782459928719435">"Duración:"</string>
     <string name="data_transmitted" msgid="7988167672982199061">"Enviado:"</string>
diff --git a/proto/src/OWNERS b/proto/src/OWNERS
index b456ba6..abd08de 100644
--- a/proto/src/OWNERS
+++ b/proto/src/OWNERS
@@ -1,3 +1,4 @@
 per-file gnss.proto = file:/services/core/java/com/android/server/location/OWNERS
 per-file wifi.proto = file:/wifi/OWNERS
 per-file camera.proto = file:/services/core/java/com/android/server/camera/OWNERS
+per-file system_messages.proto = file:/core/res/OWNERS
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index f903c20..a94bfe2 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -302,6 +302,10 @@
     // Package: android
     NOTE_WIFI_APM_NOTIFICATION = 73;
 
+    // Inform the user of bluetooth apm state changes.
+    // Package: android
+    NOTE_BT_APM_NOTIFICATION = 74;
+
     // ADD_NEW_IDS_ABOVE_THIS_LINE
     // Legacy IDs with arbitrary values appear below
     // Legacy IDs existed as stable non-conflicting constants prior to the O release
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/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 3324c52..b34482f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -354,24 +354,16 @@
 
         if (supportsFlagForNotImportantViews(info)) {
             if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) {
-                mFetchFlags |=
-                        AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS;
+                mFetchFlags |= AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
             } else {
-                mFetchFlags &=
-                        ~AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS;
+                mFetchFlags &= ~AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
             }
         }
 
         if ((info.flags & AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS) != 0) {
-            mFetchFlags |= AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS;
+            mFetchFlags |= AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
         } else {
-            mFetchFlags &= ~AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS;
-        }
-
-        if (mAccessibilityServiceInfo.isAccessibilityTool()) {
-            mFetchFlags |= AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL;
-        } else {
-            mFetchFlags &= ~AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL;
+            mFetchFlags &= ~AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
         }
 
         mRequestTouchExplorationMode = (info.flags
@@ -1530,16 +1522,9 @@
             return false;
         }
 
-        final boolean includeNotImportantViews = (mFetchFlags
-                & AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
         if ((event.getWindowId() != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
                 && !event.isImportantForAccessibility()
-                && !includeNotImportantViews) {
-            return false;
-        }
-
-        if (event.isAccessibilityDataPrivate()
-                && (mFetchFlags & AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL) == 0) {
+                && (mFetchFlags & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) {
             return false;
         }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 6a6d2bb..6eabc98 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3693,7 +3693,6 @@
             info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
             info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
             info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
-            info.setAccessibilityTool(true);
             final AccessibilityUserState userState;
             synchronized (mLock) {
                 userState = getCurrentUserStateLocked();
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/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index fe85db2..5a35474 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -231,7 +231,7 @@
             sendStateToClients(/* resetClient= */ false);
         }
         updateRemoteAugmentedAutofillService();
-        updateRemoteInlineSuggestionRenderServiceLocked();
+        getRemoteInlineSuggestionRenderServiceLocked();
 
         return enabledChanged;
     }
@@ -685,8 +685,12 @@
 
     @GuardedBy("mLock")
     void resetExtServiceLocked() {
-        if (sVerbose) Slog.v(TAG, "reset autofill service.");
+        if (sVerbose) Slog.v(TAG, "reset autofill service in ExtServices.");
         mFieldClassificationStrategy.reset();
+        if (mRemoteInlineSuggestionRenderService != null) {
+            mRemoteInlineSuggestionRenderService.destroy();
+            mRemoteInlineSuggestionRenderService = null;
+        }
     }
 
     @GuardedBy("mLock")
@@ -1583,18 +1587,6 @@
         return mFieldClassificationStrategy.getDefaultAlgorithm();
     }
 
-    private void updateRemoteInlineSuggestionRenderServiceLocked() {
-        if (mRemoteInlineSuggestionRenderService != null) {
-            if (sVerbose) {
-                Slog.v(TAG, "updateRemoteInlineSuggestionRenderService(): "
-                        + "destroying old remote service");
-            }
-            mRemoteInlineSuggestionRenderService = null;
-        }
-
-        mRemoteInlineSuggestionRenderService = getRemoteInlineSuggestionRenderServiceLocked();
-    }
-
     @Nullable RemoteInlineSuggestionRenderService getRemoteInlineSuggestionRenderServiceLocked() {
         if (mRemoteInlineSuggestionRenderService == null) {
             final ComponentName componentName = RemoteInlineSuggestionRenderService
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 243a7e0..907daa3 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -160,7 +160,6 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Queue;
@@ -3401,7 +3400,8 @@
     }
 
     /**
-     * Selects transport {@code transportName} and returns previously selected transport.
+     * Selects transport {@code transportName}, if it is already registered, and returns previously
+     * selected transport. Returns {@code null} if the transport is not registered.
      *
      * @deprecated Use {@link #selectBackupTransportAsync(ComponentName,
      * ISelectBackupTransportCallback)} instead.
@@ -3414,6 +3414,17 @@
 
         final long oldId = Binder.clearCallingIdentity();
         try {
+            if (!mTransportManager.isTransportRegistered(transportName)) {
+                Slog.v(
+                        TAG,
+                        addUserIdToLogMessage(
+                                mUserId,
+                                "Could not select transport "
+                                        + transportName
+                                        + ", as the transport is not registered."));
+                return null;
+            }
+
             String previousTransportName = mTransportManager.selectTransport(transportName);
             updateStateForTransport(transportName);
             Slog.v(
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/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 43e2b88..593a63c 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -77,6 +77,14 @@
             new ComponentName("android", BlockedAppStreamingActivity.class.getName());
 
     /**
+     * For communicating when a secure window shows on the virtual display.
+     */
+    public interface SecureWindowCallback {
+        /** Called when a secure window shows on the virtual display. */
+        void onSecureWindowShown(int displayId, int uid);
+    }
+
+    /**
      * If required, allow the secure activity to display on remote device since
      * {@link android.os.Build.VERSION_CODES#TIRAMISU}.
      */
@@ -108,6 +116,7 @@
             new ArraySet<>();
     @Nullable
     private final @AssociationRequest.DeviceProfile String mDeviceProfile;
+    @Nullable private final SecureWindowCallback mSecureWindowCallback;
 
     /**
      * Creates a window policy controller that is generic to the different use cases of virtual
@@ -131,6 +140,8 @@
      * @param activityListener Activity listener to listen for activity changes.
      * @param activityBlockedCallback Callback that is called when an activity is blocked from
      *   launching.
+     * @param secureWindowCallback Callback that is called when a secure window shows on the
+     *   virtual display.
      * @param deviceProfile The {@link AssociationRequest.DeviceProfile} of this virtual device.
      */
     public GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
@@ -142,6 +153,7 @@
             @ActivityPolicy int defaultActivityPolicy,
             @NonNull ActivityListener activityListener,
             @NonNull ActivityBlockedCallback activityBlockedCallback,
+            @NonNull SecureWindowCallback secureWindowCallback,
             @AssociationRequest.DeviceProfile String deviceProfile) {
         super();
         mAllowedUsers = allowedUsers;
@@ -154,6 +166,7 @@
         setInterestedWindowFlags(windowFlags, systemWindowFlags);
         mActivityListener = activityListener;
         mDeviceProfile = deviceProfile;
+        mSecureWindowCallback = secureWindowCallback;
     }
 
     /**
@@ -234,6 +247,12 @@
     @Override
     public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
             int systemWindowFlags) {
+        // The callback is fired only when windowFlags are changed. To let VirtualDevice owner
+        // aware that the virtual display has a secure window on top.
+        if ((windowFlags & FLAG_SECURE) != 0) {
+            mSecureWindowCallback.onSecureWindowShown(mDisplayId, activityInfo.applicationInfo.uid);
+        }
+
         if (!canContainActivity(activityInfo, windowFlags, systemWindowFlags)) {
             mActivityBlockedCallback.onActivityBlocked(mDisplayId, activityInfo);
             return false;
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 5f337ab..cca3212 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -52,6 +52,7 @@
 import android.hardware.input.VirtualTouchEvent;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -542,6 +543,7 @@
                             mParams.getDefaultActivityPolicy(),
                             createListenerAdapter(),
                             this::onActivityBlocked,
+                            this::onSecureWindowShown,
                             mAssociationInfo.getDeviceProfile());
             gwpc.registerRunningAppsChangedListener(/* listener= */ this);
             return gwpc;
@@ -591,6 +593,21 @@
                 mContext.getUser());
     }
 
+    private void onSecureWindowShown(int displayId, int uid) {
+        if (!mVirtualDisplayIds.contains(displayId)) {
+            return;
+        }
+
+        // If a virtual display isn't secure, the screen can't be captured. Show a warning toast
+        // if the secure window is shown on a non-secure virtual display.
+        DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+        Display display = displayManager.getDisplay(displayId);
+        if ((display.getFlags() & FLAG_SECURE) == 0) {
+            showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_secure_window,
+                    Toast.LENGTH_LONG, mContext.getMainLooper());
+        }
+    }
+
     private ArraySet<UserHandle> getAllowedUserHandles() {
         ArraySet<UserHandle> result = new ArraySet<>();
         DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
@@ -650,14 +667,16 @@
     /**
      * Shows a toast on virtual displays owned by this device which have a given uid running.
      */
-    void showToastWhereUidIsRunning(int uid, @StringRes int resId, @Toast.Duration int duration) {
-        showToastWhereUidIsRunning(uid, mContext.getString(resId), duration);
+    void showToastWhereUidIsRunning(int uid, @StringRes int resId, @Toast.Duration int duration,
+            Looper looper) {
+        showToastWhereUidIsRunning(uid, mContext.getString(resId), duration, looper);
     }
 
     /**
      * Shows a toast on virtual displays owned by this device which have a given uid running.
      */
-    void showToastWhereUidIsRunning(int uid, String text, @Toast.Duration int duration) {
+    void showToastWhereUidIsRunning(int uid, String text, @Toast.Duration int duration,
+            Looper looper) {
         synchronized (mVirtualDeviceLock) {
             DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
             final int size = mWindowPolicyControllers.size();
@@ -666,7 +685,7 @@
                     int displayId = mWindowPolicyControllers.keyAt(i);
                     Display display = displayManager.getDisplay(displayId);
                     if (display != null && display.isValid()) {
-                        Toast.makeText(mContext.createDisplayContext(display), text,
+                        Toast.makeText(mContext.createDisplayContext(display), looper, text,
                                 duration).show();
                     }
                 }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index b255188..cdddc1d 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -203,7 +203,7 @@
                         getContext().getString(
                             com.android.internal.R.string.vdm_camera_access_denied,
                             deviceName),
-                        Toast.LENGTH_LONG);
+                        Toast.LENGTH_LONG, Looper.myLooper());
             }
         }
     }
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index dac23a7..3d8dc14 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -975,11 +975,6 @@
      */
     public abstract void setEnableRollbackCode(int token, int enableRollbackCode);
 
-    /**
-     * Ask the package manager to compile layouts in the given package.
-     */
-    public abstract boolean compileLayouts(String packageName);
-
     /*
      * Inform the package manager that the pending package install identified by
      * {@code token} can be completed.
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/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 1d457aa..02c6ca2 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -479,6 +479,10 @@
             if (length > max) {
                 // Log and fall through to create empty tombstone below
                 Slog.w(TAG, "Dropping: " + tag + " (" + length + " > " + max + " bytes)");
+                logDropboxDropped(
+                        FrameworkStatsLog.DROPBOX_ENTRY_DROPPED__DROP_REASON__ENTRY_TOO_LARGE,
+                        tag,
+                        0);
             } else {
                 temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp");
                 try (FileOutputStream out = new FileOutputStream(temp)) {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 83e3b49..5b1f740 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -19,12 +19,10 @@
 import static android.Manifest.permission.ACCESS_MTP;
 import static android.Manifest.permission.INSTALL_PACKAGES;
 import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
-import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
 import static android.app.AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE;
 import static android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES;
-import static android.app.AppOpsManager.OP_WRITE_EXTERNAL_STORAGE;
 import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.app.PendingIntent.FLAG_ONE_SHOT;
@@ -3047,6 +3045,10 @@
 
         try {
             mVold.createUserKey(userId, serialNumber, ephemeral);
+            // New keys are always unlocked.
+            synchronized (mLock) {
+                mLocalUnlockedUsers.append(userId);
+            }
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -3058,6 +3060,10 @@
 
         try {
             mVold.destroyUserKey(userId);
+            // Destroying a key also locks it.
+            synchronized (mLock) {
+                mLocalUnlockedUsers.remove(userId);
+            }
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -4368,11 +4374,7 @@
                 }
             }
 
-            // Determine if caller is holding runtime permission
-            final boolean hasWrite = StorageManager.checkPermissionAndCheckOp(mContext, false, 0,
-                    uid, packageName, WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE);
-
-            // We're only willing to give out installer access if they also hold
+            // We're only willing to give out installer access if they hold
             // runtime permission; this is a firm CDD requirement
             final boolean hasInstall = mIPackageManager.checkUidPermission(INSTALL_PACKAGES,
                     uid) == PERMISSION_GRANTED;
@@ -4388,7 +4390,7 @@
                     break;
                 }
             }
-            if ((hasInstall || hasInstallOp) && hasWrite) {
+            if (hasInstall || hasInstallOp) {
                 return StorageManager.MOUNT_MODE_EXTERNAL_INSTALLER;
             }
             return StorageManager.MOUNT_MODE_EXTERNAL_DEFAULT;
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 e46639b..a0c2ad5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -454,11 +454,13 @@
 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;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
 
 public class ActivityManagerService extends IActivityManager.Stub
         implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock {
@@ -2399,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;
@@ -4304,7 +4306,8 @@
                 null /* excludedPackages */, OP_NONE, null /* bOptions */, false /* ordered */,
                 false /* sticky */, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
                 Binder.getCallingPid(), userId, false /* allowBackgroundActivityStarts */,
-                null /* backgroundActivityStartsToken */, broadcastAllowList);
+                null /* backgroundActivityStartsToken */,
+                broadcastAllowList, null /* filterExtrasForReceiver */);
     }
 
     private void cleanupDisabledPackageComponentsLocked(
@@ -5499,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;
@@ -5511,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);
         }
@@ -10621,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), "    ");
@@ -13077,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) {
@@ -13109,7 +13119,6 @@
     void updateUidReadyForBootCompletedBroadcastLocked(int uid) {
         for (BroadcastQueue queue : mBroadcastQueues) {
             queue.updateUidReadyForBootCompletedBroadcastLocked(uid);
-            queue.scheduleBroadcastsLocked();
         }
     }
 
@@ -13357,9 +13366,9 @@
                     BroadcastRecord r = new BroadcastRecord(queue, intent, null,
                             null, null, -1, -1, false, null, null, null, null, OP_NONE, null,
                             receivers, null, 0, null, null, false, true, true, -1, false, null,
-                            false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */);
-                    queue.enqueueParallelBroadcastLocked(r);
-                    queue.scheduleBroadcastsLocked();
+                            false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
+                            null /* filterExtrasForReceiver */);
+                    queue.enqueueBroadcastLocked(r);
                 }
             }
 
@@ -13620,7 +13629,8 @@
                 excludedPermissions, excludedPackages, appOp, bOptions, ordered, sticky, callingPid,
                 callingUid, realCallingUid, realCallingPid, userId,
                 false /* allowBackgroundActivityStarts */,
-                null /* tokenNeededForBackgroundActivityStarts */, null /* broadcastAllowList */);
+                null /* tokenNeededForBackgroundActivityStarts */,
+                null /* broadcastAllowList */, null /* filterExtrasForReceiver */);
     }
 
     @GuardedBy("this")
@@ -13633,7 +13643,8 @@
             int realCallingUid, int realCallingPid, int userId,
             boolean allowBackgroundActivityStarts,
             @Nullable IBinder backgroundActivityStartsToken,
-            @Nullable int[] broadcastAllowList) {
+            @Nullable int[] broadcastAllowList,
+            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
         intent = new Intent(intent);
 
         final boolean callerInstantApp = isInstantApp(callerApp, callerPackage, callingUid);
@@ -14233,15 +14244,9 @@
                     requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
                     registeredReceivers, resultTo, resultCode, resultData, resultExtras, ordered,
                     sticky, false, userId, allowBackgroundActivityStarts,
-                    backgroundActivityStartsToken, timeoutExempt);
+                    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;
         }
@@ -14331,34 +14336,10 @@
                     requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
                     receivers, resultTo, resultCode, resultData, resultExtras,
                     ordered, sticky, false, userId, allowBackgroundActivityStarts,
-                    backgroundActivityStartsToken, timeoutExempt);
+                    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.
@@ -14523,7 +14504,8 @@
                         resultTo, resultCode, resultData, resultExtras, requiredPermissions, null,
                         null, OP_NONE, bOptions, serialized, sticky, -1, uid, realCallingUid,
                         realCallingPid, userId, allowBackgroundActivityStarts,
-                        backgroundActivityStartsToken, broadcastAllowList);
+                        backgroundActivityStartsToken, broadcastAllowList,
+                        null /* filterExtrasForReceiver */);
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
@@ -15177,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);
@@ -15292,7 +15274,7 @@
     @GuardedBy("this")
     final boolean canGcNowLocked() {
         for (BroadcastQueue q : mBroadcastQueues) {
-            if (!q.mParallelBroadcasts.isEmpty() || !q.mDispatcher.isIdle()) {
+            if (!q.isIdle()) {
                 return false;
             }
         }
@@ -16201,6 +16183,13 @@
         return mUserController.startUser(userId, /* foreground */ true, unlockListener);
     }
 
+    @Override
+    public boolean startUserInBackgroundOnSecondaryDisplay(int userId, int displayId) {
+        // Permission check done inside UserController.
+        return mUserController.startUserOnSecondaryDisplay(userId, displayId,
+                /* unlockListener= */ null);
+    }
+
     /**
      * Unlocks the given user.
      *
@@ -17058,7 +17047,9 @@
         public int broadcastIntent(Intent intent,
                 IIntentReceiver resultTo,
                 String[] requiredPermissions,
-                boolean serialized, int userId, int[] appIdAllowList, @Nullable Bundle bOptions) {
+                boolean serialized, int userId, int[] appIdAllowList,
+                @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+                @Nullable Bundle bOptions) {
             synchronized (ActivityManagerService.this) {
                 intent = verifyBroadcastLocked(intent);
 
@@ -17074,7 +17065,8 @@
                             AppOpsManager.OP_NONE, bOptions /*options*/, serialized,
                             false /*sticky*/, callingPid, callingUid, callingUid, callingPid,
                             userId, false /*allowBackgroundStarts*/,
-                            null /*tokenNeededForBackgroundActivityStarts*/, appIdAllowList);
+                            null /*tokenNeededForBackgroundActivityStarts*/,
+                            appIdAllowList, filterExtrasForReceiver);
                 } finally {
                     Binder.restoreCallingIdentity(origId);
                 }
@@ -17507,7 +17499,8 @@
             // sends to the activity. After this race issue between WM/ATMS and AMS is solved, this
             // workaround can be removed. (b/213288355)
             if (isNewPending) {
-                mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid);
+                mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid,
+                        OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
             }
             // We need to update the network rules for the app coming to the top state so that
             // it can access network when the device or the app is in a restricted state
@@ -17870,7 +17863,7 @@
                             pw.flush();
                         }
                         Slog.v(TAG, msg);
-                        queue.cancelDeferrals();
+                        queue.flush();
                         idle = false;
                     }
                 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 4bbfa3e..36908ce 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -49,10 +49,12 @@
 import android.app.IActivityController;
 import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
+import android.app.IProcessObserver;
 import android.app.IStopUserCallback;
 import android.app.IUidObserver;
 import android.app.IUserSwitchObserver;
 import android.app.KeyguardManager;
+import android.app.ProcessStateEnum;
 import android.app.ProfilerInfo;
 import android.app.RemoteServiceException.CrashedByAdbException;
 import android.app.UserSwitchObserver;
@@ -359,6 +361,8 @@
                     return runSetBgRestrictionLevel(pw);
                 case "get-bg-restriction-level":
                     return runGetBgRestrictionLevel(pw);
+                case "observe-foreground-process":
+                    return runGetCurrentForegroundProcess(pw, mInternal, mTaskInterface);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -892,6 +896,7 @@
         }
     }
 
+    // TODO(b/239982558): might need to support --displayId as well
     private int runProfile(PrintWriter pw) throws RemoteException {
         final PrintWriter err = getErrPrintWriter();
         String profileFile = null;
@@ -2034,26 +2039,42 @@
     int runStartUser(PrintWriter pw) throws RemoteException {
         boolean wait = false;
         String opt;
+        int displayId = Display.INVALID_DISPLAY;
         while ((opt = getNextOption()) != null) {
-            if ("-w".equals(opt)) {
-                wait = true;
-            } else {
-                getErrPrintWriter().println("Error: unknown option: " + opt);
-                return -1;
+            switch(opt) {
+                case "-w":
+                    wait = true;
+                    break;
+                case "--display":
+                    displayId = getDisplayIdFromNextArg();
+                    break;
+                default:
+                    getErrPrintWriter().println("Error: unknown option: " + opt);
+                    return -1;
             }
         }
         int userId = Integer.parseInt(getNextArgRequired());
 
         final ProgressWaiter waiter = wait ? new ProgressWaiter() : null;
-        boolean success = mInterface.startUserInBackgroundWithListener(userId, waiter);
+
+        boolean success;
+        String displaySuffix;
+
+        if (displayId == Display.INVALID_DISPLAY) {
+            success = mInterface.startUserInBackgroundWithListener(userId, waiter);
+            displaySuffix = "";
+        } else {
+            success = mInterface.startUserInBackgroundOnSecondaryDisplay(userId, displayId);
+            displaySuffix = " on display " + displayId;
+        }
         if (wait && success) {
             success = waiter.waitForFinish(USER_OPERATION_TIMEOUT_MS);
         }
 
         if (success) {
-            pw.println("Success: user started");
+            pw.println("Success: user started" + displaySuffix);
         } else {
-            getErrPrintWriter().println("Error: could not start user");
+            getErrPrintWriter().println("Error: could not start user" + displaySuffix);
         }
         return 0;
     }
@@ -2506,6 +2527,14 @@
         }
     }
 
+    private int getDisplayIdFromNextArg() {
+        int displayId = Integer.parseInt(getNextArgRequired());
+        if (displayId < 0) {
+            throw new IllegalArgumentException("--display must be a non-negative integer");
+        }
+        return displayId;
+    }
+
     int runGetConfig(PrintWriter pw) throws RemoteException {
         int days = -1;
         int displayId = Display.DEFAULT_DISPLAY;
@@ -2524,10 +2553,7 @@
             } else if (opt.equals("--device")) {
                 inclDevice = true;
             } else if (opt.equals("--display")) {
-                displayId = Integer.parseInt(getNextArgRequired());
-                if (displayId < 0) {
-                    throw new IllegalArgumentException("--display must be a non-negative integer");
-                }
+                displayId = getDisplayIdFromNextArg();
             } else {
                 getErrPrintWriter().println("Error: Unknown option: " + opt);
                 return -1;
@@ -3208,6 +3234,82 @@
         return -1;
     }
 
+    private int runGetCurrentForegroundProcess(PrintWriter pw,
+            IActivityManager iam, IActivityTaskManager iatm)
+            throws RemoteException {
+
+        ProcessObserver observer = new ProcessObserver(pw, iam, iatm, mInternal);
+        iam.registerProcessObserver(observer);
+
+        final InputStream mInput = getRawInputStream();
+        InputStreamReader converter = new InputStreamReader(mInput);
+        BufferedReader in = new BufferedReader(converter);
+        String line;
+        try {
+            while ((line = in.readLine()) != null) {
+                boolean addNewline = true;
+                if (line.length() <= 0) {
+                    addNewline = false;
+                } else if ("q".equals(line) || "quit".equals(line)) {
+                    break;
+                } else {
+                    pw.println("Invalid command: " + line);
+                }
+                if (addNewline) {
+                    pw.println("");
+                }
+                pw.flush();
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+            pw.flush();
+        } finally {
+            iam.unregisterProcessObserver(observer);
+        }
+        return 0;
+    }
+
+    static final class ProcessObserver extends IProcessObserver.Stub {
+
+        private PrintWriter mPw;
+        private IActivityManager mIam;
+        private IActivityTaskManager mIatm;
+        private ActivityManagerService mInternal;
+
+        ProcessObserver(PrintWriter mPw, IActivityManager mIam,
+                IActivityTaskManager mIatm, ActivityManagerService ams) {
+            this.mPw = mPw;
+            this.mIam = mIam;
+            this.mIatm = mIatm;
+            this.mInternal = ams;
+        }
+
+        @Override
+        public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
+            if (foregroundActivities) {
+                try {
+                    int prcState = mIam.getUidProcessState(uid, "android");
+                    int topPid = mInternal.getTopApp().getPid();
+                    if (prcState == ProcessStateEnum.TOP && topPid == pid) {
+                        mPw.println("New foreground process: " + pid);
+                    }
+                    mPw.flush();
+                } catch (RemoteException e) {
+                    mPw.println("Error occurred in binder call");
+                    mPw.flush();
+                }
+            }
+        }
+
+        @Override
+        public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
+        }
+
+        @Override
+        public void onProcessDied(int pid, int uid) {
+        }
+    }
+
     private int runSetMemoryFactor(PrintWriter pw) throws RemoteException {
         final String levelArg = getNextArgRequired();
         @MemFactor int level = ADJ_MEM_FACTOR_NOTHING;
@@ -3714,10 +3816,13 @@
             pw.println("      execution of that user if it is currently stopped.");
             pw.println("  get-current-user");
             pw.println("      Returns id of the current foreground user.");
-            pw.println("  start-user [-w] <USER_ID>");
+            pw.println("  start-user [-w] [--display DISPLAY_ID] <USER_ID>");
             pw.println("      Start USER_ID in background if it is currently stopped;");
             pw.println("      use switch-user if you want to start the user in foreground.");
             pw.println("      -w: wait for start-user to complete and the user to be unlocked.");
+            pw.println("      --display <DISPLAY_ID>: allows the user to launch activities in the");
+            pw.println("        given display, when supported (typically on automotive builds");
+            pw.println("        wherethe vehicle has multiple displays)");
             pw.println("  unlock-user <USER_ID>");
             pw.println("      Unlock the given user.  This will only work if the user doesn't");
             pw.println("      have an LSKF (PIN/pattern/password).");
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 aaaacef..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,234 +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 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);
     }
 
@@ -252,2265 +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(OomAdjuster.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(new Intent(r.intent), 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;
-        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;
-        }
-
-        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(
-                        OomAdjuster.OOM_ADJ_REASON_START_RECEIVER);
-            }
-        } else if (filter.receiverList.app != null) {
-            mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(filter.receiverList.app);
-        }
-
-        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,
-                        new Intent(r.intent), 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);
 
-    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(
-                            OomAdjuster.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);
-                        }
-                        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;
-            }
-        }
-
-        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;
-        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(), (r.alarm ? HostingRecord.TRIGGER_TYPE_ALARM
-                        : HostingRecord.TRIGGER_TYPE_UNKNOWN)),
-                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;
-    }
-
-
-    @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/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index ce4528b..18fbfde 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -55,6 +55,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
 
 /**
  * An active intent broadcast.
@@ -71,6 +72,8 @@
     final boolean ordered;  // serialize the send to receivers?
     final boolean sticky;   // originated from existing sticky data?
     final boolean alarm;    // originated from an alarm triggering?
+    final boolean pushMessage; // originated from a push message?
+    final boolean pushMessageOverQuota; // originated from a push message which was over quota?
     final boolean initialSticky; // initial broadcast from register to sticky?
     final int userId;       // user id this broadcast was for
     final String resolvedType; // the resolved data type
@@ -114,6 +117,11 @@
     @Nullable
     final IBinder mBackgroundActivityStartsToken;
 
+    // Filter the intent extras by using the rules of the package visibility before broadcasting
+    // the intent to the receiver.
+    @Nullable
+    final BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver;
+
     static final int IDLE = 0;
     static final int APP_RECEIVE = 1;
     static final int CALL_IN_RECEIVE = 2;
@@ -134,6 +142,7 @@
     ProcessRecord curApp;       // hosting application of current receiver.
     ComponentName curComponent; // the receiver class that is currently running.
     ActivityInfo curReceiver;   // info about the receiver that is currently running.
+    Bundle curFilteredExtras;   // the bundle that has been filtered by the package visibility rules
 
     boolean mIsReceiverAppRunning; // Was the receiver's app already running.
 
@@ -227,6 +236,9 @@
                         pw.println(curReceiver.applicationInfo.sourceDir);
             }
         }
+        if (curFilteredExtras != null) {
+            pw.print(" filtered extras: "); pw.println(curFilteredExtras);
+        }
         if (state != IDLE) {
             String stateStr = " (?)";
             switch (state) {
@@ -273,7 +285,8 @@
             BroadcastOptions _options, List _receivers, IIntentReceiver _resultTo, int _resultCode,
             String _resultData, Bundle _resultExtras, boolean _serialized, boolean _sticky,
             boolean _initialSticky, int _userId, boolean allowBackgroundActivityStarts,
-            @Nullable IBinder backgroundActivityStartsToken, boolean timeoutExempt) {
+            @Nullable IBinder backgroundActivityStartsToken, boolean timeoutExempt,
+            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
         if (_intent == null) {
             throw new NullPointerException("Can't construct with a null intent");
         }
@@ -309,6 +322,9 @@
         mBackgroundActivityStartsToken = backgroundActivityStartsToken;
         this.timeoutExempt = timeoutExempt;
         alarm = options != null && options.isAlarmBroadcast();
+        pushMessage = options != null && options.isPushMessagingBroadcast();
+        pushMessageOverQuota = options != null && options.isPushMessagingOverQuotaBroadcast();
+        this.filterExtrasForReceiver = filterExtrasForReceiver;
     }
 
     /**
@@ -362,6 +378,9 @@
         mBackgroundActivityStartsToken = from.mBackgroundActivityStartsToken;
         timeoutExempt = from.timeoutExempt;
         alarm = from.alarm;
+        pushMessage = from.pushMessage;
+        pushMessageOverQuota = from.pushMessageOverQuota;
+        filterExtrasForReceiver = from.filterExtrasForReceiver;
     }
 
     /**
@@ -397,7 +416,7 @@
                 requiredPermissions, excludedPermissions, excludedPackages, appOp, options,
                 splitReceivers, resultTo, resultCode, resultData, resultExtras, ordered, sticky,
                 initialSticky, userId, allowBackgroundActivityStarts,
-                mBackgroundActivityStartsToken, timeoutExempt);
+                mBackgroundActivityStartsToken, timeoutExempt, filterExtrasForReceiver);
         split.enqueueTime = this.enqueueTime;
         split.enqueueRealTime = this.enqueueRealTime;
         split.enqueueClockTime = this.enqueueClockTime;
@@ -476,7 +495,8 @@
                     requiredPermissions, excludedPermissions, excludedPackages, appOp, options,
                     uid2receiverList.valueAt(i), null /* _resultTo */,
                     resultCode, resultData, resultExtras, ordered, sticky, initialSticky, userId,
-                    allowBackgroundActivityStarts, mBackgroundActivityStartsToken, timeoutExempt);
+                    allowBackgroundActivityStarts, mBackgroundActivityStartsToken, timeoutExempt,
+                    filterExtrasForReceiver);
             br.enqueueTime = this.enqueueTime;
             br.enqueueRealTime = this.enqueueRealTime;
             br.enqueueClockTime = this.enqueueClockTime;
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/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 363c9d0..653b602 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -37,6 +37,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.EventLog;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import com.android.internal.annotations.GuardedBy;
@@ -904,7 +905,7 @@
                     }
 
                     if (!enable && opt.isFrozen()) {
-                        unfreezeAppLSP(process);
+                        unfreezeAppLSP(process, OomAdjuster.OOM_ADJ_REASON_NONE);
 
                         // Set freezerOverride *after* calling unfreezeAppLSP (it resets the flag)
                         opt.setFreezerOverride(true);
@@ -1214,11 +1215,11 @@
 
     // This will ensure app will be out of the freezer for at least mFreezerDebounceTimeout.
     @GuardedBy("mAm")
-    void unfreezeTemporarily(ProcessRecord app) {
+    void unfreezeTemporarily(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
         if (mUseFreezer) {
             synchronized (mProcLock) {
                 if (app.mOptRecord.isFrozen() || app.mOptRecord.isPendingFreeze()) {
-                    unfreezeAppLSP(app);
+                    unfreezeAppLSP(app, reason);
                     freezeAppAsyncLSP(app);
                 }
             }
@@ -1244,7 +1245,7 @@
     }
 
     @GuardedBy({"mAm", "mProcLock", "mFreezerLock"})
-    void unfreezeAppInternalLSP(ProcessRecord app) {
+    void unfreezeAppInternalLSP(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
         final int pid = app.getPid();
         final ProcessCachedOptimizerRecord opt = app.mOptRecord;
         if (opt.isPendingFreeze()) {
@@ -1325,14 +1326,14 @@
                     mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
                         pid,
                         (int) Math.min(opt.getFreezeUnfreezeTime() - freezeTime, Integer.MAX_VALUE),
-                        app.processName));
+                        new Pair<String, Integer>(app.processName, reason)));
         }
     }
 
     @GuardedBy({"mAm", "mProcLock"})
-    void unfreezeAppLSP(ProcessRecord app) {
+    void unfreezeAppLSP(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
         synchronized (mFreezerLock) {
-            unfreezeAppInternalLSP(app);
+            unfreezeAppInternalLSP(app, reason);
         }
     }
 
@@ -1343,25 +1344,14 @@
      * The caller of this function should still trigger updateOomAdj for AMS to unfreeze the app.
      * @param pid pid of the process to be unfrozen
      */
-    void unfreezeProcess(int pid) {
+    void unfreezeProcess(int pid, @OomAdjuster.OomAdjReason int reason) {
         synchronized (mFreezerLock) {
             ProcessRecord app = mFrozenProcesses.get(pid);
             if (app == null) {
                 return;
             }
             Slog.d(TAG_AM, "quick sync unfreeze " + pid);
-            try {
-                freezeBinder(pid, false);
-            } catch (RuntimeException e) {
-                Slog.e(TAG_AM, "Unable to quick unfreeze binder for " + pid);
-                return;
-            }
-
-            try {
-                Process.setProcessFrozen(pid, app.uid, false);
-            } catch (Exception e) {
-                Slog.e(TAG_AM, "Unable to quick unfreeze " + pid);
-            }
+            unfreezeAppLSP(app, reason);
         }
     }
 
@@ -1880,9 +1870,11 @@
                 case REPORT_UNFREEZE_MSG:
                     int pid = msg.arg1;
                     int frozenDuration = msg.arg2;
-                    String processName = (String) msg.obj;
+                    Pair<String, Integer> obj = (Pair<String, Integer>) msg.obj;
+                    String processName = obj.first;
+                    int reason = obj.second;
 
-                    reportUnfreeze(pid, frozenDuration, processName);
+                    reportUnfreeze(pid, frozenDuration, processName, reason);
                     break;
                 default:
                     return;
@@ -1893,7 +1885,7 @@
         private void rescheduleFreeze(final ProcessRecord proc, final String reason) {
             Slog.d(TAG_AM, "Reschedule freeze for process " + proc.getPid()
                     + " " + proc.processName + " (" + reason + ")");
-            unfreezeAppLSP(proc);
+            unfreezeAppLSP(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
             freezeAppAsyncLSP(proc);
         }
 
@@ -1981,7 +1973,8 @@
                         FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP,
                         pid,
                         name,
-                        unfrozenDuration);
+                        unfrozenDuration,
+                        FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__NONE);
             }
 
             try {
@@ -2011,12 +2004,13 @@
             } catch (Exception e) {
                 Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
                 synchronized (mProcLock) {
-                    unfreezeAppLSP(proc);
+                    unfreezeAppLSP(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
                 }
             }
         }
 
-        private void reportUnfreeze(int pid, int frozenDuration, String processName) {
+        private void reportUnfreeze(int pid, int frozenDuration, String processName,
+                @OomAdjuster.OomAdjReason int reason) {
 
             EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName);
 
@@ -2027,7 +2021,39 @@
                         FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__UNFREEZE_APP,
                         pid,
                         processName,
-                        frozenDuration);
+                        frozenDuration,
+                        getUnfreezeReasonCode(reason));
+            }
+        }
+
+        private int getUnfreezeReasonCode(@OomAdjuster.OomAdjReason int oomAdjReason) {
+            switch (oomAdjReason) {
+                case OomAdjuster.OOM_ADJ_REASON_ACTIVITY:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__ACTIVITY;
+                case OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__FINISH_RECEIVER;
+                case OomAdjuster.OOM_ADJ_REASON_START_RECEIVER:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__START_RECEIVER;
+                case OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__BIND_SERVICE;
+                case OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__UNBIND_SERVICE;
+                case OomAdjuster.OOM_ADJ_REASON_START_SERVICE:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__START_SERVICE;
+                case OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__GET_PROVIDER;
+                case OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__REMOVE_PROVIDER;
+                case OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__UI_VISIBILITY;
+                case OomAdjuster.OOM_ADJ_REASON_ALLOWLIST:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__ALLOWLIST;
+                case OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__PROCESS_BEGIN;
+                case OomAdjuster.OOM_ADJ_REASON_PROCESS_END:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__PROCESS_END;
+                default:
+                    return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__NONE;
             }
         }
 
@@ -2041,7 +2067,7 @@
                 ProcessRecord app = mFrozenProcesses.get(pid);
                 if (app != null) {
                     Slog.i(TAG_AM, app.processName + " (" + pid + ") holds blocking file lock");
-                    unfreezeAppLSP(app);
+                    unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/HostingRecord.java b/services/core/java/com/android/server/am/HostingRecord.java
index efc2a27..2498f76 100644
--- a/services/core/java/com/android/server/am/HostingRecord.java
+++ b/services/core/java/com/android/server/am/HostingRecord.java
@@ -30,6 +30,8 @@
 import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SERVICE;
 import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SYSTEM;
 import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_TOP_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE_OVER_QUOTA;
 import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_ALARM;
 import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN;
 import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TYPE__UNKNOWN;
@@ -93,6 +95,8 @@
 
     public static final String TRIGGER_TYPE_UNKNOWN = "unknown";
     public static final String TRIGGER_TYPE_ALARM = "alarm";
+    public static final String TRIGGER_TYPE_PUSH_MESSAGE = "push_message";
+    public static final String TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA = "push_message_over_quota";
 
     @NonNull private final String mHostingType;
     private final String mHostingName;
@@ -308,6 +312,10 @@
         switch(triggerType) {
             case TRIGGER_TYPE_ALARM:
                 return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_ALARM;
+            case TRIGGER_TYPE_PUSH_MESSAGE:
+                return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE;
+            case TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA:
+                return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE_OVER_QUOTA;
             default:
                 return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN;
         }
diff --git a/services/core/java/com/android/server/am/LmkdStatsReporter.java b/services/core/java/com/android/server/am/LmkdStatsReporter.java
index 9158891..4380b42 100644
--- a/services/core/java/com/android/server/am/LmkdStatsReporter.java
+++ b/services/core/java/com/android/server/am/LmkdStatsReporter.java
@@ -50,7 +50,8 @@
      * Logs the event when LMKD kills a process to reduce memory pressure.
      * Code: LMK_KILL_OCCURRED = 51
      */
-    public static void logKillOccurred(DataInputStream inputData) {
+    public static void logKillOccurred(DataInputStream inputData, int totalForegroundServices,
+            int procsWithForegroundServices) {
         try {
             final long pgFault = inputData.readLong();
             final long pgMajFault = inputData.readLong();
@@ -67,11 +68,10 @@
             final int thrashing = inputData.readInt();
             final int maxThrashing = inputData.readInt();
             final String procName = inputData.readUTF();
-
             FrameworkStatsLog.write(FrameworkStatsLog.LMK_KILL_OCCURRED, uid, procName, oomScore,
                     pgFault, pgMajFault, rssInBytes, cacheInBytes, swapInBytes, processStartTimeNS,
                     minOomScore, freeMemKb, freeSwapKb, mapKillReason(killReason), thrashing,
-                    maxThrashing);
+                    maxThrashing, totalForegroundServices, procsWithForegroundServices);
         } catch (IOException e) {
             Slog.e(TAG, "Invalid buffer data. Failed to log LMK_KILL_OCCURRED");
             return;
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 36be6ff..de28be0 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -447,7 +447,7 @@
      */
     @GuardedBy({"mService", "mProcLock"})
     private boolean performUpdateOomAdjLSP(ProcessRecord app, int cachedAdj,
-            ProcessRecord topApp, long now) {
+            ProcessRecord topApp, long now, @OomAdjReason int oomAdjReason) {
         if (app.getThread() == null) {
             return false;
         }
@@ -491,7 +491,7 @@
             }
         }
 
-        return applyOomAdjLSP(app, false, now, SystemClock.elapsedRealtime());
+        return applyOomAdjLSP(app, false, now, SystemClock.elapsedRealtime(), oomAdjReason);
     }
 
     /**
@@ -591,7 +591,7 @@
         mPendingProcessSet.remove(app);
         app.mOptRecord.setLastOomAdjChangeReason(oomAdjReason);
         boolean success = performUpdateOomAdjLSP(app, cachedAdj, topApp,
-                SystemClock.uptimeMillis());
+                SystemClock.uptimeMillis(), oomAdjReason);
         // The 'app' here itself might or might not be in the cycle, for example,
         // the case A <=> B vs. A -> B <=> C; anyway, if we spot a cycle here, re-compute them.
         if (!success || (wasCached == state.isCached() && oldAdj != ProcessList.INVALID_ADJ
@@ -644,7 +644,7 @@
             processes.add(app);
             assignCachedAdjIfNecessary(processes);
             applyOomAdjLSP(app, false, SystemClock.uptimeMillis(),
-                    SystemClock.elapsedRealtime());
+                    SystemClock.elapsedRealtime(), oomAdjReason);
         }
         mTmpProcessList.clear();
         mService.mOomAdjProfiler.oomAdjEnded();
@@ -940,7 +940,8 @@
         mNumNonCachedProcs = 0;
         mNumCachedHiddenProcs = 0;
 
-        boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids);
+        boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids,
+                oomAdjReason);
         mNumServiceProcs = mNewNumServiceProcs;
 
         if (mService.mAlwaysFinishActivities) {
@@ -1118,7 +1119,7 @@
 
     @GuardedBy({"mService", "mProcLock"})
     private boolean updateAndTrimProcessLSP(final long now, final long nowElapsed,
-            final long oldTime, final ActiveUids activeUids) {
+            final long oldTime, final ActiveUids activeUids, @OomAdjReason int oomAdjReason) {
         ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
         final int numLru = lruList.size();
 
@@ -1146,7 +1147,7 @@
             if (!app.isKilledByAm() && app.getThread() != null) {
                 // We don't need to apply the update for the process which didn't get computed
                 if (state.getCompletedAdjSeq() == mAdjSeq) {
-                    applyOomAdjLSP(app, true, now, nowElapsed);
+                    applyOomAdjLSP(app, true, now, nowElapsed, oomAdjReason);
                 }
 
                 final ProcessServiceRecord psr = app.mServices;
@@ -2633,7 +2634,7 @@
     /** Applies the computed oomadj, procstate and sched group values and freezes them in set* */
     @GuardedBy({"mService", "mProcLock"})
     private boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now,
-            long nowElapsed) {
+            long nowElapsed, @OomAdjReason int oomAdjReson) {
         boolean success = true;
         final ProcessStateRecord state = app.mState;
         final UidRecord uidRec = app.getUidRecord();
@@ -2792,7 +2793,7 @@
             changes |= ActivityManagerService.ProcessChangeItem.CHANGE_ACTIVITIES;
         }
 
-        updateAppFreezeStateLSP(app);
+        updateAppFreezeStateLSP(app, oomAdjReson);
 
         if (state.getReportedProcState() != state.getCurProcState()) {
             state.setReportedProcState(state.getCurProcState());
@@ -3153,7 +3154,7 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    private void updateAppFreezeStateLSP(ProcessRecord app) {
+    private void updateAppFreezeStateLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
         if (!mCachedAppOptimizer.useFreezer()) {
             return;
         }
@@ -3165,7 +3166,7 @@
         final ProcessCachedOptimizerRecord opt = app.mOptRecord;
         // if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze
         if (opt.isFrozen() && opt.shouldNotFreeze()) {
-            mCachedAppOptimizer.unfreezeAppLSP(app);
+            mCachedAppOptimizer.unfreezeAppLSP(app, oomAdjReason);
             return;
         }
 
@@ -3175,7 +3176,7 @@
                 && !opt.shouldNotFreeze()) {
             mCachedAppOptimizer.freezeAppAsyncLSP(app);
         } else if (state.getSetAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
-            mCachedAppOptimizer.unfreezeAppLSP(app);
+            mCachedAppOptimizer.unfreezeAppLSP(app, oomAdjReason);
         }
     }
 }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 42792bf6..ccbca76 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -814,7 +814,12 @@
                                                 < LmkdStatsReporter.KILL_OCCURRED_MSG_SIZE) {
                                             return false;
                                         }
-                                        LmkdStatsReporter.logKillOccurred(inputData);
+                                        Pair<Integer, Integer> temp = getNumForegroundServices();
+                                        final int totalForegroundServices = temp.first;
+                                        final int procsWithForegroundServices = temp.second;
+                                        LmkdStatsReporter.logKillOccurred(inputData,
+                                                totalForegroundServices,
+                                                procsWithForegroundServices);
                                         return true;
                                     case LMK_STATE_CHANGED:
                                         if (receivedLen
@@ -5123,6 +5128,26 @@
         }
     }
 
+    /**
+     * Get the number of foreground services in all processes and number of processes that have
+     * foreground service within.
+     */
+    Pair<Integer, Integer> getNumForegroundServices() {
+        int numForegroundServices = 0;
+        int procs = 0;
+        synchronized (mService) {
+            for (int i = 0, size = mLruProcesses.size(); i < size; i++) {
+                ProcessRecord pr = mLruProcesses.get(i);
+                int numFgs = pr.mServices.getNumForegroundServices();
+                if (numFgs > 0) {
+                    numForegroundServices += numFgs;
+                    procs++;
+                }
+            }
+        }
+        return new Pair<>(numForegroundServices, procs);
+    }
+
     private final class ImperceptibleKillRunner extends IUidObserver.Stub {
         private static final String EXTRA_PID = "pid";
         private static final String EXTRA_UID = "uid";
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 9951e98..67eb675 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -180,6 +180,16 @@
         mRepFgServiceTypes = foregroundServiceTypes;
     }
 
+    int getNumForegroundServices() {
+        int count = 0;
+        for (int i = 0, serviceCount = mServices.size(); i < serviceCount; i++) {
+            if (mServices.valueAt(i).isForeground) {
+                count++;
+            }
+        }
+        return count;
+    }
+
     void updateHasTopStartedAlmostPerceptibleServices() {
         mHasTopStartedAlmostPerceptibleServices = false;
         mLastTopStartedAlmostPerceptibleBindRequestUptimeMs = 0;
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 262436d..eb1fd3a 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -497,6 +497,7 @@
     @GuardedBy({"mService", "mProcLock"})
     void setCurAdj(int curAdj) {
         mCurAdj = curAdj;
+        mApp.getWindowProcessController().setCurrentAdj(curAdj);
     }
 
     @GuardedBy(anyOf = {"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index d0817b0..470de8c 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -99,6 +99,7 @@
         DeviceConfig.NAMESPACE_SWCODEC_NATIVE,
         DeviceConfig.NAMESPACE_TETHERING,
         DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE,
+        DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE,
         DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE,
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 0c0ae7d..a1f3dae 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -16,9 +16,11 @@
 
 package com.android.server.am;
 
+import static android.Manifest.permission.CREATE_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.MANAGE_USERS;
 import static android.app.ActivityManager.STOP_USER_ON_SWITCH_DEFAULT;
 import static android.app.ActivityManager.STOP_USER_ON_SWITCH_TRUE;
 import static android.app.ActivityManager.StopUserOnSwitch;
@@ -69,6 +71,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackagePartitions;
 import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Bundle;
@@ -100,6 +103,7 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
+import android.view.Display;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -107,6 +111,7 @@
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.FactoryResetter;
 import com.android.server.FgThread;
@@ -1071,6 +1076,12 @@
                         Binder.getCallingPid(), UserHandle.USER_ALL);
             });
         }
+
+        // TODO(b/239982558): for now we're just updating the user's visibility, but most likely
+        // we'll need to remove this call and handle that as part of the user state workflow
+        // instead.
+        // TODO(b/240613396) also check if multi-display is supported
+        mInjector.getUserManagerInternal().assignUserToDisplay(userId, Display.INVALID_DISPLAY);
     }
 
     private void finishUserStopping(final int userId, final UserState uss,
@@ -1357,12 +1368,14 @@
         List<UserInfo> profilesToStart = new ArrayList<>(profiles.size());
         for (UserInfo user : profiles) {
             if ((user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED
-                    && user.id != currentUserId && !user.isQuietModeEnabled()) {
+                    && user.id != currentUserId
+                    && shouldStartWithParent(user)) {
                 profilesToStart.add(user);
             }
         }
         final int profilesToStartSize = profilesToStart.size();
         int i = 0;
+        // TODO(b/239982558): pass displayId
         for (; i < profilesToStartSize && i < (getMaxRunningUsers() - 1); ++i) {
             startUser(profilesToStart.get(i).id, /* foreground= */ false);
         }
@@ -1371,6 +1384,14 @@
         }
     }
 
+    private boolean shouldStartWithParent(UserInfo user) {
+        final UserProperties properties = mInjector.getUserManagerInternal()
+                .getUserProperties(user.id);
+        return (properties != null && properties.getStartWithParent())
+                && !user.isQuietModeEnabled();
+    }
+
+    // TODO(b/239982558): might need to infer the display id based on parent user
     /**
      * Starts a user only if it's a profile, with a more relaxed permission requirement:
      * {@link android.Manifest.permission#MANAGE_USERS} or
@@ -1399,7 +1420,8 @@
             return false;
         }
 
-        return startUserNoChecks(userId, /* foreground= */ false, /* unlockListener= */ null);
+        return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, /* foreground= */ false,
+                /* unlockListener= */ null);
     }
 
     @VisibleForTesting
@@ -1445,26 +1467,70 @@
             @Nullable IProgressListener unlockListener) {
         checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "startUser");
 
-        return startUserNoChecks(userId, foreground, unlockListener);
+        return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, foreground, unlockListener);
     }
 
-    private boolean startUserNoChecks(final @UserIdInt int userId, final boolean foreground,
+    // TODO(b/239982558): add javadoc (need to wait until the intents / SystemService callbacks are
+    // defined
+    boolean startUserOnSecondaryDisplay(@UserIdInt int userId, int displayId,
+            @Nullable IProgressListener unlockListener) {
+        checkCallingHasOneOfThosePermissions("startUserOnSecondaryDisplay",
+                MANAGE_USERS, CREATE_USERS);
+
+        // DEFAULT_DISPLAY is used for "regular" start user operations
+        Preconditions.checkArgument(displayId != Display.DEFAULT_DISPLAY,
+                "Cannot use DEFAULT_DISPLAY");
+
+        try {
+            return startUserNoChecks(userId, displayId, /* foreground= */ false, unlockListener);
+        } catch (RuntimeException e) {
+            Slogf.w(TAG, "startUserOnSecondaryDisplay(%d, %d) failed: %s", userId, displayId, e);
+            return false;
+        }
+    }
+
+    private boolean startUserNoChecks(@UserIdInt int userId, int displayId, boolean foreground,
             @Nullable IProgressListener unlockListener) {
         TimingsTraceAndSlog t = new TimingsTraceAndSlog();
 
-        t.traceBegin("UserController.startUser-" + userId + "-" + (foreground ? "fg" : "bg"));
+        t.traceBegin("UserController.startUser-" + userId
+                + (displayId == Display.DEFAULT_DISPLAY ? "" : "-display-" + displayId)
+                + "-" + (foreground ? "fg" : "bg"));
         try {
-            return startUserInternal(userId, foreground, unlockListener, t);
+            return startUserInternal(userId, displayId, foreground, unlockListener, t);
         } finally {
             t.traceEnd();
         }
     }
 
-    private boolean startUserInternal(@UserIdInt int userId, boolean foreground,
+    private boolean startUserInternal(@UserIdInt int userId, int displayId, boolean foreground,
             @Nullable IProgressListener unlockListener, @NonNull TimingsTraceAndSlog t) {
         if (DEBUG_MU) {
-            Slogf.i(TAG, "Starting user %d%s", userId, foreground ? " in foreground" : "");
+            Slogf.i(TAG, "Starting user %d on display %d %s", userId, displayId,
+                    foreground ? " in foreground" : "");
         }
+
+        // TODO(b/239982558): move logic below to a different class (like DisplayAssignmentManager)
+        if (displayId != Display.DEFAULT_DISPLAY) {
+            // This is called by startUserOnSecondaryDisplay()
+            if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) {
+                // TODO(b/239824814): add CTS test and/or unit test for all exceptional cases
+                throw new UnsupportedOperationException("Not supported by device");
+            }
+
+            // TODO(b/239982558): call DisplayManagerInternal to check if display is valid instead
+            Preconditions.checkArgument(displayId > 0, "Invalid display id (%d)", displayId);
+            Preconditions.checkArgument(userId != UserHandle.USER_SYSTEM, "Cannot start system user"
+                    + " on secondary display (%d)", displayId);
+            Preconditions.checkArgument(!foreground, "Cannot start user %d in foreground AND "
+                    + "on secondary display (%d)", userId, displayId);
+
+            // TODO(b/239982558): for now we're just updating the user's visibility, but most likely
+            // we'll need to remove this call and handle that as part of the user state workflow
+            // instead.
+            mInjector.getUserManagerInternal().assignUserToDisplay(userId, displayId);
+        }
+
         EventLog.writeEvent(EventLogTags.UC_START_USER_INTERNAL, userId);
 
         final int callingUid = Binder.getCallingUid();
@@ -1519,6 +1585,7 @@
                 return false;
             }
 
+            // TODO(b/239982558): might need something similar for bg users on secondary display
             if (foreground && isUserSwitchUiEnabled()) {
                 t.traceBegin("startFreezingScreen");
                 mInjector.getWindowManager().startFreezingScreen(
@@ -2568,15 +2635,24 @@
     }
 
     private void checkCallingPermission(String permission, String methodName) {
-        if (mInjector.checkCallingPermission(permission)
-                != PackageManager.PERMISSION_GRANTED) {
-            String msg = "Permission denial: " + methodName
-                    + "() from pid=" + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid()
-                    + " requires " + permission;
-            Slogf.w(TAG, msg);
-            throw new SecurityException(msg);
+        checkCallingHasOneOfThosePermissions(methodName, permission);
+    }
+
+    private void checkCallingHasOneOfThosePermissions(String methodName, String...permissions) {
+        for (String permission : permissions) {
+            if (mInjector.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
+                return;
+            }
         }
+        String msg = "Permission denial: " + methodName
+                + "() from pid=" + Binder.getCallingPid()
+                + ", uid=" + Binder.getCallingUid()
+                + " requires "
+                + (permissions.length == 1
+                        ? permissions[0]
+                        : "one of " + Arrays.toString(permissions));
+        Slogf.w(TAG, msg);
+        throw new SecurityException(msg);
     }
 
     private void enforceShellRestriction(String restriction, @UserIdInt int userId) {
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 854b818..134e206 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -99,6 +99,7 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Service to manage game related features.
@@ -119,7 +120,7 @@
     static final int SET_GAME_STATE = 4;
     static final int CANCEL_GAME_LOADING_MODE = 5;
     static final int WRITE_GAME_MODE_INTERVENTION_LIST_FILE = 6;
-    static final int WRITE_SETTINGS_DELAY = 10 * 1000;  // 10 seconds
+    static final int WRITE_DELAY_MILLIS = 10 * 1000;  // 10 seconds
     static final int LOADING_BOOST_MAX_DURATION = 5 * 1000;  // 5 seconds
 
     private static final String PACKAGE_NAME_MSG_KEY = "packageName";
@@ -130,8 +131,8 @@
     private final Context mContext;
     private final Object mLock = new Object();
     private final Object mDeviceConfigLock = new Object();
-    private final Object mOverrideConfigLock = new Object();
-    private final Handler mHandler;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    final Handler mHandler;
     private final PackageManager mPackageManager;
     private final UserManager mUserManager;
     private final PowerManagerInternal mPowerManagerInternal;
@@ -143,8 +144,6 @@
     private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>();
     @GuardedBy("mDeviceConfigLock")
     private final ArrayMap<String, GamePackageConfiguration> mConfigs = new ArrayMap<>();
-    @GuardedBy("mOverrideConfigLock")
-    private final ArrayMap<String, GamePackageConfiguration> mOverrideConfigs = new ArrayMap<>();
     @Nullable
     private final GameServiceController mGameServiceController;
 
@@ -236,7 +235,7 @@
         final int userId = ActivityManager.getCurrentUser();
         String[] packageList = getInstalledGamePackageNames(userId);
         for (final String packageName : packageList) {
-            pw.println(getInterventionList(packageName));
+            pw.println(getInterventionList(packageName, userId));
         }
     }
 
@@ -258,14 +257,13 @@
                     if (userId < 0) {
                         Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId);
                         synchronized (mLock) {
-                            removeMessages(WRITE_SETTINGS, msg.obj);
+                            removeEqualMessages(WRITE_SETTINGS, msg.obj);
                         }
                         break;
                     }
-
                     Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
                     synchronized (mLock) {
-                        removeMessages(WRITE_SETTINGS, msg.obj);
+                        removeEqualMessages(WRITE_SETTINGS, msg.obj);
                         if (mSettings.containsKey(userId)) {
                             GameManagerSettings userSettings = mSettings.get(userId);
                             userSettings.writePersistentDataLocked();
@@ -279,8 +277,8 @@
                     if (userId < 0) {
                         Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId);
                         synchronized (mLock) {
-                            removeMessages(WRITE_SETTINGS, msg.obj);
-                            removeMessages(REMOVE_SETTINGS, msg.obj);
+                            removeEqualMessages(WRITE_SETTINGS, msg.obj);
+                            removeEqualMessages(REMOVE_SETTINGS, msg.obj);
                         }
                         break;
                     }
@@ -288,8 +286,8 @@
                     synchronized (mLock) {
                         // Since the user was removed, ignore previous write message
                         // and do write here.
-                        removeMessages(WRITE_SETTINGS, msg.obj);
-                        removeMessages(REMOVE_SETTINGS, msg.obj);
+                        removeEqualMessages(WRITE_SETTINGS, msg.obj);
+                        removeEqualMessages(REMOVE_SETTINGS, msg.obj);
                         if (mSettings.containsKey(userId)) {
                             final GameManagerSettings userSettings = mSettings.get(userId);
                             mSettings.remove(userId);
@@ -299,7 +297,7 @@
                     break;
                 }
                 case POPULATE_GAME_MODE_SETTINGS: {
-                    removeMessages(POPULATE_GAME_MODE_SETTINGS, msg.obj);
+                    removeEqualMessages(POPULATE_GAME_MODE_SETTINGS, msg.obj);
                     final int userId = (int) msg.obj;
                     final String[] packageNames = getInstalledGamePackageNames(userId);
                     updateConfigsForUser(userId, false /*checkGamePackage*/, packageNames);
@@ -345,13 +343,13 @@
                     if (userId < 0) {
                         Slog.wtf(TAG, "Attempt to write setting for invalid user: " + userId);
                         synchronized (mLock) {
-                            removeMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, null);
+                            removeEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, msg.obj);
                         }
                         break;
                     }
 
                     Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
-                    removeMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, null);
+                    removeEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, msg.obj);
                     writeGameModeInterventionsToFile(userId);
                     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                     break;
@@ -446,8 +444,7 @@
     /**
      * GamePackageConfiguration manages all game mode config details for its associated package.
      */
-    @VisibleForTesting
-    public class GamePackageConfiguration {
+    public static class GamePackageConfiguration {
         public static final String TAG = "GameManagerService_GamePackageConfiguration";
 
         /**
@@ -499,23 +496,29 @@
         private boolean mAllowAngle;
         private boolean mAllowFpsOverride;
 
-        GamePackageConfiguration(String packageName, int userId) {
+        GamePackageConfiguration(String packageName) {
             mPackageName = packageName;
+        }
+
+        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 = mPackageManager.getApplicationInfoAsUser(packageName,
+                final ApplicationInfo ai = packageManager.getApplicationInfoAsUser(packageName,
                         PackageManager.GET_META_DATA, userId);
-                if (!parseInterventionFromXml(ai, packageName)) {
+                if (!parseInterventionFromXml(packageManager, ai, packageName)) {
                     if (ai.metaData != null) {
                         mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
                         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) {
@@ -538,16 +541,17 @@
             }
         }
 
-        private boolean parseInterventionFromXml(ApplicationInfo ai, String packageName) {
+        private boolean parseInterventionFromXml(PackageManager packageManager, ApplicationInfo ai,
+                String packageName) {
             boolean xmlFound = false;
-            try (XmlResourceParser parser = ai.loadXmlMetaData(mPackageManager,
+            try (XmlResourceParser parser = ai.loadXmlMetaData(packageManager,
                     METADATA_GAME_MODE_CONFIG)) {
                 if (parser == null) {
                     Slog.v(TAG, "No " + METADATA_GAME_MODE_CONFIG
                             + " meta-data found for package " + mPackageName);
                 } else {
                     xmlFound = true;
-                    final Resources resources = mPackageManager.getResourcesForApplication(
+                    final Resources resources = packageManager.getResourcesForApplication(
                             packageName);
                     final AttributeSet attributeSet = Xml.asAttributeSet(parser);
                     int type;
@@ -596,7 +600,6 @@
          * GameModeConfiguration contains all the values for all the interventions associated with
          * a game mode.
          */
-        @VisibleForTesting
         public class GameModeConfiguration {
             public static final String TAG = "GameManagerService_GameModeConfiguration";
             public static final String MODE_KEY = "mode";
@@ -613,8 +616,8 @@
             private final @GameMode int mGameMode;
             private float mScaling = DEFAULT_SCALING;
             private String mFps = DEFAULT_FPS;
-            private final boolean mUseAngle;
-            private final int mLoadingBoostDuration;
+            private boolean mUseAngle;
+            private int mLoadingBoostDuration;
 
             GameModeConfiguration(int gameMode) {
                 mGameMode = gameMode;
@@ -657,11 +660,15 @@
                 return GameManagerService.getFpsInt(mFps);
             }
 
-            public boolean getUseAngle() {
+            synchronized String getFpsStr() {
+                return mFps;
+            }
+
+            public synchronized boolean getUseAngle() {
                 return mUseAngle;
             }
 
-            public int getLoadingBoostDuration() {
+            public synchronized int getLoadingBoostDuration() {
                 return mLoadingBoostDuration;
             }
 
@@ -673,6 +680,14 @@
                 mFps = fpsStr;
             }
 
+            public synchronized void setUseAngle(boolean useAngle) {
+                mUseAngle = useAngle;
+            }
+
+            public synchronized void setLoadingBoostDuration(int loadingBoostDuration) {
+                mLoadingBoostDuration = loadingBoostDuration;
+            }
+
             public boolean isActive() {
                 return (mGameMode == GameManager.GAME_MODE_STANDARD
                         || mGameMode == GameManager.GAME_MODE_PERFORMANCE
@@ -759,7 +774,7 @@
         }
 
         /**
-         * Inserts a new GameModeConfiguration
+         * Inserts a new GameModeConfiguration.
          */
         public void addModeConfig(GameModeConfiguration config) {
             if (config.isActive()) {
@@ -772,6 +787,15 @@
             }
         }
 
+        /**
+         * Removes the GameModeConfiguration.
+         */
+        public void removeModeConfig(int mode) {
+            synchronized (mModeConfigLock) {
+                mModeConfigs.remove(mode);
+            }
+        }
+
         public boolean isActive() {
             synchronized (mModeConfigLock) {
                 return mModeConfigs.size() > 0 || mBatteryModeOptedIn || mPerfModeOptedIn;
@@ -823,7 +847,8 @@
 
         @Override
         public void onUserStarting(@NonNull TargetUser user) {
-            mService.onUserStarting(user);
+            mService.onUserStarting(user,
+                    Environment.getDataSystemDeDirectory(user.getUserIdentifier()));
         }
 
         @Override
@@ -860,7 +885,10 @@
     }
 
     private @GameMode int[] getAvailableGameModesUnchecked(String packageName) {
-        final GamePackageConfiguration config = getConfig(packageName);
+        final GamePackageConfiguration config;
+        synchronized (mDeviceConfigLock) {
+            config = mConfigs.get(packageName);
+        }
         if (config == null) {
             return new int[]{};
         }
@@ -987,18 +1015,11 @@
             }
             GameManagerSettings userSettings = mSettings.get(userId);
             userSettings.setGameModeLocked(packageName, gameMode);
-            final Message msg = mHandler.obtainMessage(WRITE_SETTINGS);
-            msg.obj = userId;
-            if (!mHandler.hasEqualMessages(WRITE_SETTINGS, userId)) {
-                mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY);
-            }
         }
         updateInterventions(packageName, gameMode, userId);
-        final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE);
-        msg.obj = userId;
-        if (!mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, userId)) {
-            mHandler.sendMessage(msg);
-        }
+        sendUserMessage(userId, WRITE_SETTINGS, "SET_GAME_MODE", WRITE_DELAY_MILLIS);
+        sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
+                "SET_GAME_MODE", 0 /*delayMillis*/);
     }
 
     /**
@@ -1033,7 +1054,6 @@
      * the boost duration. If no configuration is available for the selected package or mode, the
      * default is returned.
      */
-    @VisibleForTesting
     public int getLoadingBoostDuration(String packageName, int userId)
             throws SecurityException {
         final int gameMode = getGameMode(packageName, userId);
@@ -1165,7 +1185,7 @@
     }
 
     float getResolutionScalingFactorInternal(String packageName, int gameMode, int userId) {
-        final GamePackageConfiguration packageConfig = getConfig(packageName);
+        final GamePackageConfiguration packageConfig = getConfig(packageName, userId);
         if (packageConfig == null) {
             return GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING;
         }
@@ -1186,22 +1206,46 @@
         if (mGameServiceController != null) {
             mGameServiceController.onBootComplete();
         }
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
+                    synchronized (mLock) {
+                        // Note that the max wait time of broadcast is 10s (see
+                        // {@ShutdownThread#MAX_BROADCAST_TIMEMAX_BROADCAST_TIME}) currently so
+                        // this can be optional only if we have message delay plus processing
+                        // time significant smaller to prevent data loss.
+                        for (Map.Entry<Integer, GameManagerSettings> entry : mSettings.entrySet()) {
+                            final int userId = entry.getKey();
+                            sendUserMessage(userId, WRITE_SETTINGS,
+                                    Intent.ACTION_SHUTDOWN, 0 /*delayMillis*/);
+                            sendUserMessage(userId,
+                                    WRITE_GAME_MODE_INTERVENTION_LIST_FILE, Intent.ACTION_SHUTDOWN,
+                                    0 /*delayMillis*/);
+                        }
+                    }
+                }
+            }
+        }, new IntentFilter(Intent.ACTION_SHUTDOWN));
     }
 
-    void onUserStarting(@NonNull TargetUser user) {
-        final int userId = user.getUserIdentifier();
+    private void sendUserMessage(int userId, int what, String eventForLog, int delayMillis) {
+        Message msg = mHandler.obtainMessage(what, userId);
+        if (!mHandler.sendMessageDelayed(msg, delayMillis)) {
+            Slog.e(TAG, "Failed to send user message " + what + " on " + eventForLog);
+        }
+    }
 
+    void onUserStarting(@NonNull TargetUser user, File settingDataDir) {
+        final int userId = user.getUserIdentifier();
         synchronized (mLock) {
             if (!mSettings.containsKey(userId)) {
-                GameManagerSettings userSettings =
-                        new GameManagerSettings(Environment.getDataSystemDeDirectory(userId));
+                GameManagerSettings userSettings = new GameManagerSettings(settingDataDir);
                 mSettings.put(userId, userSettings);
                 userSettings.readPersistentDataLocked();
             }
         }
-        final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS);
-        msg.obj = userId;
-        mHandler.sendMessage(msg);
+        sendUserMessage(userId, POPULATE_GAME_MODE_SETTINGS, "ON_USER_STARTING", 0 /*delayMillis*/);
 
         if (mGameServiceController != null) {
             mGameServiceController.notifyUserStarted(user);
@@ -1221,9 +1265,7 @@
             if (!mSettings.containsKey(userId)) {
                 return;
             }
-            final Message msg = mHandler.obtainMessage(REMOVE_SETTINGS);
-            msg.obj = userId;
-            mHandler.sendMessage(msg);
+            sendUserMessage(userId, REMOVE_SETTINGS, "ON_USER_STOPPING", 0 /*delayMillis*/);
         }
 
         if (mGameServiceController != null) {
@@ -1237,15 +1279,14 @@
             synchronized (mLock) {
                 final int fromUserId = from.getUserIdentifier();
                 if (mSettings.containsKey(fromUserId)) {
-                    final Message msg = mHandler.obtainMessage(REMOVE_SETTINGS);
-                    msg.obj = fromUserId;
-                    mHandler.sendMessage(msg);
+                    sendUserMessage(fromUserId, REMOVE_SETTINGS, "ON_USER_SWITCHING",
+                            0 /*delayMillis*/);
                 }
             }
         }
-        final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS);
-        msg.obj = toUserId;
-        mHandler.sendMessage(msg);
+
+        sendUserMessage(toUserId, POPULATE_GAME_MODE_SETTINGS, "ON_USER_SWITCHING",
+                0 /*delayMillis*/);
 
         if (mGameServiceController != null) {
             mGameServiceController.notifyNewForegroundUser(to);
@@ -1265,7 +1306,7 @@
         }
     }
 
-    private int modeToBitmask(@GameMode int gameMode) {
+    private static int modeToBitmask(@GameMode int gameMode) {
         return (1 << gameMode);
     }
 
@@ -1305,7 +1346,7 @@
             resetFps(packageName, userId);
             return;
         }
-        final GamePackageConfiguration packageConfig = getConfig(packageName);
+        final GamePackageConfiguration packageConfig = getConfig(packageName, userId);
         if (packageConfig == null) {
             Slog.v(TAG, "Package configuration not found for " + packageName);
             return;
@@ -1318,7 +1359,7 @@
     }
 
     /**
-     * Set the override Game Mode Configuration.
+     * Set the Game Mode Configuration override.
      * Update the config if exists, create one if not.
      */
     @VisibleForTesting
@@ -1326,95 +1367,86 @@
     public void setGameModeConfigOverride(String packageName, @UserIdInt int userId,
             @GameMode int gameMode, String fpsStr, String scaling) throws SecurityException {
         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
+        // Adding game mode config override of the given package name
+        GamePackageConfiguration configOverride;
         synchronized (mLock) {
             if (!mSettings.containsKey(userId)) {
                 return;
             }
-        }
-        // Adding override game mode configuration of the given package name
-        GamePackageConfiguration overrideConfig;
-        synchronized (mOverrideConfigLock) {
-            // look for the existing override GamePackageConfiguration
-            overrideConfig = mOverrideConfigs.get(packageName);
-            if (overrideConfig == null) {
-                overrideConfig = new GamePackageConfiguration(packageName, userId);
-                mOverrideConfigs.put(packageName, overrideConfig);
+            final GameManagerSettings settings = mSettings.get(userId);
+            // look for the existing GamePackageConfiguration override
+            configOverride = settings.getConfigOverride(packageName);
+            if (configOverride == null) {
+                configOverride = new GamePackageConfiguration(mPackageManager, packageName, userId);
+                settings.setConfigOverride(packageName, configOverride);
             }
         }
         // modify GameModeConfiguration intervention settings
-        GamePackageConfiguration.GameModeConfiguration overrideModeConfig =
-                overrideConfig.getOrAddDefaultGameModeConfiguration(gameMode);
+        GamePackageConfiguration.GameModeConfiguration modeConfigOverride =
+                configOverride.getOrAddDefaultGameModeConfiguration(gameMode);
 
         if (fpsStr != null) {
-            overrideModeConfig.setFpsStr(fpsStr);
+            modeConfigOverride.setFpsStr(fpsStr);
         } else {
-            overrideModeConfig.setFpsStr(
+            modeConfigOverride.setFpsStr(
                     GamePackageConfiguration.GameModeConfiguration.DEFAULT_FPS);
         }
         if (scaling != null) {
-            overrideModeConfig.setScaling(Float.parseFloat(scaling));
+            modeConfigOverride.setScaling(Float.parseFloat(scaling));
         }
         Slog.i(TAG, "Package Name: " + packageName
-                + " FPS: " + String.valueOf(overrideModeConfig.getFps())
-                + " Scaling: " + overrideModeConfig.getScaling());
+                + " FPS: " + String.valueOf(modeConfigOverride.getFps())
+                + " Scaling: " + modeConfigOverride.getScaling());
         setGameMode(packageName, gameMode, userId);
     }
 
     /**
      * Reset the overridden gameModeConfiguration of the given mode.
-     * Remove the override config if game mode is not specified.
+     * Remove the config override if game mode is not specified.
      */
     @VisibleForTesting
     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
     public void resetGameModeConfigOverride(String packageName, @UserIdInt int userId,
             @GameMode int gameModeToReset) throws SecurityException {
         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
-        synchronized (mLock) {
-            if (!mSettings.containsKey(userId)) {
-                return;
-            }
+        final GamePackageConfiguration deviceConfig;
+        synchronized (mDeviceConfigLock) {
+            deviceConfig = mConfigs.get(packageName);
         }
 
         // resets GamePackageConfiguration of a given packageName.
         // If a gameMode is specified, only reset the GameModeConfiguration of the gameMode.
-        if (gameModeToReset != -1) {
-            GamePackageConfiguration overrideConfig = null;
-            synchronized (mOverrideConfigLock) {
-                overrideConfig = mOverrideConfigs.get(packageName);
-            }
-
-            GamePackageConfiguration config = null;
-            synchronized (mDeviceConfigLock) {
-                config = mConfigs.get(packageName);
-            }
-
-            int[] modes = overrideConfig.getAvailableGameModes();
-
-            // First check if the mode to reset exists
-            boolean isGameModeExist = false;
-            for (int mode : modes) {
-                if (gameModeToReset == mode) {
-                    isGameModeExist = true;
-                }
-            }
-            if (!isGameModeExist) {
+        synchronized (mLock) {
+            if (!mSettings.containsKey(userId)) {
                 return;
             }
-
-            // If the game mode to reset is the only mode other than standard mode,
-            // the override config is removed.
-            if (modes.length <= 2) {
-                synchronized (mOverrideConfigLock) {
-                    mOverrideConfigs.remove(packageName);
+            final GameManagerSettings settings = mSettings.get(userId);
+            if (gameModeToReset != -1) {
+                final GamePackageConfiguration configOverride = settings.getConfigOverride(
+                        packageName);
+                if (configOverride == null) {
+                    return;
+                }
+                final int modesBitfield = configOverride.getAvailableGameModesBitfield();
+                if (!bitFieldContainsModeBitmask(modesBitfield, gameModeToReset)) {
+                    return;
+                }
+                // if the game mode to reset is the only mode other than standard mode or there
+                // is device config, the config override is removed.
+                if (Integer.bitCount(modesBitfield) <= 2 || deviceConfig == null) {
+                    settings.removeConfigOverride(packageName);
+                } else {
+                    final GamePackageConfiguration.GameModeConfiguration defaultModeConfig =
+                            deviceConfig.getGameModeConfiguration(gameModeToReset);
+                    // otherwise we reset the mode by copying the original config.
+                    if (defaultModeConfig == null) {
+                        configOverride.removeModeConfig(gameModeToReset);
+                    } else {
+                        configOverride.addModeConfig(defaultModeConfig);
+                    }
                 }
             } else {
-                // otherwise we reset the mode by copying the original config.
-                overrideConfig.addModeConfig(config.getGameModeConfiguration(gameModeToReset));
-            }
-        } else {
-            synchronized (mOverrideConfigLock) {
-                // remove override config if there is one
-                mOverrideConfigs.remove(packageName);
+                settings.removeConfigOverride(packageName);
             }
         }
 
@@ -1422,7 +1454,7 @@
         // If not, set the game mode to standard
         int gameMode = getGameMode(packageName, userId);
 
-        final GamePackageConfiguration config = getConfig(packageName);
+        final GamePackageConfiguration config = getConfig(packageName, userId);
         final int newGameMode = getNewGameMode(gameMode, config);
         if (gameMode != newGameMode) {
             setGameMode(packageName, GameManager.GAME_MODE_STANDARD, userId);
@@ -1460,8 +1492,8 @@
     /**
      * Returns the string listing all the interventions currently set to a game.
      */
-    public String getInterventionList(String packageName) {
-        final GamePackageConfiguration packageConfig = getConfig(packageName);
+    public String getInterventionList(String packageName, int userId) {
+        final GamePackageConfiguration packageConfig = getConfig(packageName, userId);
         final StringBuilder listStrSb = new StringBuilder();
         if (packageConfig == null) {
             listStrSb.append("\n No intervention found for package ")
@@ -1487,7 +1519,7 @@
             synchronized (mDeviceConfigLock) {
                 for (final String packageName : packageNames) {
                     final GamePackageConfiguration config =
-                            new GamePackageConfiguration(packageName, userId);
+                            new GamePackageConfiguration(mPackageManager, packageName, userId);
                     if (config.isActive()) {
                         if (DEBUG) {
                             Slog.i(TAG, "Adding config: " + config.toString());
@@ -1526,15 +1558,11 @@
                     updateInterventions(packageName, gameMode, userId);
                 }
             }
+            sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
+                    "UPDATE_CONFIGS_FOR_USERS", 0 /*delayMillis*/);
         } catch (Exception e) {
             Slog.e(TAG, "Failed to update configs for user " + userId + ": " + e);
         }
-
-        final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE);
-        msg.obj = userId;
-        if (!mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, userId)) {
-            mHandler.sendMessage(msg);
-        }
     }
 
     /*
@@ -1556,7 +1584,7 @@
             final StringBuilder sb = new StringBuilder();
             final List<String> installedGamesList = getInstalledGamePackageNamesByAllUsers(userId);
             for (final String packageName : installedGamesList) {
-                GamePackageConfiguration packageConfig = getConfig(packageName);
+                GamePackageConfiguration packageConfig = getConfig(packageName, userId);
                 if (packageConfig == null) {
                     continue;
                 }
@@ -1634,11 +1662,12 @@
     /**
      * @hide
      */
-    @VisibleForTesting
-    public GamePackageConfiguration getConfig(String packageName) {
+    public GamePackageConfiguration getConfig(String packageName, int userId) {
         GamePackageConfiguration packageConfig = null;
-        synchronized (mOverrideConfigLock) {
-            packageConfig = mOverrideConfigs.get(packageName);
+        synchronized (mLock) {
+            if (mSettings.containsKey(userId)) {
+                packageConfig = mSettings.get(userId).getConfigOverride(packageName);
+            }
         }
         if (packageConfig == null) {
             synchronized (mDeviceConfigLock) {
@@ -1679,9 +1708,6 @@
                             break;
                         case ACTION_PACKAGE_REMOVED:
                             if (!intent.getBooleanExtra(EXTRA_REPLACING, false)) {
-                                synchronized (mOverrideConfigLock) {
-                                    mOverrideConfigs.remove(packageName);
-                                }
                                 synchronized (mDeviceConfigLock) {
                                     mConfigs.remove(packageName);
                                 }
@@ -1689,6 +1715,11 @@
                                     if (mSettings.containsKey(userId)) {
                                         mSettings.get(userId).removeGame(packageName);
                                     }
+                                    sendUserMessage(userId, WRITE_SETTINGS,
+                                            Intent.ACTION_PACKAGE_REMOVED, WRITE_DELAY_MILLIS);
+                                    sendUserMessage(userId,
+                                            WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
+                                            Intent.ACTION_PACKAGE_REMOVED, WRITE_DELAY_MILLIS);
                                 }
                             }
                             break;
diff --git a/services/core/java/com/android/server/app/GameManagerSettings.java b/services/core/java/com/android/server/app/GameManagerSettings.java
index 1455a61..1162498 100644
--- a/services/core/java/com/android/server/app/GameManagerSettings.java
+++ b/services/core/java/com/android/server/app/GameManagerSettings.java
@@ -27,6 +27,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.XmlUtils;
+import com.android.server.app.GameManagerService.GamePackageConfiguration;
+import com.android.server.app.GameManagerService.GamePackageConfiguration.GameModeConfiguration;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -39,10 +41,11 @@
 
 /**
  * Persists all GameService related settings.
+ *
  * @hide
  */
 public class GameManagerSettings {
-
+    public static final String TAG = "GameManagerService_GameManagerSettings";
     // The XML file follows the below format:
     // <?xml>
     // <packages>
@@ -53,8 +56,14 @@
 
     private static final String TAG_PACKAGE = "package";
     private static final String TAG_PACKAGES = "packages";
+    private static final String TAG_GAME_MODE_CONFIG = "gameModeConfig";
+
     private static final String ATTR_NAME = "name";
     private static final String ATTR_GAME_MODE = "gameMode";
+    private static final String ATTR_SCALING = "scaling";
+    private static final String ATTR_FPS = "fps";
+    private static final String ATTR_USE_ANGLE = "useAngle";
+    private static final String ATTR_LOADING_BOOST_DURATION = "loadingBoost";
 
     private final File mSystemDir;
     @VisibleForTesting
@@ -62,6 +71,8 @@
 
     // PackageName -> GameMode
     private final ArrayMap<String, Integer> mGameModes = new ArrayMap<>();
+    // PackageName -> GamePackageConfiguration
+    private final ArrayMap<String, GamePackageConfiguration> mConfigOverrides = new ArrayMap<>();
 
     GameManagerSettings(File dataDir) {
         mSystemDir = new File(dataDir, "system");
@@ -74,7 +85,7 @@
     }
 
     /**
-     * Return the game mode of a given package.
+     * Returns the game mode of a given package.
      * This operation must be synced with an external lock.
      */
     int getGameModeLocked(String packageName) {
@@ -85,7 +96,7 @@
     }
 
     /**
-     * Set the game mode of a given package.
+     * Sets the game mode of a given package.
      * This operation must be synced with an external lock.
      */
     void setGameModeLocked(String packageName, int gameMode) {
@@ -93,15 +104,40 @@
     }
 
     /**
-     * Remove the game mode of a given package.
+     * Removes all game settings of a given package.
      * This operation must be synced with an external lock.
      */
     void removeGame(String packageName) {
         mGameModes.remove(packageName);
+        mConfigOverrides.remove(packageName);
     }
 
     /**
-     * Write all current game service settings into disk.
+     * Returns the game config override of a given package or null if absent.
+     * This operation must be synced with an external lock.
+     */
+    GamePackageConfiguration getConfigOverride(String packageName) {
+        return mConfigOverrides.get(packageName);
+    }
+
+    /**
+     * Sets the game config override of a given package.
+     * This operation must be synced with an external lock.
+     */
+    void setConfigOverride(String packageName, GamePackageConfiguration configOverride) {
+        mConfigOverrides.put(packageName, configOverride);
+    }
+
+    /**
+     * Removes the game mode config override of a given package.
+     * This operation must be synced with an external lock.
+     */
+    void removeConfigOverride(String packageName) {
+        mConfigOverrides.remove(packageName);
+    }
+
+    /**
+     * Writes all current game service settings into disk.
      * This operation must be synced with an external lock.
      */
     void writePersistentDataLocked() {
@@ -115,9 +151,11 @@
 
             serializer.startTag(null, TAG_PACKAGES);
             for (Map.Entry<String, Integer> entry : mGameModes.entrySet()) {
+                String packageName = entry.getKey();
                 serializer.startTag(null, TAG_PACKAGE);
-                serializer.attribute(null, ATTR_NAME, entry.getKey());
+                serializer.attribute(null, ATTR_NAME, packageName);
                 serializer.attributeInt(null, ATTR_GAME_MODE, entry.getValue());
+                writeGameModeConfigTags(serializer, mConfigOverrides.get(packageName));
                 serializer.endTag(null, TAG_PACKAGE);
             }
             serializer.endTag(null, TAG_PACKAGES);
@@ -133,20 +171,41 @@
             return;
         } catch (java.io.IOException e) {
             mSettingsFile.failWrite(fstr);
-            Slog.wtf(GameManagerService.TAG, "Unable to write game manager service settings, "
+            Slog.wtf(TAG, "Unable to write game manager service settings, "
                     + "current changes will be lost at reboot", e);
         }
     }
 
+    private void writeGameModeConfigTags(TypedXmlSerializer serializer,
+            GamePackageConfiguration config) throws IOException {
+        if (config == null) {
+            return;
+        }
+        final int[] gameModes = config.getAvailableGameModes();
+        for (final int mode : gameModes) {
+            final GameModeConfiguration modeConfig = config.getGameModeConfiguration(mode);
+            if (modeConfig != null) {
+                serializer.startTag(null, TAG_GAME_MODE_CONFIG);
+                serializer.attributeInt(null, ATTR_GAME_MODE, mode);
+                serializer.attributeBoolean(null, ATTR_USE_ANGLE, modeConfig.getUseAngle());
+                serializer.attribute(null, ATTR_FPS, modeConfig.getFpsStr());
+                serializer.attributeFloat(null, ATTR_SCALING, modeConfig.getScaling());
+                serializer.attributeInt(null, ATTR_LOADING_BOOST_DURATION,
+                        modeConfig.getLoadingBoostDuration());
+                serializer.endTag(null, TAG_GAME_MODE_CONFIG);
+            }
+        }
+    }
+
     /**
-     * Read game service settings from the disk.
+     * Reads game service settings from the disk.
      * This operation must be synced with an external lock.
      */
     boolean readPersistentDataLocked() {
         mGameModes.clear();
 
         if (!mSettingsFile.exists()) {
-            Slog.v(GameManagerService.TAG, "Settings file doesn't exists, skip reading");
+            Slog.v(TAG, "Settings file doesn't exist, skip reading");
             return false;
         }
 
@@ -160,8 +219,7 @@
                 // Do nothing
             }
             if (type != XmlPullParser.START_TAG) {
-                Slog.wtf(GameManagerService.TAG,
-                        "No start tag found in package manager settings");
+                Slog.wtf(TAG, "No start tag found in package manager settings");
                 return false;
             }
 
@@ -173,35 +231,107 @@
                 }
 
                 String tagName = parser.getName();
-                if (tagName.equals(TAG_PACKAGE)) {
+                if (type == XmlPullParser.START_TAG && TAG_PACKAGE.equals(tagName)) {
                     readPackage(parser);
                 } else {
-                    Slog.w(GameManagerService.TAG, "Unknown element: " + parser.getName());
                     XmlUtils.skipCurrentTag(parser);
+                    Slog.w(TAG, "Unknown element under packages tag: " + tagName + " with type: "
+                            + type);
                 }
             }
         } catch (XmlPullParserException | java.io.IOException e) {
-            Slog.wtf(GameManagerService.TAG, "Error reading package manager settings", e);
+            Slog.wtf(TAG, "Error reading package manager settings", e);
             return false;
         }
-
         return true;
     }
 
+    // this must be called on tag of type START_TAG.
     private void readPackage(TypedXmlPullParser parser) throws XmlPullParserException,
             IOException {
-        String name = null;
+        final String name = parser.getAttributeValue(null, ATTR_NAME);
+        if (name == null) {
+            Slog.wtf(TAG, "No package name found in package tag");
+            XmlUtils.skipCurrentTag(parser);
+            return;
+        }
         int gameMode = GameManager.GAME_MODE_UNSUPPORTED;
         try {
-            name = parser.getAttributeValue(null, ATTR_NAME);
             gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE);
         } catch (XmlPullParserException e) {
-            Slog.wtf(GameManagerService.TAG, "Error reading game mode", e);
+            Slog.wtf(TAG, "Invalid game mode in package tag: "
+                    + parser.getAttributeValue(null, ATTR_GAME_MODE), e);
+            return;
         }
-        if (name != null) {
-            mGameModes.put(name, gameMode);
-        } else {
-            XmlUtils.skipCurrentTag(parser);
+        mGameModes.put(name, gameMode);
+        final int packageTagDepth = parser.getDepth();
+        int type;
+        final GamePackageConfiguration config = new GamePackageConfiguration(name);
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG
+                || parser.getDepth() > packageTagDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            final String tagName = parser.getName();
+            if (type == XmlPullParser.START_TAG && TAG_GAME_MODE_CONFIG.equals(tagName)) {
+                readGameModeConfig(parser, config);
+            } else {
+                XmlUtils.skipCurrentTag(parser);
+                Slog.w(TAG, "Unknown element under package tag: " + tagName + " with type: "
+                        + type);
+            }
+        }
+        if (config.getAvailableGameModes().length > 1) {
+            mConfigOverrides.put(name, config);
+        }
+    }
+
+    // this must be called on tag of type START_TAG.
+    private void readGameModeConfig(TypedXmlPullParser parser, GamePackageConfiguration config) {
+        final int gameMode;
+        try {
+            gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE);
+        } catch (XmlPullParserException e) {
+            Slog.wtf(TAG, "Invalid game mode value in config tag: " + parser.getAttributeValue(null,
+                    ATTR_GAME_MODE), e);
+            return;
+        }
+
+        final GameModeConfiguration modeConfig = config.getOrAddDefaultGameModeConfiguration(
+                gameMode);
+        try {
+            final float scaling = parser.getAttributeFloat(null, ATTR_SCALING);
+            modeConfig.setScaling(scaling);
+        } catch (XmlPullParserException e) {
+            final String rawScaling = parser.getAttributeValue(null, ATTR_SCALING);
+            if (rawScaling != null) {
+                Slog.wtf(TAG, "Invalid scaling value in config tag: " + rawScaling, e);
+            }
+        }
+
+        final String fps = parser.getAttributeValue(null, ATTR_FPS);
+        modeConfig.setFpsStr(fps != null ? fps : GameModeConfiguration.DEFAULT_FPS);
+
+        try {
+            final boolean useAngle = parser.getAttributeBoolean(null, ATTR_USE_ANGLE);
+            modeConfig.setUseAngle(useAngle);
+        } catch (XmlPullParserException e) {
+            final String rawUseAngle = parser.getAttributeValue(null, ATTR_USE_ANGLE);
+            if (rawUseAngle != null) {
+                Slog.wtf(TAG, "Invalid useAngle value in config tag: " + rawUseAngle, e);
+            }
+        }
+        try {
+            final int loadingBoostDuration = parser.getAttributeInt(null,
+                    ATTR_LOADING_BOOST_DURATION);
+            modeConfig.setLoadingBoostDuration(loadingBoostDuration);
+        } catch (XmlPullParserException e) {
+            final String rawLoadingBoost = parser.getAttributeValue(null,
+                    ATTR_LOADING_BOOST_DURATION);
+            if (rawLoadingBoost != null) {
+                Slog.wtf(TAG, "Invalid loading boost in config tag: " + rawLoadingBoost, e);
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java
index 6e289b1..cdbffbe 100644
--- a/services/core/java/com/android/server/app/GameManagerShellCommand.java
+++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java
@@ -81,7 +81,8 @@
         final GameManagerService gameManagerService = (GameManagerService)
                 ServiceManager.getService(Context.GAME_SERVICE);
 
-        final String listStr = gameManagerService.getInterventionList(packageName);
+        final String listStr = gameManagerService.getInterventionList(packageName,
+                ActivityManager.getCurrentUser());
 
         if (listStr == null) {
             pw.println("No interventions found for " + packageName);
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/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index d16fe12..d4ef638 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -343,6 +343,9 @@
      *
      * Calling this multiple times for duplicate requests will be no-ops, returning true.
      *
+     * TODO(b/239130847): Maintain the proximity state in AttentionManagerService and change this
+     * to a polling API.
+     *
      * @return {@code true} if the framework was able to dispatch the request
      */
     @VisibleForTesting
@@ -853,9 +856,6 @@
     @GuardedBy("mLock")
     private void cancelAndUnbindLocked() {
         synchronized (mLock) {
-            if (mCurrentAttentionCheck == null && mCurrentProximityUpdate == null) {
-                return;
-            }
             if (mCurrentAttentionCheck != null) {
                 cancel();
             }
@@ -937,7 +937,7 @@
             }
         }
 
-        class TestableProximityUpdateCallbackInternal extends ProximityUpdateCallbackInternal {
+        class TestableProximityUpdateCallbackInternal implements ProximityUpdateCallbackInternal {
             private double mLastCallbackCode = PROXIMITY_UNKNOWN;
 
             @Override
@@ -1069,6 +1069,7 @@
         private void resetStates() {
             synchronized (mLock) {
                 mCurrentProximityUpdate = null;
+                cancelAndUnbindLocked();
             }
             mComponentName = resolveAttentionService(mContext);
         }
diff --git a/services/core/java/com/android/server/attention/TEST_MAPPING b/services/core/java/com/android/server/attention/TEST_MAPPING
new file mode 100644
index 0000000..35b8165
--- /dev/null
+++ b/services/core/java/com/android/server/attention/TEST_MAPPING
@@ -0,0 +1,24 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsVoiceInteractionTestCases",
+      "options": [
+        {
+          "include-filter": "android.voiceinteraction.cts.AlwaysOnHotwordDetectorTest"
+        },
+        {
+          "include-filter": "android.voiceinteraction.cts.HotwordDetectedResultTest"
+        },
+        {
+          "include-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest"
+        },
+        {
+          "include-filter": "android.voiceinteraction.cts.HotwordDetectionServiceProximityTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index b3aff65..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");
     }
 
     /**
@@ -1915,7 +1915,7 @@
         return null;
     }
 
-    UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
+    @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
         synchronized (mDeviceStateLock) {
             return mDeviceInventory.getDeviceSensorUuid(device);
         }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 1312d08..c1f4969 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -16,6 +16,7 @@
 package com.android.server.audio;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
@@ -375,7 +376,8 @@
                         makeLeAudioDeviceUnavailable(address, btInfo.mAudioSystemDevice);
                     } else if (switchToAvailable) {
                         makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
-                                streamType, btInfo.mAudioSystemDevice, "onSetBtActiveDevice");
+                                streamType, btInfo.mVolume, btInfo.mAudioSystemDevice,
+                                "onSetBtActiveDevice");
                     }
                     break;
                 default: throw new IllegalArgumentException("Invalid profile "
@@ -1175,8 +1177,8 @@
     }
 
     @GuardedBy("mDevicesLock")
-    private void makeLeAudioDeviceAvailable(String address, String name, int streamType, int device,
-            String eventSource) {
+    private void makeLeAudioDeviceAvailable(String address, String name, int streamType,
+            int volumeIndex, int device, String eventSource) {
         if (device != AudioSystem.DEVICE_NONE) {
             /* Audio Policy sees Le Audio similar to A2DP. Let's make sure
              * AUDIO_POLICY_FORCE_NO_BT_A2DP is not set
@@ -1197,7 +1199,9 @@
             return;
         }
 
-        final int leAudioVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType, device);
+        final int leAudioVolIndex = (volumeIndex == -1)
+                ? mDeviceBroker.getVssVolumeForDevice(streamType, device)
+                : volumeIndex;
         final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType);
         mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType);
         mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable");
@@ -1511,7 +1515,7 @@
         mDevRoleCapturePresetDispatchers.finishBroadcast();
     }
 
-    UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
+    @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
         final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
                 device.getAddress());
         synchronized (mDevicesLock) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5a20db3..785040e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -63,6 +63,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.SensorPrivacyManager;
 import android.hardware.SensorPrivacyManagerInternal;
@@ -412,10 +413,10 @@
     protected static int[] MAX_STREAM_VOLUME = new int[] {
         5,  // STREAM_VOICE_CALL
         7,  // STREAM_SYSTEM
-        7,  // STREAM_RING
+        7,  // STREAM_RING            // configured by config_audio_ring_vol_steps
         15, // STREAM_MUSIC
         7,  // STREAM_ALARM
-        7,  // STREAM_NOTIFICATION
+        7,  // STREAM_NOTIFICATION    // configured by config_audio_notif_vol_steps
         15, // STREAM_BLUETOOTH_SCO
         7,  // STREAM_SYSTEM_ENFORCED
         15, // STREAM_DTMF
@@ -1116,6 +1117,48 @@
                         MAX_STREAM_VOLUME[AudioSystem.STREAM_SYSTEM];
         }
 
+        // Read following properties to configure max volume (number of steps) and default volume
+        //   for STREAM_NOTIFICATION and STREAM_RING:
+        //      config_audio_notif_vol_default
+        //      config_audio_notif_vol_steps
+        //      config_audio_ring_vol_default
+        //      config_audio_ring_vol_steps
+        int[] streams = { AudioSystem.STREAM_NOTIFICATION, AudioSystem.STREAM_RING };
+        int[] stepsResId = { com.android.internal.R.integer.config_audio_notif_vol_steps,
+                com.android.internal.R.integer.config_audio_ring_vol_steps };
+        int[] defaultResId = { com.android.internal.R.integer.config_audio_notif_vol_default,
+                com.android.internal.R.integer.config_audio_ring_vol_default };
+        for (int s = 0; s < streams.length; s++) {
+            try {
+                final int maxVol = mContext.getResources().getInteger(stepsResId[s]);
+                if (maxVol <= 0) {
+                    throw new IllegalArgumentException("Invalid negative max volume for stream "
+                            + streams[s]);
+                }
+                Log.i(TAG, "Stream " + streams[s] + ": using max vol of " + maxVol);
+                MAX_STREAM_VOLUME[streams[s]] = maxVol;
+            } catch (Resources.NotFoundException e) {
+                Log.e(TAG, "Error querying max vol for stream type " + streams[s], e);
+            }
+            try {
+                final int defaultVol = mContext.getResources().getInteger(defaultResId[s]);
+                if (defaultVol > MAX_STREAM_VOLUME[streams[s]]) {
+                    throw new IllegalArgumentException("Invalid default volume (" + defaultVol
+                            + ") for stream " + streams[s] + ", greater than max volume of "
+                            + MAX_STREAM_VOLUME[streams[s]]);
+                }
+                if (defaultVol < MIN_STREAM_VOLUME[streams[s]]) {
+                    throw new IllegalArgumentException("Invalid default volume (" + defaultVol
+                            + ") for stream " + streams[s] + ", lower than min volume of "
+                            + MIN_STREAM_VOLUME[streams[s]]);
+                }
+                Log.i(TAG, "Stream " + streams[s] + ": using default vol of " + defaultVol);
+                AudioSystem.DEFAULT_STREAM_VOLUME[streams[s]] = defaultVol;
+            } catch (Resources.NotFoundException e) {
+                Log.e(TAG, "Error querying default vol for stream type " + streams[s], e);
+            }
+        }
+
         if (looper == null) {
             createAudioSystemThread();
         } else {
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index e27fb11..b7e817e 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -353,6 +353,14 @@
         mASA.getDevicesForAttributes(
                 DEFAULT_ATTRIBUTES, false /* forVolume */).toArray(ROUTING_DEVICES);
 
+        // check validity of routing information
+        if (ROUTING_DEVICES[0] == null) {
+            logloge("onRoutingUpdated: device is null, no Spatial Audio");
+            setDispatchAvailableState(false);
+            // not changing the spatializer level as this is likely a transient state
+            return;
+        }
+
         // is media routed to a new device?
         if (isWireless(ROUTING_DEVICES[0].getType())) {
             addWirelessDeviceIfNew(ROUTING_DEVICES[0]);
@@ -563,7 +571,9 @@
     // There may be different devices with the same device type (aliasing).
     // We always send the full device state info on each change.
     private void logDeviceState(SADeviceState deviceState, String event) {
-        final String deviceName = AudioSystem.getDeviceName(deviceState.mDeviceType);
+        final int deviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
+                deviceState.mDeviceType);
+        final String deviceName = AudioSystem.getDeviceName(deviceType);
         new MediaMetrics.Item(METRICS_DEVICE_PREFIX + deviceName)
             .set(MediaMetrics.Property.ADDRESS, deviceState.mDeviceAddress)
             .set(MediaMetrics.Property.ENABLED, deviceState.mEnabled ? "true" : "false")
@@ -719,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)) {
@@ -1098,7 +1111,7 @@
         logDeviceState(deviceState, "setHeadTrackerEnabled");
 
         // check current routing to see if it affects the headtracking mode
-        if (ROUTING_DEVICES[0].getType() == ada.getType()
+        if (ROUTING_DEVICES[0] != null && ROUTING_DEVICES[0].getType() == ada.getType()
                 && ROUTING_DEVICES[0].getAddress().equals(ada.getAddress())) {
             setDesiredHeadTrackingMode(enabled ? mDesiredHeadTrackingModeWhenEnabled
                     : Spatializer.HEAD_TRACKING_MODE_DISABLED);
@@ -1528,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() {
@@ -1633,7 +1646,11 @@
 
     private int getHeadSensorHandleUpdateTracker() {
         int headHandle = -1;
-        UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(ROUTING_DEVICES[0]);
+        final AudioDeviceAttributes currentDevice = ROUTING_DEVICES[0];
+        if (currentDevice == null) {
+            return headHandle;
+        }
+        UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(currentDevice);
         // We limit only to Sensor.TYPE_HEAD_TRACKER here to avoid confusion
         // with gaming sensors. (Note that Sensor.TYPE_ROTATION_VECTOR
         // and Sensor.TYPE_GAME_ROTATION_VECTOR are supported internally by
@@ -1644,7 +1661,7 @@
             final UUID uuid = sensor.getUuid();
             if (uuid.equals(routingDeviceUuid)) {
                 headHandle = sensor.getHandle();
-                if (!setHasHeadTracker(ROUTING_DEVICES[0])) {
+                if (!setHasHeadTracker(currentDevice)) {
                     headHandle = -1;
                 }
                 break;
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 16a060a..b4e91b5 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -752,7 +752,7 @@
         return true;
     }
 
-    private boolean sendEventToVpnManagerApp(@NonNull String category, int errorClass,
+    private Intent buildVpnManagerEventIntent(@NonNull String category, int errorClass,
             int errorCode, @NonNull final String packageName, @Nullable final String sessionKey,
             @NonNull final VpnProfileState profileState, @Nullable final Network underlyingNetwork,
             @Nullable final NetworkCapabilities nc, @Nullable final LinkProperties lp) {
@@ -771,6 +771,20 @@
             intent.putExtra(VpnManager.EXTRA_ERROR_CODE, errorCode);
         }
 
+        return intent;
+    }
+
+    private boolean sendEventToVpnManagerApp(@NonNull String category, int errorClass,
+            int errorCode, @NonNull final String packageName, @Nullable final String sessionKey,
+            @NonNull final VpnProfileState profileState, @Nullable final Network underlyingNetwork,
+            @Nullable final NetworkCapabilities nc, @Nullable final LinkProperties lp) {
+        final Intent intent = buildVpnManagerEventIntent(category, errorClass, errorCode,
+                packageName, sessionKey, profileState, underlyingNetwork, nc, lp);
+        return sendEventToVpnManagerApp(intent, packageName);
+    }
+
+    private boolean sendEventToVpnManagerApp(@NonNull final Intent intent,
+            @NonNull final String packageName) {
         // Allow VpnManager app to temporarily run background services to handle this error.
         // If an app requires anything beyond this grace period, they MUST either declare
         // themselves as a foreground service, or schedule a job/workitem.
@@ -1096,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);
             }
@@ -1182,12 +1196,25 @@
                 mContext.unbindService(mConnection);
                 cleanupVpnStateLocked();
             } else if (mVpnRunner != null) {
-                if (!VpnConfig.LEGACY_VPN.equals(mPackage)) {
-                    notifyVpnManagerVpnStopped(mPackage, mOwnerUID);
+                // Build intent first because the sessionKey will be reset after performing
+                // VpnRunner.exit(). Also, cache mOwnerUID even if ownerUID will not be changed in
+                // VpnRunner.exit() to prevent design being changed in the future.
+                // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
+                //  ConnectivityServiceTest.
+                final int ownerUid = mOwnerUID;
+                Intent intent = null;
+                if (SdkLevel.isAtLeastT() && isVpnApp(mPackage)) {
+                    intent = buildVpnManagerEventIntent(
+                            VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
+                            -1 /* errorClass */, -1 /* errorCode*/, mPackage,
+                            getSessionKeyLocked(), makeVpnProfileStateLocked(),
+                            null /* underlyingNetwork */, null /* nc */, null /* lp */);
                 }
-
                 // cleanupVpnStateLocked() is called from mVpnRunner.exit()
                 mVpnRunner.exit();
+                if (intent != null && isVpnApp(mPackage)) {
+                    notifyVpnManagerVpnStopped(mPackage, ownerUid, intent);
+                }
             }
 
             try {
@@ -2046,10 +2073,6 @@
                 "Unauthorized Caller");
     }
 
-    private boolean hasControlVpnPermission() {
-        return mContext.checkCallingOrSelfPermission(CONTROL_VPN) == PERMISSION_GRANTED;
-    }
-
     private class Connection implements ServiceConnection {
         private IBinder mService;
 
@@ -2886,6 +2909,9 @@
                 final LinkProperties lp;
 
                 synchronized (Vpn.this) {
+                    // Ignore stale runner.
+                    if (mVpnRunner != this) return;
+
                     mInterface = interfaceName;
                     mConfig.mtu = maxMtu;
                     mConfig.interfaze = mInterface;
@@ -2987,6 +3013,9 @@
 
             try {
                 synchronized (Vpn.this) {
+                    // Ignore stale runner.
+                    if (mVpnRunner != this) return;
+
                     mConfig.underlyingNetworks = new Network[] {network};
                     mNetworkCapabilities =
                             new NetworkCapabilities.Builder(mNetworkCapabilities)
@@ -3076,7 +3105,12 @@
 
                 // Clear mInterface to prevent Ikev2VpnRunner being cleared when
                 // interfaceRemoved() is called.
-                mInterface = null;
+                synchronized (Vpn.this) {
+                    // Ignore stale runner.
+                    if (mVpnRunner != this) return;
+
+                    mInterface = null;
+                }
                 // Without MOBIKE, we have no way to seamlessly migrate. Close on old
                 // (non-default) network, and start the new one.
                 resetIkeState();
@@ -3261,6 +3295,9 @@
         /** Marks the state as FAILED, and disconnects. */
         private void markFailedAndDisconnect(Exception exception) {
             synchronized (Vpn.this) {
+                // Ignore stale runner.
+                if (mVpnRunner != this) return;
+
                 updateState(DetailedState.FAILED, exception.getMessage());
             }
 
@@ -3345,6 +3382,9 @@
             }
 
             synchronized (Vpn.this) {
+                // Ignore stale runner.
+                if (mVpnRunner != this) return;
+
                 // TODO(b/230548427): Remove SDK check once VPN related stuff are
                 //  decoupled from ConnectivityServiceTest.
                 if (SdkLevel.isAtLeastT() && category != null && isVpnApp(mPackage)) {
@@ -3371,6 +3411,9 @@
             Log.d(TAG, "Resetting state for token: " + mCurrentToken);
 
             synchronized (Vpn.this) {
+                // Ignore stale runner.
+                if (mVpnRunner != this) return;
+
                 // Since this method handles non-fatal errors only, set mInterface to null to
                 // prevent the NetworkManagementEventObserver from killing this VPN based on the
                 // interface going down (which we expect).
@@ -3854,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);
@@ -3993,6 +4034,7 @@
             mConfig.proxyInfo = profile.proxy;
             mConfig.requiresInternetValidation = profile.requiresInternetValidation;
             mConfig.excludeLocalRoutes = profile.excludeLocalRoutes;
+            mConfig.allowBypass = profile.isBypassable;
 
             switch (profile.type) {
                 case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
@@ -4042,13 +4084,23 @@
         // To stop the VPN profile, the caller must be the current prepared package and must be
         // running an Ikev2VpnProfile.
         if (isCurrentIkev2VpnLocked(packageName)) {
-            notifyVpnManagerVpnStopped(packageName, mOwnerUID);
+            // Build intent first because the sessionKey will be reset after performing
+            // VpnRunner.exit(). Also, cache mOwnerUID even if ownerUID will not be changed in
+            // VpnRunner.exit() to prevent design being changed in the future.
+            final int ownerUid = mOwnerUID;
+            final Intent intent = buildVpnManagerEventIntent(
+                    VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
+                    -1 /* errorClass */, -1 /* errorCode*/, packageName,
+                    getSessionKeyLocked(), makeVpnProfileStateLocked(),
+                    null /* underlyingNetwork */, null /* nc */, null /* lp */);
 
             mVpnRunner.exit();
+            notifyVpnManagerVpnStopped(packageName, ownerUid, intent);
         }
     }
 
-    private synchronized void notifyVpnManagerVpnStopped(String packageName, int ownerUID) {
+    private synchronized void notifyVpnManagerVpnStopped(String packageName, int ownerUID,
+            Intent intent) {
         mAppOpsManager.finishOp(
                 AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER, ownerUID, packageName, null);
         // The underlying network, NetworkCapabilities and LinkProperties are not
@@ -4057,10 +4109,7 @@
         // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
         //  ConnectivityServiceTest.
         if (SdkLevel.isAtLeastT()) {
-            sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
-                    -1 /* errorClass */, -1 /* errorCode*/, packageName,
-                    getSessionKeyLocked(), makeVpnProfileStateLocked(),
-                    null /* underlyingNetwork */, null /* nc */, null /* lp */);
+            sendEventToVpnManagerApp(intent, packageName);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index a817cea..6145a91c 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -148,6 +148,12 @@
     // The currently accepted nominal ambient light level.
     private float mAmbientLux;
 
+    // The last calculated ambient light level (long time window).
+    private float mSlowAmbientLux;
+
+    // The last calculated ambient light level (short time window).
+    private float mFastAmbientLux;
+
     // The last ambient lux value prior to passing the darkening or brightening threshold.
     private float mPreThresholdLux;
 
@@ -440,6 +446,14 @@
         return mAmbientLux;
     }
 
+    float getSlowAmbientLux() {
+        return mSlowAmbientLux;
+    }
+
+    float getFastAmbientLux() {
+        return mFastAmbientLux;
+    }
+
     private boolean setDisplayPolicy(int policy) {
         if (mDisplayPolicy == policy) {
             return false;
@@ -812,20 +826,20 @@
         // proposed ambient light value since the slow value might be sufficiently far enough away
         // from the fast value to cause a recalculation while its actually just converging on
         // the fast value still.
-        float slowAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonLong);
-        float fastAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonShort);
+        mSlowAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonLong);
+        mFastAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonShort);
 
-        if ((slowAmbientLux >= mAmbientBrighteningThreshold
-                && fastAmbientLux >= mAmbientBrighteningThreshold
+        if ((mSlowAmbientLux >= mAmbientBrighteningThreshold
+                && mFastAmbientLux >= mAmbientBrighteningThreshold
                 && nextBrightenTransition <= time)
-                || (slowAmbientLux <= mAmbientDarkeningThreshold
-                        && fastAmbientLux <= mAmbientDarkeningThreshold
+                || (mSlowAmbientLux <= mAmbientDarkeningThreshold
+                        && mFastAmbientLux <= mAmbientDarkeningThreshold
                         && nextDarkenTransition <= time)) {
             mPreThresholdLux = mAmbientLux;
-            setAmbientLux(fastAmbientLux);
+            setAmbientLux(mFastAmbientLux);
             if (mLoggingEnabled) {
                 Slog.d(TAG, "updateAmbientLux: "
-                        + ((fastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
+                        + ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
                         + "mBrighteningLuxThreshold=" + mAmbientBrighteningThreshold + ", "
                         + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
                         + "mAmbientLux=" + mAmbientLux);
@@ -994,8 +1008,9 @@
                     final String packageName = info.topActivity.getPackageName();
                     // If the app didn't change, there's nothing to do. Otherwise, we have to
                     // update the category and re-apply the brightness correction.
-                    if (mForegroundAppPackageName != null
-                            && mForegroundAppPackageName.equals(packageName)) {
+                    String currentForegroundAppPackageName = mForegroundAppPackageName;
+                    if (currentForegroundAppPackageName != null
+                            && currentForegroundAppPackageName.equals(packageName)) {
                         return;
                     }
                     mPendingForegroundAppPackageName = packageName;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 6f3a0c5..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;
@@ -1640,11 +1644,16 @@
         // brightness cap, RBC state, etc.
         mTempBrightnessEvent.setTime(System.currentTimeMillis());
         mTempBrightnessEvent.setBrightness(brightnessState);
+        mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
         mTempBrightnessEvent.setReason(mBrightnessReason);
         mTempBrightnessEvent.setHbmMax(mHbmController.getCurrentBrightnessMax());
         mTempBrightnessEvent.setHbmMode(mHbmController.getHighBrightnessMode());
         mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
-                | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0));
+                | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0)
+                | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0));
+        mTempBrightnessEvent.setRbcStrength(mCdsi != null
+                ? mCdsi.getReduceBrightColorsStrength() : -1);
+        mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
         // Temporary is what we use during slider interactions. We avoid logging those so that
         // we don't spam logcat when the slider is being used.
         boolean tempToTempTransition =
@@ -1653,9 +1662,17 @@
                         == BrightnessReason.REASON_TEMPORARY;
         if ((!mTempBrightnessEvent.equalsMainData(mLastBrightnessEvent) && !tempToTempTransition)
                 || brightnessAdjustmentFlags != 0) {
+            float lastBrightness = mLastBrightnessEvent.getBrightness();
+            mTempBrightnessEvent.setInitialBrightness(lastBrightness);
+            mTempBrightnessEvent.setFastAmbientLux(
+                    mAutomaticBrightnessController == null
+                        ? -1f : mAutomaticBrightnessController.getFastAmbientLux());
+            mTempBrightnessEvent.setSlowAmbientLux(
+                    mAutomaticBrightnessController == null
+                        ? -1f : mAutomaticBrightnessController.getSlowAmbientLux());
+            mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
             mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
             BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
-
             // Adjustment flags (and user-set flag) only get added after the equality checks since
             // they are transient.
             newEvent.setAdjustmentFlags(brightnessAdjustmentFlags);
@@ -1663,6 +1680,9 @@
                     ? BrightnessEvent.FLAG_USER_SET : 0));
             Slog.i(mTag, newEvent.toString(/* includeTime= */ false));
 
+            if (userSetBrightnessChanged) {
+                logManualBrightnessEvent(newEvent);
+            }
             if (mBrightnessEventRingBuffer != null) {
                 mBrightnessEventRingBuffer.append(newEvent);
             }
@@ -2752,6 +2772,31 @@
         }
     }
 
+    private void logManualBrightnessEvent(BrightnessEvent event) {
+        float appliedLowPowerMode = event.isLowPowerModeSet() ? event.getPowerFactor() : -1f;
+        int appliedRbcStrength  = event.isRbcEnabled() ? event.getRbcStrength() : -1;
+        float appliedHbmMaxNits =
+                event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
+                ? -1f : convertToNits(event.getHbmMax());
+        // thermalCapNits set to -1 if not currently capping max brightness
+        float appliedThermalCapNits =
+                event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
+                ? -1f : convertToNits(event.getThermalMax());
+
+        FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
+                convertToNits(event.getInitialBrightness()),
+                convertToNits(event.getBrightness()),
+                event.getSlowAmbientLux(),
+                event.getPhysicalDisplayId(),
+                event.isShortTermModelActive(),
+                appliedLowPowerMode,
+                appliedRbcStrength,
+                appliedHbmMaxNits,
+                appliedThermalCapNits,
+                event.isAutomaticBrightnessEnabled(),
+                FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+    }
+
     private final class DisplayControllerHandler extends Handler {
         DisplayControllerHandler(Looper looper) {
             super(looper, null, true /*async*/);
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index d831dbd..e3fa622 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -21,6 +21,8 @@
 import android.os.SystemClock;
 import android.util.TimeUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 /**
  * Represents a particular brightness change event.
  */
@@ -29,21 +31,29 @@
     public static final int FLAG_INVALID_LUX = 0x2;
     public static final int FLAG_DOZE_SCALE = 0x4;
     public static final int FLAG_USER_SET = 0x8;
-    public static final int FLAG_IDLE_CURVE = 0x16;
+    public static final int FLAG_IDLE_CURVE = 0x10;
+    public static final int FLAG_LOW_POWER_MODE = 0x20;
 
     private BrightnessReason mReason = new BrightnessReason();
     private int mDisplayId;
-    private float mLux;
-    private float mPreThresholdLux;
+    private String mPhysicalDisplayId;
     private long mTime;
+    private float mLux;
+    private float mFastAmbientLux;
+    private float mSlowAmbientLux;
+    private float mPreThresholdLux;
+    private float mInitialBrightness;
     private float mBrightness;
     private float mRecommendedBrightness;
     private float mPreThresholdBrightness;
-    private float mHbmMax;
-    private float mThermalMax;
     private int mHbmMode;
+    private float mHbmMax;
+    private int mRbcStrength;
+    private float mThermalMax;
+    private float mPowerFactor;
     private int mFlags;
     private int mAdjustmentFlags;
+    private boolean mAutomaticBrightnessEnabled;
 
     public BrightnessEvent(BrightnessEvent that) {
         copyFrom(that);
@@ -60,37 +70,59 @@
      * @param that BrightnessEvent which is to be copied
      */
     public void copyFrom(BrightnessEvent that) {
+        mReason.set(that.getReason());
         mDisplayId = that.getDisplayId();
+        mPhysicalDisplayId = that.getPhysicalDisplayId();
         mTime = that.getTime();
+        // Lux values
         mLux = that.getLux();
+        mFastAmbientLux = that.getFastAmbientLux();
+        mSlowAmbientLux = that.getSlowAmbientLux();
         mPreThresholdLux = that.getPreThresholdLux();
+        // Brightness values
+        mInitialBrightness = that.getInitialBrightness();
         mBrightness = that.getBrightness();
         mRecommendedBrightness = that.getRecommendedBrightness();
         mPreThresholdBrightness = that.getPreThresholdBrightness();
-        mHbmMax = that.getHbmMax();
-        mThermalMax = that.getThermalMax();
-        mFlags = that.getFlags();
+        // Different brightness modulations
         mHbmMode = that.getHbmMode();
-        mReason.set(that.getReason());
+        mHbmMax = that.getHbmMax();
+        mRbcStrength = that.getRbcStrength();
+        mThermalMax = that.getThermalMax();
+        mPowerFactor = that.getPowerFactor();
+        mFlags = that.getFlags();
         mAdjustmentFlags = that.getAdjustmentFlags();
+        // Auto-brightness setting
+        mAutomaticBrightnessEnabled = that.isAutomaticBrightnessEnabled();
     }
 
     /**
      * A utility to reset the BrightnessEvent to default values
      */
     public void reset() {
+        mReason.set(null);
         mTime = SystemClock.uptimeMillis();
+        mPhysicalDisplayId = "";
+        // Lux values
+        mLux = 0;
+        mFastAmbientLux = 0;
+        mSlowAmbientLux = 0;
+        mPreThresholdLux = 0;
+        // Brightness values
+        mInitialBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
         mBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
         mRecommendedBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-        mLux = 0;
-        mPreThresholdLux = 0;
         mPreThresholdBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-        mHbmMax = PowerManager.BRIGHTNESS_MAX;
-        mThermalMax = PowerManager.BRIGHTNESS_MAX;
-        mFlags = 0;
+        // Different brightness modulations
         mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
-        mReason.set(null);
+        mHbmMax = PowerManager.BRIGHTNESS_MAX;
+        mRbcStrength = 0;
+        mThermalMax = PowerManager.BRIGHTNESS_MAX;
+        mPowerFactor = 1f;
+        mFlags = 0;
         mAdjustmentFlags = 0;
+        // Auto-brightness setting
+        mAutomaticBrightnessEnabled = true;
     }
 
     /**
@@ -104,23 +136,34 @@
     public boolean equalsMainData(BrightnessEvent that) {
         // This equals comparison purposefully ignores time since it is regularly changing and
         // we don't want to log a brightness event just because the time changed.
-        return mDisplayId == that.mDisplayId
+        return mReason.equals(that.mReason)
+                && mDisplayId == that.mDisplayId
+                && mPhysicalDisplayId.equals(that.mPhysicalDisplayId)
+                && Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux)
+                && Float.floatToRawIntBits(mFastAmbientLux)
+                == Float.floatToRawIntBits(that.mFastAmbientLux)
+                && Float.floatToRawIntBits(mSlowAmbientLux)
+                == Float.floatToRawIntBits(that.mSlowAmbientLux)
+                && Float.floatToRawIntBits(mPreThresholdLux)
+                == Float.floatToRawIntBits(that.mPreThresholdLux)
+                && Float.floatToRawIntBits(mInitialBrightness)
+                == Float.floatToRawIntBits(that.mInitialBrightness)
                 && Float.floatToRawIntBits(mBrightness)
                 == Float.floatToRawIntBits(that.mBrightness)
                 && Float.floatToRawIntBits(mRecommendedBrightness)
                 == Float.floatToRawIntBits(that.mRecommendedBrightness)
                 && Float.floatToRawIntBits(mPreThresholdBrightness)
                 == Float.floatToRawIntBits(that.mPreThresholdBrightness)
-                && Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux)
-                && Float.floatToRawIntBits(mPreThresholdLux)
-                == Float.floatToRawIntBits(that.mPreThresholdLux)
-                && Float.floatToRawIntBits(mHbmMax) == Float.floatToRawIntBits(that.mHbmMax)
                 && mHbmMode == that.mHbmMode
+                && Float.floatToRawIntBits(mHbmMax) == Float.floatToRawIntBits(that.mHbmMax)
+                && mRbcStrength == that.mRbcStrength
                 && Float.floatToRawIntBits(mThermalMax)
                 == Float.floatToRawIntBits(that.mThermalMax)
+                && Float.floatToRawIntBits(mPowerFactor)
+                == Float.floatToRawIntBits(that.mPowerFactor)
                 && mFlags == that.mFlags
                 && mAdjustmentFlags == that.mAdjustmentFlags
-                && mReason.equals(that.mReason);
+                && mAutomaticBrightnessEnabled == that.mAutomaticBrightnessEnabled;
     }
 
     /**
@@ -133,16 +176,23 @@
         return (includeTime ? TimeUtils.formatForLogging(mTime) + " - " : "")
                 + "BrightnessEvent: "
                 + "disp=" + mDisplayId
+                + ", physDisp=" + mPhysicalDisplayId
                 + ", brt=" + mBrightness + ((mFlags & FLAG_USER_SET) != 0 ? "(user_set)" : "")
+                + ", initBrt=" + mInitialBrightness
                 + ", rcmdBrt=" + mRecommendedBrightness
                 + ", preBrt=" + mPreThresholdBrightness
                 + ", lux=" + mLux
+                + ", fastLux=" + mFastAmbientLux
+                + ", slowLux=" + mSlowAmbientLux
                 + ", preLux=" + mPreThresholdLux
                 + ", hbmMax=" + mHbmMax
                 + ", hbmMode=" + BrightnessInfo.hbmToString(mHbmMode)
+                + ", rbcStrength=" + mRbcStrength
                 + ", thrmMax=" + mThermalMax
+                + ", powerFactor=" + mPowerFactor
                 + ", flags=" + flagsToString()
-                + ", reason=" + mReason.toString(mAdjustmentFlags);
+                + ", reason=" + mReason.toString(mAdjustmentFlags)
+                + ", autoBrightness=" + mAutomaticBrightnessEnabled;
     }
 
     @Override
@@ -150,12 +200,20 @@
         return toString(/* includeTime */ true);
     }
 
+    public BrightnessReason getReason() {
+        return mReason;
+    }
+
     public void setReason(BrightnessReason reason) {
         this.mReason = reason;
     }
 
-    public BrightnessReason getReason() {
-        return mReason;
+    public long getTime() {
+        return mTime;
+    }
+
+    public void setTime(long time) {
+        this.mTime = time;
     }
 
     public int getDisplayId() {
@@ -166,6 +224,14 @@
         this.mDisplayId = displayId;
     }
 
+    public String getPhysicalDisplayId() {
+        return mPhysicalDisplayId;
+    }
+
+    public void setPhysicalDisplayId(String mPhysicalDisplayId) {
+        this.mPhysicalDisplayId = mPhysicalDisplayId;
+    }
+
     public float getLux() {
         return mLux;
     }
@@ -174,6 +240,22 @@
         this.mLux = lux;
     }
 
+    public float getFastAmbientLux() {
+        return mFastAmbientLux;
+    }
+
+    public void setFastAmbientLux(float mFastAmbientLux) {
+        this.mFastAmbientLux = mFastAmbientLux;
+    }
+
+    public float getSlowAmbientLux() {
+        return mSlowAmbientLux;
+    }
+
+    public void setSlowAmbientLux(float mSlowAmbientLux) {
+        this.mSlowAmbientLux = mSlowAmbientLux;
+    }
+
     public float getPreThresholdLux() {
         return mPreThresholdLux;
     }
@@ -182,12 +264,12 @@
         this.mPreThresholdLux = preThresholdLux;
     }
 
-    public long getTime() {
-        return mTime;
+    public float getInitialBrightness() {
+        return mInitialBrightness;
     }
 
-    public void setTime(long time) {
-        this.mTime = time;
+    public void setInitialBrightness(float mInitialBrightness) {
+        this.mInitialBrightness = mInitialBrightness;
     }
 
     public float getBrightness() {
@@ -214,6 +296,14 @@
         this.mPreThresholdBrightness = preThresholdBrightness;
     }
 
+    public int getHbmMode() {
+        return mHbmMode;
+    }
+
+    public void setHbmMode(int hbmMode) {
+        this.mHbmMode = hbmMode;
+    }
+
     public float getHbmMax() {
         return mHbmMax;
     }
@@ -222,6 +312,18 @@
         this.mHbmMax = hbmMax;
     }
 
+    public int getRbcStrength() {
+        return mRbcStrength;
+    }
+
+    public void setRbcStrength(int mRbcStrength) {
+        this.mRbcStrength = mRbcStrength;
+    }
+
+    public boolean isRbcEnabled() {
+        return (mFlags & FLAG_RBC) != 0;
+    }
+
     public float getThermalMax() {
         return mThermalMax;
     }
@@ -230,12 +332,16 @@
         this.mThermalMax = thermalMax;
     }
 
-    public int getHbmMode() {
-        return mHbmMode;
+    public float getPowerFactor() {
+        return mPowerFactor;
     }
 
-    public void setHbmMode(int hbmMode) {
-        this.mHbmMode = hbmMode;
+    public void setPowerFactor(float mPowerFactor) {
+        this.mPowerFactor = mPowerFactor;
+    }
+
+    public boolean isLowPowerModeSet() {
+        return (mFlags & FLAG_LOW_POWER_MODE) != 0;
     }
 
     public int getFlags() {
@@ -246,6 +352,10 @@
         this.mFlags = flags;
     }
 
+    public boolean isShortTermModelActive() {
+        return (mFlags & FLAG_USER_SET) != 0;
+    }
+
     public int getAdjustmentFlags() {
         return mAdjustmentFlags;
     }
@@ -254,11 +364,25 @@
         this.mAdjustmentFlags = adjustmentFlags;
     }
 
-    private String flagsToString() {
+    public boolean isAutomaticBrightnessEnabled() {
+        return mAutomaticBrightnessEnabled;
+    }
+
+    public void setAutomaticBrightnessEnabled(boolean mAutomaticBrightnessEnabled) {
+        this.mAutomaticBrightnessEnabled = mAutomaticBrightnessEnabled;
+    }
+
+    /**
+     * A utility to stringify flags from a BrightnessEvent
+     * @return Stringified flags from BrightnessEvent
+     */
+    @VisibleForTesting
+    public String flagsToString() {
         return ((mFlags & FLAG_USER_SET) != 0 ? "user_set " : "")
                 + ((mFlags & FLAG_RBC) != 0 ? "rbc " : "")
                 + ((mFlags & FLAG_INVALID_LUX) != 0 ? "invalid_lux " : "")
                 + ((mFlags & FLAG_DOZE_SCALE) != 0 ? "doze_scale " : "")
-                + ((mFlags & FLAG_IDLE_CURVE) != 0 ? "idle_curve " : "");
+                + ((mFlags & FLAG_IDLE_CURVE) != 0 ? "idle_curve " : "")
+                + ((mFlags & FLAG_LOW_POWER_MODE) != 0 ? "low_power_mode " : "");
     }
 }
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 6e30416..366dfd1 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -1515,6 +1515,10 @@
             return mReduceBrightColorsTintController.isActivated();
         }
 
+        public int getReduceBrightColorsStrength() {
+            return mReduceBrightColorsTintController.getStrength();
+        }
+
         /**
          * Gets the computed brightness, in nits, when the reduce bright colors feature is applied
          * at the current strength.
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 7b60345..4e0489a 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -218,6 +218,7 @@
         }, pw, "", 200);
     }
 
+    /** Whether a real dream is occurring. */
     private boolean isDreamingInternal() {
         synchronized (mLock) {
             return mCurrentDreamToken != null && !mCurrentDreamIsPreview
@@ -225,6 +226,13 @@
         }
     }
 
+    /** Whether a real dream, or a dream preview is occurring. */
+    private boolean isDreamingOrInPreviewInternal() {
+        synchronized (mLock) {
+            return mCurrentDreamToken != null && !mCurrentDreamIsWaking;
+        }
+    }
+
     protected void requestStartDreamFromShell() {
         requestDreamInternal();
     }
@@ -695,6 +703,19 @@
         }
 
         @Override // Binder call
+        public boolean isDreamingOrInPreview() {
+            checkPermission(android.Manifest.permission.READ_DREAM_STATE);
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return isDreamingOrInPreviewInternal();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+
+        @Override // Binder call
         public void dream() {
             checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
 
diff --git a/services/core/java/com/android/server/hdmi/CecMessageBuffer.java b/services/core/java/com/android/server/hdmi/CecMessageBuffer.java
index 8f971fd..0c73582 100644
--- a/services/core/java/com/android/server/hdmi/CecMessageBuffer.java
+++ b/services/core/java/com/android/server/hdmi/CecMessageBuffer.java
@@ -48,6 +48,12 @@
             case Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST:
                 bufferSystemAudioModeRequest(message);
                 return true;
+            case Constants.MESSAGE_ROUTING_CHANGE:
+                bufferRoutingChange(message);
+                return true;
+            case Constants.MESSAGE_SET_STREAM_PATH:
+                bufferSetStreamPath(message);
+                return true;
             // Add here if new message that needs to buffer
             default:
                 // Do not need to buffer messages other than above
@@ -89,6 +95,22 @@
         }
     }
 
+    private void bufferRoutingChange(HdmiCecMessage message) {
+        if (!replaceMessageIfBuffered(message, Constants.MESSAGE_ROUTING_CHANGE)) {
+            mBuffer.add(message);
+        }
+    }
+
+    private void bufferSetStreamPath(HdmiCecMessage message) {
+        if (!replaceMessageIfBuffered(message, Constants.MESSAGE_SET_STREAM_PATH)) {
+            mBuffer.add(message);
+        }
+    }
+
+    public List<HdmiCecMessage> getBuffer() {
+        return new ArrayList<>(mBuffer);
+    }
+
     // Returns true if the message is replaced
     private boolean replaceMessageIfBuffered(HdmiCecMessage message, int opcode) {
         for (int i = 0; i < mBuffer.size(); i++) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 97e9c64..5aa3fa4 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -147,6 +147,9 @@
 
     private final HdmiCecAtomWriter mHdmiCecAtomWriter;
 
+    // This variable is used for testing, in order to delay the logical address allocation.
+    private long mLogicalAddressAllocationDelay = 0;
+
     // Private constructor.  Use HdmiCecController.create().
     private HdmiCecController(
             HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter) {
@@ -215,12 +218,12 @@
             final AllocateAddressCallback callback) {
         assertRunOnServiceThread();
 
-        runOnIoThread(new Runnable() {
+        mIoHandler.postDelayed(new Runnable() {
             @Override
             public void run() {
                 handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
             }
-        });
+        }, mLogicalAddressAllocationDelay);
     }
 
     /**
@@ -386,6 +389,14 @@
     }
 
     /**
+     * This method is used for testing, in order to delay the logical address allocation.
+     */
+    @VisibleForTesting
+    void setLogicalAddressAllocationDelay(long delay) {
+        mLogicalAddressAllocationDelay = delay;
+    }
+
+    /**
      * Returns true if the language code is well-formed.
      */
     @VisibleForTesting static boolean isLanguage(String language) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 1de1a7a..2622cef 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -649,6 +649,13 @@
         return Constants.NOT_HANDLED;
     }
 
+    /**
+     * Called after logical address allocation is finished, allowing a local device to react to
+     * messages in the buffer before they are processed. This method may be used to cancel deferred
+     * actions.
+     */
+    protected void preprocessBufferedMessages(List<HdmiCecMessage> bufferedMessages) {}
+
     @Constants.RcProfile
     protected abstract int getRcProfile();
 
@@ -963,8 +970,10 @@
     }
 
     @ServiceThreadOnly
-    final void handleAddressAllocated(int logicalAddress, int reason) {
+    final void handleAddressAllocated(
+            int logicalAddress, List<HdmiCecMessage> bufferedMessages, int reason) {
         assertRunOnServiceThread();
+        preprocessBufferedMessages(bufferedMessages);
         mPreferredAddress = logicalAddress;
         updateDeviceFeatures();
         if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 5cfe27a..e6c2e7c 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -530,6 +530,26 @@
         }
     }
 
+    /**
+     * Called after logical address allocation is finished, allowing a local device to react to
+     * messages in the buffer before they are processed. This method may be used to cancel deferred
+     * actions.
+     */
+    @Override
+    protected void preprocessBufferedMessages(List<HdmiCecMessage> bufferedMessages) {
+        for (HdmiCecMessage message: bufferedMessages) {
+            // Prevent the device from broadcasting <Active Source> message if the active path
+            // changed during address allocation.
+            if (message.getOpcode() == Constants.MESSAGE_ROUTING_CHANGE
+                    || message.getOpcode() == Constants.MESSAGE_SET_STREAM_PATH
+                    || message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) {
+                removeAction(ActiveSourceAction.class);
+                removeAction(OneTouchPlayAction.class);
+                return;
+            }
+        }
+    }
+
     @Override
     protected int findKeyReceiverAddress() {
         return Constants.ADDR_TV;
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 6c37bf1..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.
@@ -1079,9 +1079,10 @@
     @ServiceThreadOnly
     private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
         assertRunOnServiceThread();
+        List<HdmiCecMessage> bufferedMessages = mCecMessageBuffer.getBuffer();
         for (HdmiCecLocalDevice device : devices) {
             int address = device.getDeviceInfo().getLogicalAddress();
-            device.handleAddressAllocated(address, initiatedBy);
+            device.handleAddressAllocated(address, bufferedMessages, initiatedBy);
         }
         if (isTvDeviceEnabled()) {
             tv().setSelectRequestBuffer(mSelectRequestBuffer);
@@ -1361,8 +1362,9 @@
 
         @Constants.HandleMessageResult int handleMessageResult =
                 dispatchMessageToLocalDevice(message);
-        if (handleMessageResult == Constants.NOT_HANDLED
-                && !mAddressAllocated
+        // mAddressAllocated is false during address allocation, meaning there is no device to
+        // handle the message, so it should be buffered, if possible.
+        if (!mAddressAllocated
                 && mCecMessageBuffer.bufferMessage(message)) {
             return Constants.HANDLED;
         }
@@ -3286,7 +3288,8 @@
     }
 
     @ServiceThreadOnly
-    private void onWakeUp(@WakeReason final int wakeUpAction) {
+    @VisibleForTesting
+    protected void onWakeUp(@WakeReason final int wakeUpAction) {
         assertRunOnServiceThread();
         mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON,
                 false);
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 d48acb1..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;
 
@@ -910,11 +911,14 @@
             final String mImeControlTargetName;
             @Nullable
             final String mImeTargetNameFromWm;
+            @Nullable
+            final String mImeSurfaceParentName;
 
             Entry(ClientState client, EditorInfo editorInfo, String focusedWindowName,
                     @SoftInputModeFlags int softInputMode, @SoftInputShowHideReason int reason,
                     boolean inFullscreenMode, String requestWindowName,
-                    @Nullable String imeControlTargetName, @Nullable String imeTargetName) {
+                    @Nullable String imeControlTargetName, @Nullable String imeTargetName,
+                    @Nullable String imeSurfaceParentName) {
                 mClientState = client;
                 mEditorInfo = editorInfo;
                 mFocusedWindowName = focusedWindowName;
@@ -926,6 +930,7 @@
                 mRequestWindowName = requestWindowName;
                 mImeControlTargetName = imeControlTargetName;
                 mImeTargetNameFromWm = imeTargetName;
+                mImeSurfaceParentName = imeSurfaceParentName;
             }
         }
 
@@ -972,6 +977,9 @@
                 pw.println(" imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
 
                 pw.print(prefix);
+                pw.println(" imeSurfaceParentName=" + entry.mImeSurfaceParentName);
+
+                pw.print(prefix);
                 pw.print(" editorInfo: ");
                 pw.print(" inputType=" + entry.mEditorInfo.inputType);
                 pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
@@ -2712,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
@@ -2727,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 */)
@@ -4356,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);
@@ -4415,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);
             }
@@ -4434,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) {
@@ -4676,7 +4687,8 @@
         mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
                 mCurFocusedWindowClient, mCurEditorInfo, info.focusedWindowName,
                 mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
-                info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName));
+                info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName,
+                info.imeSurfaceParentName));
     }
 
     @BinderThread
@@ -4914,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/LocationPermissions.java b/services/core/java/com/android/server/location/LocationPermissions.java
index ca2ff60..f7da0d8 100644
--- a/services/core/java/com/android/server/location/LocationPermissions.java
+++ b/services/core/java/com/android/server/location/LocationPermissions.java
@@ -26,8 +26,10 @@
 import android.content.Context;
 import android.os.Binder;
 
+import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 
 /** Utility class for dealing with location permissions. */
 public final class LocationPermissions {
@@ -49,6 +51,7 @@
      */
     public static final int PERMISSION_FINE = 2;
 
+    @Target(ElementType.TYPE_USE)
     @IntDef({PERMISSION_NONE, PERMISSION_COARSE, PERMISSION_FINE})
     @Retention(RetentionPolicy.SOURCE)
     public @interface PermissionLevel {}
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/location/geofence/GeofenceKey.java b/services/core/java/com/android/server/location/geofence/GeofenceKey.java
deleted file mode 100644
index bbfa68f..0000000
--- a/services/core/java/com/android/server/location/geofence/GeofenceKey.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.location.geofence;
-
-import android.app.PendingIntent;
-import android.location.Geofence;
-
-import com.android.server.location.listeners.PendingIntentListenerRegistration;
-
-import java.util.Objects;
-
-// geofencing unfortunately allows multiple geofences under the same pending intent, even though
-// this makes no real sense. therefore we manufacture an artificial key to use (pendingintent +
-// geofence) instead of (pendingintent).
-final class GeofenceKey  implements PendingIntentListenerRegistration.PendingIntentKey {
-
-    private final PendingIntent mPendingIntent;
-    private final Geofence mGeofence;
-
-    GeofenceKey(PendingIntent pendingIntent, Geofence geofence) {
-        mPendingIntent = Objects.requireNonNull(pendingIntent);
-        mGeofence = Objects.requireNonNull(geofence);
-    }
-
-    @Override
-    public PendingIntent getPendingIntent() {
-        return mPendingIntent;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!(o instanceof GeofenceKey)) {
-            return false;
-        }
-        GeofenceKey that = (GeofenceKey) o;
-        return mPendingIntent.equals(that.mPendingIntent) && mGeofence.equals(that.mGeofence);
-    }
-
-    @Override
-    public int hashCode() {
-        return mPendingIntent.hashCode();
-    }
-}
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
index b6342a4..0f5e3d4 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
@@ -59,8 +59,8 @@
  * Manages all geofences.
  */
 public class GeofenceManager extends
-        ListenerMultiplexer<GeofenceKey, PendingIntent, GeofenceManager.GeofenceRegistration,
-                LocationRequest> implements
+        ListenerMultiplexer<GeofenceManager.GeofenceKey, PendingIntent,
+                GeofenceManager.GeofenceRegistration, LocationRequest> implements
         LocationListener {
 
     private static final String TAG = "GeofenceManager";
@@ -73,13 +73,49 @@
     private static final long MAX_LOCATION_AGE_MS = 5 * 60 * 1000L; // five minutes
     private static final long MAX_LOCATION_INTERVAL_MS = 2 * 60 * 60 * 1000; // two hours
 
-    protected final class GeofenceRegistration extends
-            PendingIntentListenerRegistration<Geofence, PendingIntent> {
+    // geofencing unfortunately allows multiple geofences under the same pending intent, even though
+    // this makes no real sense. therefore we manufacture an artificial key to use (pendingintent +
+    // geofence) instead of (pendingintent).
+    static class GeofenceKey {
+
+        private final PendingIntent mPendingIntent;
+        private final Geofence mGeofence;
+
+        GeofenceKey(PendingIntent pendingIntent, Geofence geofence) {
+            mPendingIntent = Objects.requireNonNull(pendingIntent);
+            mGeofence = Objects.requireNonNull(geofence);
+        }
+
+        public PendingIntent getPendingIntent() {
+            return mPendingIntent;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof GeofenceKey) {
+                GeofenceKey that = (GeofenceKey) o;
+                return mPendingIntent.equals(that.mPendingIntent) && mGeofence.equals(
+                        that.mGeofence);
+            }
+
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return mPendingIntent.hashCode();
+        }
+    }
+
+    protected class GeofenceRegistration extends
+            PendingIntentListenerRegistration<GeofenceKey, PendingIntent> {
 
         private static final int STATE_UNKNOWN = 0;
         private static final int STATE_INSIDE = 1;
         private static final int STATE_OUTSIDE = 2;
 
+        private final Geofence mGeofence;
+        private final CallerIdentity mIdentity;
         private final Location mCenter;
         private final PowerManager.WakeLock mWakeLock;
 
@@ -89,13 +125,15 @@
         // spam us, and because checking the values may be more expensive
         private boolean mPermitted;
 
-        private @Nullable Location mCachedLocation;
+        @Nullable private Location mCachedLocation;
         private float mCachedLocationDistanceM;
 
-        protected GeofenceRegistration(Geofence geofence, CallerIdentity identity,
+        GeofenceRegistration(Geofence geofence, CallerIdentity identity,
                 PendingIntent pendingIntent) {
-            super(geofence, identity, pendingIntent);
+            super(pendingIntent);
 
+            mGeofence = geofence;
+            mIdentity = identity;
             mCenter = new Location("");
             mCenter.setLatitude(geofence.getLatitude());
             mCenter.setLongitude(geofence.getLongitude());
@@ -107,16 +145,36 @@
             mWakeLock.setWorkSource(identity.addToWorkSource(null));
         }
 
+        public Geofence getGeofence() {
+            return mGeofence;
+        }
+
+        public CallerIdentity getIdentity() {
+            return mIdentity;
+        }
+
+        @Override
+        public String getTag() {
+            return TAG;
+        }
+
+        @Override
+        protected PendingIntent getPendingIntentFromKey(GeofenceKey geofenceKey) {
+            return geofenceKey.getPendingIntent();
+        }
+
         @Override
         protected GeofenceManager getOwner() {
             return GeofenceManager.this;
         }
 
         @Override
-        protected void onPendingIntentListenerRegister() {
+        protected void onRegister() {
+            super.onRegister();
+
             mGeofenceState = STATE_UNKNOWN;
             mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
-                    getIdentity());
+                    mIdentity);
         }
 
         @Override
@@ -132,7 +190,7 @@
         }
 
         boolean onLocationPermissionsChanged(@Nullable String packageName) {
-            if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
+            if (packageName == null || mIdentity.getPackageName().equals(packageName)) {
                 return onLocationPermissionsChanged();
             }
 
@@ -140,7 +198,7 @@
         }
 
         boolean onLocationPermissionsChanged(int uid) {
-            if (getIdentity().getUid() == uid) {
+            if (mIdentity.getUid() == uid) {
                 return onLocationPermissionsChanged();
             }
 
@@ -149,7 +207,7 @@
 
         private boolean onLocationPermissionsChanged() {
             boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
-                    getIdentity());
+                    mIdentity);
             if (permitted != mPermitted) {
                 mPermitted = permitted;
                 return true;
@@ -164,12 +222,12 @@
                 mCachedLocationDistanceM = mCenter.distanceTo(mCachedLocation);
             }
 
-            return Math.abs(getRequest().getRadius() - mCachedLocationDistanceM);
+            return Math.abs(mGeofence.getRadius() - mCachedLocationDistanceM);
         }
 
         ListenerOperation<PendingIntent> onLocationChanged(Location location) {
             // remove expired fences
-            if (getRequest().isExpired()) {
+            if (mGeofence.isExpired()) {
                 remove();
                 return null;
             }
@@ -178,7 +236,7 @@
             mCachedLocationDistanceM = mCenter.distanceTo(mCachedLocation);
 
             int oldState = mGeofenceState;
-            float radius = Math.max(getRequest().getRadius(), location.getAccuracy());
+            float radius = Math.max(mGeofence.getRadius(), location.getAccuracy());
             if (mCachedLocationDistanceM <= radius) {
                 mGeofenceState = STATE_INSIDE;
                 if (oldState != STATE_INSIDE) {
@@ -206,14 +264,14 @@
                         null, null, PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
             } catch (PendingIntent.CanceledException e) {
                 mWakeLock.release();
-                removeRegistration(new GeofenceKey(pendingIntent, getRequest()), this);
+                removeRegistration(new GeofenceKey(pendingIntent, mGeofence), this);
             }
         }
 
         @Override
         public String toString() {
             StringBuilder builder = new StringBuilder();
-            builder.append(getIdentity());
+            builder.append(mIdentity);
 
             ArraySet<String> flags = new ArraySet<>(1);
             if (!mPermitted) {
@@ -223,7 +281,7 @@
                 builder.append(" ").append(flags);
             }
 
-            builder.append(" ").append(getRequest());
+            builder.append(" ").append(mGeofence);
             return builder.toString();
         }
     }
@@ -258,10 +316,10 @@
     protected final LocationUsageLogger mLocationUsageLogger;
 
     @GuardedBy("mLock")
-    private @Nullable LocationManager mLocationManager;
+    @Nullable private LocationManager mLocationManager;
 
     @GuardedBy("mLock")
-    private @Nullable Location mLastLocation;
+    @Nullable private Location mLastLocation;
 
     public GeofenceManager(Context context, Injector injector) {
         mContext = context.createAttributionContext(ATTRIBUTION_TAG);
@@ -271,11 +329,6 @@
         mLocationUsageLogger = injector.getLocationUsageLogger();
     }
 
-    @Override
-    public String getTag() {
-        return TAG;
-    }
-
     private LocationManager getLocationManager() {
         synchronized (mLock) {
             if (mLocationManager == null) {
@@ -375,7 +428,7 @@
                 /* LocationRequest= */ null,
                 /* hasListener= */ false,
                 true,
-                registration.getRequest(), true);
+                registration.getGeofence(), true);
     }
 
     @Override
@@ -389,7 +442,7 @@
                 /* LocationRequest= */ null,
                 /* hasListener= */ false,
                 true,
-                registration.getRequest(), true);
+                registration.getGeofence(), true);
     }
 
     @Override
@@ -417,7 +470,7 @@
         WorkSource workSource = null;
         double minFenceDistanceM = Double.MAX_VALUE;
         for (GeofenceRegistration registration : registrations) {
-            if (registration.getRequest().isExpired(realtimeMs)) {
+            if (registration.getGeofence().isExpired(realtimeMs)) {
                 continue;
             }
 
diff --git a/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java b/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
index e375007..62ab22a 100644
--- a/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
@@ -16,6 +16,7 @@
 
 package com.android.server.location.gnss;
 
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
 import static com.android.server.location.gnss.GnssManagerService.TAG;
 
 import android.annotation.Nullable;
@@ -25,6 +26,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 
+import com.android.server.FgThread;
 import com.android.server.location.gnss.hal.GnssNative;
 import com.android.server.location.listeners.BinderListenerRegistration;
 import com.android.server.location.listeners.ListenerMultiplexer;
@@ -45,17 +47,35 @@
      * Registration object for GNSS listeners.
      */
     protected class AntennaInfoListenerRegistration extends
-            BinderListenerRegistration<Void, IGnssAntennaInfoListener> {
+            BinderListenerRegistration<IBinder, IGnssAntennaInfoListener> {
 
-        protected AntennaInfoListenerRegistration(CallerIdentity callerIdentity,
+        private final CallerIdentity mIdentity;
+
+        protected AntennaInfoListenerRegistration(CallerIdentity identity,
                 IGnssAntennaInfoListener listener) {
-            super(null, callerIdentity, listener);
+            super(identity.isMyProcess() ? FgThread.getExecutor() : DIRECT_EXECUTOR, listener);
+            mIdentity = identity;
+        }
+
+        @Override
+        protected String getTag() {
+            return TAG;
         }
 
         @Override
         protected GnssAntennaInfoProvider getOwner() {
             return GnssAntennaInfoProvider.this;
         }
+
+        @Override
+        protected IBinder getBinderFromKey(IBinder key) {
+            return key;
+        }
+
+        @Override
+        public String toString() {
+            return mIdentity.toString();
+        }
     }
 
     private final GnssNative mGnssNative;
@@ -72,11 +92,6 @@
         return mAntennaInfos;
     }
 
-    @Override
-    public String getTag() {
-        return TAG;
-    }
-
     public boolean isSupported() {
         return mGnssNative.isAntennaInfoSupported();
     }
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index a540476..82bcca2 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -18,6 +18,7 @@
 
 import static android.location.LocationManager.GPS_PROVIDER;
 
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
 import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
 import static com.android.server.location.gnss.GnssManagerService.TAG;
 
@@ -33,6 +34,7 @@
 import android.util.ArraySet;
 
 import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.location.injector.AppForegroundHelper;
 import com.android.server.location.injector.Injector;
@@ -67,16 +69,34 @@
      * Registration object for GNSS listeners.
      */
     protected class GnssListenerRegistration extends
-            BinderListenerRegistration<TRequest, TListener> {
+            BinderListenerRegistration<IBinder, TListener> {
+
+        private final TRequest mRequest;
+        private final CallerIdentity mIdentity;
 
         // we store these values because we don't trust the listeners not to give us dupes, not to
         // spam us, and because checking the values may be more expensive
         private boolean mForeground;
         private boolean mPermitted;
 
-        protected GnssListenerRegistration(@Nullable TRequest request,
-                CallerIdentity callerIdentity, TListener listener) {
-            super(request, callerIdentity, listener);
+        protected GnssListenerRegistration(TRequest request, CallerIdentity identity,
+                TListener listener) {
+            super(identity.isMyProcess() ? FgThread.getExecutor() : DIRECT_EXECUTOR, listener);
+            mRequest = request;
+            mIdentity = identity;
+        }
+
+        public final TRequest getRequest() {
+            return mRequest;
+        }
+
+        public final CallerIdentity getIdentity() {
+            return mIdentity;
+        }
+
+        @Override
+        public String getTag() {
+            return TAG;
         }
 
         @Override
@@ -84,6 +104,11 @@
             return GnssListenerMultiplexer.this;
         }
 
+        @Override
+        protected IBinder getBinderFromKey(IBinder key) {
+            return key;
+        }
+
         /**
          * Returns true if this registration is currently in the foreground.
          */
@@ -96,31 +121,16 @@
         }
 
         @Override
-        protected final void onBinderListenerRegister() {
+        protected void onRegister() {
+            super.onRegister();
+
             mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
-                    getIdentity());
-            mForeground = mAppForegroundHelper.isAppForeground(getIdentity().getUid());
-
-            onGnssListenerRegister();
+                    mIdentity);
+            mForeground = mAppForegroundHelper.isAppForeground(mIdentity.getUid());
         }
 
-        @Override
-        protected final void onBinderListenerUnregister() {
-            onGnssListenerUnregister();
-        }
-
-        /**
-         * May be overridden in place of {@link #onBinderListenerRegister()}.
-         */
-        protected void onGnssListenerRegister() {}
-
-        /**
-         * May be overridden in place of {@link #onBinderListenerUnregister()}.
-         */
-        protected void onGnssListenerUnregister() {}
-
         boolean onLocationPermissionsChanged(@Nullable String packageName) {
-            if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
+            if (packageName == null || mIdentity.getPackageName().equals(packageName)) {
                 return onLocationPermissionsChanged();
             }
 
@@ -128,7 +138,7 @@
         }
 
         boolean onLocationPermissionsChanged(int uid) {
-            if (getIdentity().getUid() == uid) {
+            if (mIdentity.getUid() == uid) {
                 return onLocationPermissionsChanged();
             }
 
@@ -137,7 +147,7 @@
 
         private boolean onLocationPermissionsChanged() {
             boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
-                    getIdentity());
+                    mIdentity);
             if (permitted != mPermitted) {
                 mPermitted = permitted;
                 return true;
@@ -147,7 +157,7 @@
         }
 
         boolean onForegroundChanged(int uid, boolean foreground) {
-            if (getIdentity().getUid() == uid && foreground != mForeground) {
+            if (mIdentity.getUid() == uid && foreground != mForeground) {
                 mForeground = foreground;
                 return true;
             }
@@ -158,7 +168,7 @@
         @Override
         public String toString() {
             StringBuilder builder = new StringBuilder();
-            builder.append(getIdentity());
+            builder.append(mIdentity);
 
             ArraySet<String> flags = new ArraySet<>(2);
             if (!mForeground) {
@@ -171,8 +181,8 @@
                 builder.append(" ").append(flags);
             }
 
-            if (getRequest() != null) {
-                builder.append(" ").append(getRequest());
+            if (mRequest != null) {
+                builder.append(" ").append(mRequest);
             }
             return builder.toString();
         }
@@ -218,11 +228,6 @@
                 LocalServices.getService(LocationManagerInternal.class));
     }
 
-    @Override
-    public String getTag() {
-        return TAG;
-    }
-
     /**
      * May be overridden by subclasses to return whether the service is supported or not. This value
      * should never change for the lifetime of the multiplexer. If the service is unsupported, all
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index e4e9d01..9f2a9cf 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -40,10 +40,7 @@
 import java.util.Collection;
 
 /**
- * An base implementation for GNSS measurements provider. It abstracts out the responsibility of
- * handling listeners, while still allowing technology specific implementations to be built.
- *
- * @hide
+ * GNSS measurements HAL module and listener multiplexer.
  */
 public final class GnssMeasurementsProvider extends
         GnssListenerMultiplexer<GnssMeasurementRequest, IGnssMeasurementsListener,
@@ -61,7 +58,9 @@
         }
 
         @Override
-        protected void onGnssListenerRegister() {
+        protected void onRegister() {
+            super.onRegister();
+
             executeOperation(listener -> listener.onStatusChanged(
                     GnssMeasurementsEvent.Callback.STATUS_READY));
         }
diff --git a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
index e9fce05..63134bb 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
@@ -32,11 +32,7 @@
 import java.util.Collection;
 
 /**
- * An base implementation for GPS navigation messages provider.
- * It abstracts out the responsibility of handling listeners, while still allowing technology
- * specific implementations to be built.
- *
- * @hide
+ * GNSS navigation message HAL module and listener multiplexer.
  */
 public class GnssNavigationMessageProvider extends
         GnssListenerMultiplexer<Void, IGnssNavigationMessageListener, Void> implements
@@ -51,7 +47,9 @@
         }
 
         @Override
-        protected void onGnssListenerRegister() {
+        protected void onRegister() {
+            super.onRegister();
+
             executeOperation(listener -> listener.onStatusChanged(
                     GnssNavigationMessage.Callback.STATUS_READY));
         }
diff --git a/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java b/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
index bfef978..d4e38b6a 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
@@ -34,7 +34,7 @@
 import java.util.function.Function;
 
 /**
- * Implementation of a handler for {@link IGnssNmeaListener}.
+ * GNSS NMEA HAL module and listener multiplexer.
  */
 class GnssNmeaProvider extends GnssListenerMultiplexer<Void, IGnssNmeaListener, Void> implements
         GnssNative.BaseCallbacks, GnssNative.NmeaCallbacks {
@@ -97,7 +97,7 @@
                         ListenerExecutor.ListenerOperation<IGnssNmeaListener>>() {
 
                     // only read in the nmea string if we need to
-                    private @Nullable String mNmea;
+                    @Nullable private String mNmea;
 
                     @Override
                     public ListenerExecutor.ListenerOperation<IGnssNmeaListener> apply(
diff --git a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
index 0ce36d6..41fa7a1 100644
--- a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
@@ -35,7 +35,7 @@
 import java.util.Collection;
 
 /**
- * Implementation of a handler for {@link IGnssStatusListener}.
+ * GNSS status HAL module and listener multiplexer.
  */
 public class GnssStatusProvider extends
         GnssListenerMultiplexer<Void, IGnssStatusListener, Void> implements
diff --git a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
index 709e236..5555aeb 100644
--- a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
@@ -16,71 +16,59 @@
 
 package com.android.server.location.listeners;
 
-import android.annotation.Nullable;
-import android.location.util.identity.CallerIdentity;
-import android.os.Binder;
 import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
 import android.os.RemoteException;
 import android.util.Log;
 
+import java.util.NoSuchElementException;
+import java.util.concurrent.Executor;
+
 /**
  * A registration that works with IBinder keys, and registers a DeathListener to automatically
- * remove the registration if the binder dies. The key for this registration must either be an
- * {@link IBinder} or a {@link BinderKey}.
+ * remove the registration if the binder dies.
  *
- * @param <TRequest>  request type
+ * @param <TKey>      key type
  * @param <TListener> listener type
  */
-public abstract class BinderListenerRegistration<TRequest, TListener> extends
-        RemoteListenerRegistration<TRequest, TListener> implements Binder.DeathRecipient {
+public abstract class BinderListenerRegistration<TKey, TListener> extends
+        RemovableListenerRegistration<TKey, TListener> implements DeathRecipient {
 
-    /**
-     * Interface to allow binder retrieval when keys are not themselves IBinders.
-     */
-    public interface BinderKey {
-        /**
-         * Returns the binder associated with this key.
-         */
-        IBinder getBinder();
+    protected BinderListenerRegistration(Executor executor, TListener listener) {
+        super(executor, listener);
     }
 
-    protected BinderListenerRegistration(@Nullable TRequest request, CallerIdentity callerIdentity,
-            TListener listener) {
-        super(request, callerIdentity, listener);
-    }
+    protected abstract IBinder getBinderFromKey(TKey key);
 
     @Override
-    protected final void onRemovableListenerRegister() {
-        IBinder binder = getBinderFromKey(getKey());
+    protected void onRegister() {
+        super.onRegister();
+
         try {
-            binder.linkToDeath(this, 0);
+            getBinderFromKey(getKey()).linkToDeath(this, 0);
         } catch (RemoteException e) {
             remove();
         }
-
-        onBinderListenerRegister();
     }
 
     @Override
-    protected final void onRemovableListenerUnregister() {
-        onBinderListenerUnregister();
-        getBinderFromKey(getKey()).unlinkToDeath(this, 0);
+    protected void onUnregister() {
+        try {
+            getBinderFromKey(getKey()).unlinkToDeath(this, 0);
+        } catch (NoSuchElementException e) {
+            // the only way this exception can occur should be if another exception has been thrown
+            // prior to registration completing, and that exception is currently unwinding the call
+            // stack and causing this cleanup. since that exception should crash us anyways, drop
+            // this exception so we're not hiding the original exception.
+            Log.w(getTag(), "failed to unregister binder death listener", e);
+        }
+
+        super.onUnregister();
     }
 
-    /**
-     * May be overridden in place of {@link #onRemovableListenerRegister()}.
-     */
-    protected void onBinderListenerRegister() {}
-
-    /**
-     * May be overridden in place of {@link #onRemovableListenerUnregister()}.
-     */
-    protected void onBinderListenerUnregister() {}
-
-    @Override
     public void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
         if (e instanceof RemoteException) {
-            Log.w(getOwner().getTag(), "registration " + this + " removed", e);
+            Log.w(getTag(), "registration " + this + " removed", e);
             remove();
         } else {
             super.onOperationFailure(operation, e);
@@ -90,9 +78,10 @@
     @Override
     public void binderDied() {
         try {
-            if (Log.isLoggable(getOwner().getTag(), Log.DEBUG)) {
-                Log.d(getOwner().getTag(), "binder registration " + getIdentity() + " died");
+            if (Log.isLoggable(getTag(), Log.DEBUG)) {
+                Log.d(getTag(), "binder registration " + this + " died");
             }
+
             remove();
         } catch (RuntimeException e) {
             // the caller may swallow runtime exceptions, so we rethrow as assertion errors to
@@ -100,14 +89,4 @@
             throw new AssertionError(e);
         }
     }
-
-    private static IBinder getBinderFromKey(Object key) {
-        if (key instanceof IBinder) {
-            return (IBinder) key;
-        } else if (key instanceof BinderKey) {
-            return ((BinderKey) key).getBinder();
-        } else {
-            throw new IllegalArgumentException("key must be IBinder or BinderKey");
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
index 33b08d4..67ae265 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.os.Build;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
@@ -37,40 +36,48 @@
 import java.util.function.Predicate;
 
 /**
- * A base class to multiplex client listener registrations within system server. Every listener is
+ * A base class to multiplex some event source to multiple listener registrations. Every listener is
  * represented by a registration object which stores all required state for a listener. Keys are
  * used to uniquely identify every registration. Listener operations may be executed on
  * registrations in order to invoke the represented listener.
  *
- * Registrations are divided into two categories, active registrations and inactive registrations,
- * as defined by {@link #isActive(ListenerRegistration)}. If a registration's active state changes,
- * {@link #updateRegistrations(Predicate)} must be invoked and return true for any registration
- * whose active state may have changed. Listeners will only be invoked for active registrations.
+ * <p>Registrations are divided into two categories, active registrations and inactive
+ * registrations, as defined by {@link #isActive(ListenerRegistration)}. The set of active
+ * registrations is combined into a single merged registration, which is submitted to the backing
+ * event source when necessary in order to register with the event source. The merged registration
+ * is updated whenever the set of active registration changes. Listeners will only be invoked for
+ * active registrations.
  *
- * The set of active registrations is combined into a single merged registration, which is submitted
- * to the backing service when necessary in order to register the service. The merged registration
- * is updated whenever the set of active registration changes.
+ * <p>In order to inform the multiplexer of state changes, if a registration's active state changes,
+ * or if the merged registration changes, {@link #updateRegistrations(Predicate)} or {@link
+ * #updateRegistration(Object, Predicate)} must be invoked and return true for any registration
+ * whose state may have changed in such a way that the active state or merged registration state has
+ * changed. It is acceptable to return true from a predicate even if nothing has changed, though
+ * this may result in extra pointless work.
  *
- * Callbacks invoked for various changes will always be ordered according to this lifecycle list:
+ * <p>Callbacks invoked for various changes will always be ordered according to this lifecycle list:
  *
  * <ul>
- * <li>{@link #onRegister()}</li>
- * <li>{@link ListenerRegistration#onRegister(Object)}</li>
- * <li>{@link #onRegistrationAdded(Object, ListenerRegistration)}</li>
- * <li>{@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} (only
- * invoked if this registration is replacing a prior registration)</li>
- * <li>{@link #onActive()}</li>
- * <li>{@link ListenerRegistration#onActive()}</li>
- * <li>{@link ListenerRegistration#onInactive()}</li>
- * <li>{@link #onInactive()}</li>
- * <li>{@link #onRegistrationRemoved(Object, ListenerRegistration)}</li>
- * <li>{@link ListenerRegistration#onUnregister()}</li>
- * <li>{@link #onUnregister()}</li>
+ *   <li>{@link #onRegister()}
+ *   <li>{@link ListenerRegistration#onRegister(Object)}
+ *   <li>{@link #onRegistrationAdded(Object, ListenerRegistration)}
+ *   <li>{@link #onActive()}
+ *   <li>{@link ListenerRegistration#onActive()}
+ *   <li>{@link ListenerRegistration#onInactive()}
+ *   <li>{@link #onInactive()}
+ *   <li>{@link #onRegistrationRemoved(Object, ListenerRegistration)}
+ *   <li>{@link ListenerRegistration#onUnregister()}
+ *   <li>{@link #onUnregister()}
  * </ul>
  *
- * Adding registrations is not allowed to be called re-entrantly (ie, while in the middle of some
- * other operation or callback. Removal is allowed re-entrantly, however only via
- * {@link #removeRegistration(Object, ListenerRegistration)}, not via any other removal method. This
+ * <p>If one registration replaces another, then {@link #onRegistrationReplaced(Object,
+ * ListenerRegistration, Object, ListenerRegistration)} is invoked instead of {@link
+ * #onRegistrationRemoved(Object, ListenerRegistration)} and {@link #onRegistrationAdded(Object,
+ * ListenerRegistration)}.
+ *
+ * <p>Adding registrations is not allowed to be called re-entrantly (ie, while in the middle of some
+ * other operation or callback). Removal is allowed re-entrantly, however only via {@link
+ * #removeRegistration(Object, ListenerRegistration)}, not via any other removal method. This
  * ensures re-entrant removal does not accidentally remove the incorrect registration.
  *
  * @param <TKey>                key type
@@ -81,30 +88,31 @@
 public abstract class ListenerMultiplexer<TKey, TListener,
         TRegistration extends ListenerRegistration<TListener>, TMergedRegistration> {
 
-    @GuardedBy("mRegistrations")
+    /**
+     * The lock object used by the multiplexer. Acquiring this lock allows for multiple operations
+     * on the multiplexer to be completed atomically. Otherwise, it is not required to hold this
+     * lock. This lock is held while invoking all lifecycle callbacks on both the multiplexer and
+     * any registrations.
+     */
+    protected final Object mMultiplexerLock = new Object();
+
+    @GuardedBy("mMultiplexerLock")
     private final ArrayMap<TKey, TRegistration> mRegistrations = new ArrayMap<>();
 
-    @GuardedBy("mRegistrations")
     private final UpdateServiceBuffer mUpdateServiceBuffer = new UpdateServiceBuffer();
 
-    @GuardedBy("mRegistrations")
     private final ReentrancyGuard mReentrancyGuard = new ReentrancyGuard();
 
-    @GuardedBy("mRegistrations")
+    @GuardedBy("mMultiplexerLock")
     private int mActiveRegistrationsCount = 0;
 
-    @GuardedBy("mRegistrations")
+    @GuardedBy("mMultiplexerLock")
     private boolean mServiceRegistered = false;
 
-    @GuardedBy("mRegistrations")
+    @GuardedBy("mMultiplexerLock")
     @Nullable private TMergedRegistration mMerged;
 
     /**
-     * Should be implemented to return a unique identifying tag that may be used for logging, etc...
-     */
-    public abstract @NonNull String getTag();
-
-    /**
      * Should be implemented to register with the backing service with the given merged
      * registration, and should return true if a matching call to {@link #unregisterWithService()}
      * is required to unregister (ie, if registration succeeds). The set of registrations passed in
@@ -120,6 +128,7 @@
      * @see #mergeRegistrations(Collection)
      * @see #reregisterWithService(Object, Object, Collection)
      */
+    @GuardedBy("mMultiplexerLock")
     protected abstract boolean registerWithService(TMergedRegistration merged,
             @NonNull Collection<TRegistration> registrations);
 
@@ -130,6 +139,7 @@
      *
      * @see #registerWithService(Object, Collection)
      */
+    @GuardedBy("mMultiplexerLock")
     protected boolean reregisterWithService(TMergedRegistration oldMerged,
             TMergedRegistration newMerged, @NonNull Collection<TRegistration> registrations) {
         return registerWithService(newMerged, registrations);
@@ -138,6 +148,7 @@
     /**
      * Should be implemented to unregister from the backing service.
      */
+    @GuardedBy("mMultiplexerLock")
     protected abstract void unregisterWithService();
 
     /**
@@ -147,6 +158,7 @@
      * {@link #updateRegistrations(Predicate)} must be invoked with a function that returns true for
      * any registrations that may have changed their active state.
      */
+    @GuardedBy("mMultiplexerLock")
     protected abstract boolean isActive(@NonNull TRegistration registration);
 
     /**
@@ -157,7 +169,8 @@
      * {@link #reregisterWithService(Object, Object, Collection)} will be invoked with the new
      * merged registration so that the backing service can be updated.
      */
-    protected abstract @Nullable TMergedRegistration mergeRegistrations(
+    @GuardedBy("mMultiplexerLock")
+    protected abstract TMergedRegistration mergeRegistrations(
             @NonNull Collection<TRegistration> registrations);
 
     /**
@@ -166,6 +179,7 @@
      * present while there are any registrations. Invoked while holding the multiplexer's internal
      * lock.
      */
+    @GuardedBy("mMultiplexerLock")
     protected void onRegister() {}
 
     /**
@@ -174,28 +188,38 @@
      * present while there are any registrations. Invoked while holding the multiplexer's internal
      * lock.
      */
+    @GuardedBy("mMultiplexerLock")
     protected void onUnregister() {}
 
     /**
      * Invoked when a registration is added. Invoked while holding the multiplexer's internal lock.
      */
+    @GuardedBy("mMultiplexerLock")
     protected void onRegistrationAdded(@NonNull TKey key, @NonNull TRegistration registration) {}
 
     /**
-     * Invoked instead of {@link #onRegistrationAdded(Object, ListenerRegistration)} if a
-     * registration is replacing an old registration. The old registration will have already been
-     * unregistered. Invoked while holding the multiplexer's internal lock. The default behavior is
-     * simply to call into {@link #onRegistrationAdded(Object, ListenerRegistration)}.
+     * Invoked when one registration replaces another (through {@link #replaceRegistration(Object,
+     * Object, ListenerRegistration)}). The old registration has already been unregistered at this
+     * point. Invoked while holding the multiplexer's internal lock.
+     *
+     * <p>The default behavior is simply to call first {@link #onRegistrationRemoved(Object,
+     * ListenerRegistration)} and then {@link #onRegistrationAdded(Object, ListenerRegistration)}.
      */
-    protected void onRegistrationReplaced(@NonNull TKey key, @NonNull TRegistration oldRegistration,
+    @GuardedBy("mMultiplexerLock")
+    protected void onRegistrationReplaced(
+            @NonNull TKey oldKey,
+            @NonNull TRegistration oldRegistration,
+            @NonNull TKey newKey,
             @NonNull TRegistration newRegistration) {
-        onRegistrationAdded(key, newRegistration);
+        onRegistrationRemoved(oldKey, oldRegistration);
+        onRegistrationAdded(newKey, newRegistration);
     }
 
     /**
      * Invoked when a registration is removed. Invoked while holding the multiplexer's internal
      * lock.
      */
+    @GuardedBy("mMultiplexerLock")
     protected void onRegistrationRemoved(@NonNull TKey key, @NonNull TRegistration registration) {}
 
     /**
@@ -204,6 +228,7 @@
      * need to be present while there are active registrations. Invoked while holding the
      * multiplexer's internal lock.
      */
+    @GuardedBy("mMultiplexerLock")
     protected void onActive() {}
 
     /**
@@ -212,6 +237,7 @@
      * need to be present while there are active registrations. Invoked while holding the
      * multiplexer's internal lock.
      */
+    @GuardedBy("mMultiplexerLock")
     protected void onInactive() {}
 
     /**
@@ -224,13 +250,12 @@
 
     /**
      * Atomically removes the registration with the old key and adds a new registration with the
-     * given key. If there was a registration for the old key,
-     * {@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} will be
-     * invoked for the new registration and key instead of
-     * {@link #onRegistrationAdded(Object, ListenerRegistration)}, even though they may not share
-     * the same key. The old key may be the same value as the new key, in which case this function
-     * is equivalent to {@link #putRegistration(Object, ListenerRegistration)}. This method cannot
-     * be called to add a registration re-entrantly.
+     * given key. If there was a registration for the old key, {@link
+     * #onRegistrationReplaced(Object, ListenerRegistration, Object, ListenerRegistration)} will be
+     * invoked instead of {@link #onRegistrationAdded(Object, ListenerRegistration)}, even if they
+     * share the same key. The old key may be the same value as the new key, in which case this
+     * function is equivalent to {@link #putRegistration(Object, ListenerRegistration)}. This method
+     * cannot be called to add a registration re-entrantly.
      */
     protected final void replaceRegistration(@NonNull TKey oldKey, @NonNull TKey key,
             @NonNull TRegistration registration) {
@@ -238,7 +263,7 @@
         Objects.requireNonNull(key);
         Objects.requireNonNull(registration);
 
-        synchronized (mRegistrations) {
+        synchronized (mMultiplexerLock) {
             // adding listeners reentrantly is not supported
             Preconditions.checkState(!mReentrancyGuard.isReentrant());
 
@@ -257,12 +282,18 @@
                 boolean wasEmpty = mRegistrations.isEmpty();
 
                 TRegistration oldRegistration = null;
-                int index = mRegistrations.indexOfKey(oldKey);
-                if (index >= 0) {
-                    oldRegistration = removeRegistration(index, oldKey != key);
+                int oldIndex = mRegistrations.indexOfKey(oldKey);
+                if (oldIndex >= 0) {
+                    // remove ourselves instead of using remove(), to balance registration callbacks
+                    oldRegistration = mRegistrations.valueAt(oldIndex);
+                    unregister(oldRegistration);
+                    oldRegistration.onUnregister();
+                    if (oldKey != key) {
+                        mRegistrations.removeAt(oldIndex);
+                    }
                 }
-                if (oldKey == key && index >= 0) {
-                    mRegistrations.setValueAt(index, registration);
+                if (oldKey == key && oldIndex >= 0) {
+                    mRegistrations.setValueAt(oldIndex, registration);
                 } else {
                     mRegistrations.put(key, registration);
                 }
@@ -274,7 +305,7 @@
                 if (oldRegistration == null) {
                     onRegistrationAdded(key, registration);
                 } else {
-                    onRegistrationReplaced(key, oldRegistration, registration);
+                    onRegistrationReplaced(oldKey, oldRegistration, key, registration);
                 }
                 onRegistrationActiveChanged(registration);
             }
@@ -282,29 +313,11 @@
     }
 
     /**
-     * Removes the registration with the given key. This method cannot be called to remove a
-     * registration re-entrantly.
-     */
-    protected final void removeRegistration(@NonNull Object key) {
-        synchronized (mRegistrations) {
-            // this method does not support removing listeners reentrantly
-            Preconditions.checkState(!mReentrancyGuard.isReentrant());
-
-            int index = mRegistrations.indexOfKey(key);
-            if (index < 0) {
-                return;
-            }
-
-            removeRegistration(index, true);
-        }
-    }
-
-    /**
      * Removes all registrations with keys that satisfy the given predicate. This method cannot be
      * called to remove a registration re-entrantly.
      */
     protected final void removeRegistrationIf(@NonNull Predicate<TKey> predicate) {
-        synchronized (mRegistrations) {
+        synchronized (mMultiplexerLock) {
             // this method does not support removing listeners reentrantly
             Preconditions.checkState(!mReentrancyGuard.isReentrant());
 
@@ -329,13 +342,31 @@
     }
 
     /**
+     * Removes the registration with the given key. This method cannot be called to remove a
+     * registration re-entrantly.
+     */
+    protected final void removeRegistration(TKey key) {
+        synchronized (mMultiplexerLock) {
+            // this method does not support removing listeners reentrantly
+            Preconditions.checkState(!mReentrancyGuard.isReentrant());
+
+            int index = mRegistrations.indexOfKey(key);
+            if (index < 0) {
+                return;
+            }
+
+            removeRegistration(index);
+        }
+    }
+
+    /**
      * Removes the given registration with the given key. If the given key has a different
      * registration at the time this method is called, nothing happens. This method allows for
      * re-entrancy, and may be called to remove a registration re-entrantly.
      */
-    protected final void removeRegistration(@NonNull Object key,
+    protected final void removeRegistration(@NonNull TKey key,
             @NonNull ListenerRegistration<?> registration) {
-        synchronized (mRegistrations) {
+        synchronized (mMultiplexerLock) {
             int index = mRegistrations.indexOfKey(key);
             if (index < 0) {
                 return;
@@ -350,17 +381,13 @@
                 unregister(typedRegistration);
                 mReentrancyGuard.markForRemoval(key, typedRegistration);
             } else {
-                removeRegistration(index, true);
+                removeRegistration(index);
             }
         }
     }
 
-    @GuardedBy("mRegistrations")
-    private TRegistration removeRegistration(int index, boolean removeEntry) {
-        if (Build.IS_DEBUGGABLE) {
-            Preconditions.checkState(Thread.holdsLock(mRegistrations));
-        }
-
+    @GuardedBy("mMultiplexerLock")
+    private void removeRegistration(int index) {
         TKey key = mRegistrations.keyAt(index);
         TRegistration registration = mRegistrations.valueAt(index);
 
@@ -376,15 +403,11 @@
             unregister(registration);
             onRegistrationRemoved(key, registration);
             registration.onUnregister();
-            if (removeEntry) {
-                mRegistrations.removeAt(index);
-                if (mRegistrations.isEmpty()) {
-                    onUnregister();
-                }
+            mRegistrations.removeAt(index);
+            if (mRegistrations.isEmpty()) {
+                onUnregister();
             }
         }
-
-        return registration;
     }
 
     /**
@@ -392,14 +415,14 @@
      * registration accordingly.
      */
     protected final void updateService() {
-        synchronized (mRegistrations) {
+        synchronized (mMultiplexerLock) {
             if (mUpdateServiceBuffer.isBuffered()) {
                 mUpdateServiceBuffer.markUpdateServiceRequired();
                 return;
             }
 
-            ArrayList<TRegistration> actives = new ArrayList<>(mRegistrations.size());
             final int size = mRegistrations.size();
+            ArrayList<TRegistration> actives = new ArrayList<>(size);
             for (int i = 0; i < size; i++) {
                 TRegistration registration = mRegistrations.valueAt(i);
                 if (registration.isActive()) {
@@ -413,17 +436,17 @@
                     mServiceRegistered = false;
                     unregisterWithService();
                 }
-                return;
-            }
-
-            TMergedRegistration merged = mergeRegistrations(actives);
-            if (!mServiceRegistered || !Objects.equals(merged, mMerged)) {
+            } else {
+                TMergedRegistration merged = mergeRegistrations(actives);
                 if (mServiceRegistered) {
-                    mServiceRegistered = reregisterWithService(mMerged, merged, actives);
+                    if (!Objects.equals(merged, mMerged)) {
+                        mServiceRegistered = reregisterWithService(mMerged, merged, actives);
+                        mMerged = mServiceRegistered ? merged : null;
+                    }
                 } else {
                     mServiceRegistered = registerWithService(merged, actives);
+                    mMerged = mServiceRegistered ? merged : null;
                 }
-                mMerged = mServiceRegistered ? merged : null;
             }
         }
     }
@@ -437,7 +460,7 @@
      * reinitialized.
      */
     protected final void resetService() {
-        synchronized (mRegistrations) {
+        synchronized (mMultiplexerLock) {
             if (mServiceRegistered) {
                 mMerged = null;
                 mServiceRegistered = false;
@@ -453,7 +476,31 @@
      * buffering {@code updateService()} until after multiple adds/removes/updates occur.
      */
     public UpdateServiceLock newUpdateServiceLock() {
-        return new UpdateServiceLock(mUpdateServiceBuffer.acquire());
+        return new UpdateServiceLock(mUpdateServiceBuffer);
+    }
+
+    /**
+     * Evaluates the predicate on all registrations until the predicate returns true, at which point
+     * evaluation will cease. Returns true if the predicate ever returned true, and returns false
+     * otherwise.
+     */
+    protected final boolean findRegistration(Predicate<TRegistration> predicate) {
+        synchronized (mMultiplexerLock) {
+            // we only acquire a reentrancy guard in case of removal while iterating. this method
+            // does not directly affect active state or merged state, so there is no advantage to
+            // acquiring an update source buffer.
+            try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
+                final int size = mRegistrations.size();
+                for (int i = 0; i < size; i++) {
+                    TRegistration registration = mRegistrations.valueAt(i);
+                    if (predicate.test(registration)) {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
     }
 
     /**
@@ -463,7 +510,7 @@
      * the resulting changes.
      */
     protected final void updateRegistrations(@NonNull Predicate<TRegistration> predicate) {
-        synchronized (mRegistrations) {
+        synchronized (mMultiplexerLock) {
             // since updating a registration can invoke a variety of callbacks, we need to ensure
             // those callbacks themselves do not re-enter, as this could lead to out-of-order
             // callbacks. note that try-with-resources ordering is meaningful here as well. we want
@@ -492,7 +539,7 @@
      */
     protected final boolean updateRegistration(@NonNull Object key,
             @NonNull Predicate<TRegistration> predicate) {
-        synchronized (mRegistrations) {
+        synchronized (mMultiplexerLock) {
             // since updating a registration can invoke a variety of callbacks, we need to ensure
             // those callbacks themselves do not re-enter, as this could lead to out-of-order
             // callbacks. note that try-with-resources ordering is meaningful here as well. we want
@@ -515,12 +562,8 @@
         }
     }
 
-    @GuardedBy("mRegistrations")
+    @GuardedBy("mMultiplexerLock")
     private void onRegistrationActiveChanged(TRegistration registration) {
-        if (Build.IS_DEBUGGABLE) {
-            Preconditions.checkState(Thread.holdsLock(mRegistrations));
-        }
-
         boolean active = registration.isRegistered() && isActive(registration);
         boolean changed = registration.setActive(active);
         if (changed) {
@@ -547,7 +590,7 @@
      */
     protected final void deliverToListeners(
             @NonNull Function<TRegistration, ListenerOperation<TListener>> function) {
-        synchronized (mRegistrations) {
+        synchronized (mMultiplexerLock) {
             try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
                 final int size = mRegistrations.size();
                 for (int i = 0; i < size; i++) {
@@ -571,7 +614,7 @@
      * </pre>
      */
     protected final void deliverToListeners(@NonNull ListenerOperation<TListener> operation) {
-        synchronized (mRegistrations) {
+        synchronized (mMultiplexerLock) {
             try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
                 final int size = mRegistrations.size();
                 for (int i = 0; i < size; i++) {
@@ -584,6 +627,7 @@
         }
     }
 
+    @GuardedBy("mMultiplexerLock")
     private void unregister(TRegistration registration) {
         registration.unregisterInternal();
         onRegistrationActiveChanged(registration);
@@ -593,7 +637,7 @@
      * Dumps debug information.
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        synchronized (mRegistrations) {
+        synchronized (mMultiplexerLock) {
             pw.print("service: ");
             pw.print(getServiceState());
             pw.println();
@@ -620,6 +664,7 @@
      * May be overridden to provide additional details on service state when dumping the manager
      * state. Invoked while holding the multiplexer's internal lock.
      */
+    @GuardedBy("mMultiplexerLock")
     protected String getServiceState() {
         if (mServiceRegistered) {
             if (mMerged != null) {
@@ -643,61 +688,63 @@
      */
     private final class ReentrancyGuard implements AutoCloseable {
 
-        @GuardedBy("mRegistrations")
+        @GuardedBy("mMultiplexerLock")
         private int mGuardCount;
-        @GuardedBy("mRegistrations")
-        private @Nullable ArraySet<Entry<Object, ListenerRegistration<?>>> mScheduledRemovals;
+
+        @GuardedBy("mMultiplexerLock")
+        @Nullable private ArraySet<Entry<TKey, ListenerRegistration<?>>> mScheduledRemovals;
 
         ReentrancyGuard() {
             mGuardCount = 0;
             mScheduledRemovals = null;
         }
 
-        @GuardedBy("mRegistrations")
         boolean isReentrant() {
-            if (Build.IS_DEBUGGABLE) {
-                Preconditions.checkState(Thread.holdsLock(mRegistrations));
+            synchronized (mMultiplexerLock) {
+                return mGuardCount != 0;
             }
-            return mGuardCount != 0;
         }
 
-        @GuardedBy("mRegistrations")
-        void markForRemoval(Object key, ListenerRegistration<?> registration) {
-            if (Build.IS_DEBUGGABLE) {
-                Preconditions.checkState(Thread.holdsLock(mRegistrations));
-            }
-            Preconditions.checkState(isReentrant());
+        void markForRemoval(TKey key, ListenerRegistration<?> registration) {
+            synchronized (mMultiplexerLock) {
+                Preconditions.checkState(isReentrant());
 
-            if (mScheduledRemovals == null) {
-                mScheduledRemovals = new ArraySet<>(mRegistrations.size());
+                if (mScheduledRemovals == null) {
+                    mScheduledRemovals = new ArraySet<>(mRegistrations.size());
+                }
+                mScheduledRemovals.add(new AbstractMap.SimpleImmutableEntry<>(key, registration));
             }
-            mScheduledRemovals.add(new AbstractMap.SimpleImmutableEntry<>(key, registration));
         }
 
         ReentrancyGuard acquire() {
-            ++mGuardCount;
-            return this;
+            synchronized (mMultiplexerLock) {
+                ++mGuardCount;
+                return this;
+            }
         }
 
         @Override
         public void close() {
-            ArraySet<Entry<Object, ListenerRegistration<?>>> scheduledRemovals = null;
+            synchronized (mMultiplexerLock) {
+                Preconditions.checkState(mGuardCount > 0);
 
-            Preconditions.checkState(mGuardCount > 0);
-            if (--mGuardCount == 0) {
-                scheduledRemovals = mScheduledRemovals;
-                mScheduledRemovals = null;
-            }
+                ArraySet<Entry<TKey, ListenerRegistration<?>>> scheduledRemovals = null;
 
-            if (scheduledRemovals == null) {
-                return;
-            }
+                if (--mGuardCount == 0) {
+                    scheduledRemovals = mScheduledRemovals;
+                    mScheduledRemovals = null;
+                }
 
-            try (UpdateServiceBuffer ignored = mUpdateServiceBuffer.acquire()) {
-                final int size = scheduledRemovals.size();
-                for (int i = 0; i < size; i++) {
-                    Entry<Object, ListenerRegistration<?>> entry = scheduledRemovals.valueAt(i);
-                    removeRegistration(entry.getKey(), entry.getValue());
+                if (scheduledRemovals == null) {
+                    return;
+                }
+
+                try (UpdateServiceBuffer ignored = mUpdateServiceBuffer.acquire()) {
+                    final int size = scheduledRemovals.size();
+                    for (int i = 0; i < size; i++) {
+                        Entry<TKey, ListenerRegistration<?>> entry = scheduledRemovals.valueAt(i);
+                        removeRegistration(entry.getKey(), entry.getValue());
+                    }
                 }
             }
         }
@@ -721,6 +768,7 @@
 
         @GuardedBy("this")
         private int mBufferCount;
+
         @GuardedBy("this")
         private boolean mUpdateServiceRequired;
 
@@ -765,18 +813,18 @@
      * {@link #close()}ed. This can be used to save work by acquiring the lock before multiple calls
      * to updateService() are expected, and closing the lock after.
      */
-    public final class UpdateServiceLock implements AutoCloseable {
+    public static final class UpdateServiceLock implements AutoCloseable {
 
-        private @Nullable UpdateServiceBuffer mUpdateServiceBuffer;
+        @Nullable private ListenerMultiplexer<?, ?, ?, ?>.UpdateServiceBuffer mUpdateServiceBuffer;
 
-        UpdateServiceLock(UpdateServiceBuffer updateServiceBuffer) {
-            mUpdateServiceBuffer = updateServiceBuffer;
+        UpdateServiceLock(ListenerMultiplexer<?, ?, ?, ?>.UpdateServiceBuffer updateServiceBuffer) {
+            mUpdateServiceBuffer = updateServiceBuffer.acquire();
         }
 
         @Override
         public void close() {
             if (mUpdateServiceBuffer != null) {
-                UpdateServiceBuffer buffer = mUpdateServiceBuffer;
+                ListenerMultiplexer<?, ?, ?, ?>.UpdateServiceBuffer buffer = mUpdateServiceBuffer;
                 mUpdateServiceBuffer = null;
                 buffer.close();
             }
diff --git a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
index 711dde8..fcb2a9b 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
@@ -35,7 +35,7 @@
 
     private boolean mActive;
 
-    private volatile @Nullable TListener mListener;
+    @Nullable private volatile TListener mListener;
 
     protected ListenerRegistration(Executor executor, TListener listener) {
         mExecutor = Objects.requireNonNull(executor);
@@ -43,6 +43,13 @@
         mListener = Objects.requireNonNull(listener);
     }
 
+    /**
+     * Returns a tag to use for logging. Should be overridden by subclasses.
+     */
+    protected String getTag() {
+        return "ListenerRegistration";
+    }
+
     protected final Executor getExecutor() {
         return mExecutor;
     }
@@ -50,26 +57,36 @@
     /**
      * May be overridden by subclasses. Invoked when registration occurs. Invoked while holding the
      * owning multiplexer's internal lock.
+     *
+     * <p>If overridden you must ensure the superclass method is invoked (usually as the first thing
+     * in the overridden method).
      */
     protected void onRegister(Object key) {}
 
     /**
      * May be overridden by subclasses. Invoked when unregistration occurs. Invoked while holding
      * the owning multiplexer's internal lock.
+     *
+     * <p>If overridden you must ensure the superclass method is invoked (usually as the last thing
+     * in the overridden method).
      */
     protected void onUnregister() {}
 
     /**
-     * May be overridden by subclasses. Invoked when this registration becomes active. If this
-     * returns a non-null operation, that operation will be invoked for the listener. Invoked
-     * while holding the owning multiplexer's internal lock.
+     * May be overridden by subclasses. Invoked when this registration becomes active. Invoked while
+     * holding the owning multiplexer's internal lock.
+     *
+     * <p>If overridden you must ensure the superclass method is invoked (usually as the first thing
+     * in the overridden method).
      */
     protected void onActive() {}
 
     /**
-     * May be overridden by subclasses. Invoked when registration becomes inactive. If this returns
-     * a non-null operation, that operation will be invoked for the listener. Invoked while holding
-     * the owning multiplexer's internal lock.
+     * May be overridden by subclasses. Invoked when registration becomes inactive. Invoked while
+     * holding the owning multiplexer's internal lock.
+     *
+     * <p>If overridden you must ensure the superclass method is invoked (usually as the last thing
+     * in the overridden method).
      */
     protected void onInactive() {}
 
diff --git a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
index 240ac01..c976601 100644
--- a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
@@ -16,63 +16,47 @@
 
 package com.android.server.location.listeners;
 
-import android.annotation.Nullable;
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+
 import android.app.PendingIntent;
-import android.location.util.identity.CallerIdentity;
 import android.util.Log;
 
 /**
  * A registration that works with PendingIntent keys, and registers a CancelListener to
- * automatically remove the registration if the PendingIntent is canceled. The key for this
- * registration must either be a {@link PendingIntent} or a {@link PendingIntentKey}.
+ * automatically remove the registration if the PendingIntent is canceled.
  *
- * @param <TRequest>  request type
+ * @param <TKey>      key type
  * @param <TListener> listener type
  */
-public abstract class PendingIntentListenerRegistration<TRequest, TListener> extends
-        RemoteListenerRegistration<TRequest, TListener> implements PendingIntent.CancelListener {
+public abstract class PendingIntentListenerRegistration<TKey, TListener> extends
+        RemovableListenerRegistration<TKey, TListener> implements PendingIntent.CancelListener {
 
-    /**
-     * Interface to allowed pending intent retrieval when keys are not themselves PendingIntents.
-     */
-    public interface PendingIntentKey {
-        /**
-         * Returns the pending intent associated with this key.
-         */
-        PendingIntent getPendingIntent();
+    protected PendingIntentListenerRegistration(TListener listener) {
+        super(DIRECT_EXECUTOR, listener);
     }
 
-    protected PendingIntentListenerRegistration(@Nullable TRequest request,
-            CallerIdentity callerIdentity, TListener listener) {
-        super(request, callerIdentity, listener);
+    protected abstract PendingIntent getPendingIntentFromKey(TKey key);
+
+    @Override
+    protected void onRegister() {
+        super.onRegister();
+
+        if (!getPendingIntentFromKey(getKey()).addCancelListener(DIRECT_EXECUTOR, this)) {
+            remove();
+        }
     }
 
     @Override
-    protected final void onRemovableListenerRegister() {
-        getPendingIntentFromKey(getKey()).registerCancelListener(this);
-        onPendingIntentListenerRegister();
+    protected void onUnregister() {
+        getPendingIntentFromKey(getKey()).removeCancelListener(this);
+
+        super.onUnregister();
     }
 
     @Override
-    protected final void onRemovableListenerUnregister() {
-        onPendingIntentListenerUnregister();
-        getPendingIntentFromKey(getKey()).unregisterCancelListener(this);
-    }
-
-    /**
-     * May be overridden in place of {@link #onRemovableListenerRegister()}.
-     */
-    protected void onPendingIntentListenerRegister() {}
-
-    /**
-     * May be overridden in place of {@link #onRemovableListenerUnregister()}.
-     */
-    protected void onPendingIntentListenerUnregister() {}
-
-    @Override
     protected void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
         if (e instanceof PendingIntent.CanceledException) {
-            Log.w(getOwner().getTag(), "registration " + this + " removed", e);
+            Log.w(getTag(), "registration " + this + " removed", e);
             remove();
         } else {
             super.onOperationFailure(operation, e);
@@ -81,21 +65,10 @@
 
     @Override
     public void onCanceled(PendingIntent intent) {
-        if (Log.isLoggable(getOwner().getTag(), Log.DEBUG)) {
-            Log.d(getOwner().getTag(),
-                    "pending intent registration " + getIdentity() + " canceled");
+        if (Log.isLoggable(getTag(), Log.DEBUG)) {
+            Log.d(getTag(), "pending intent registration " + this + " canceled");
         }
 
         remove();
     }
-
-    private PendingIntent getPendingIntentFromKey(Object key) {
-        if (key instanceof PendingIntent) {
-            return (PendingIntent) key;
-        } else if (key instanceof PendingIntentKey) {
-            return ((PendingIntentKey) key).getPendingIntent();
-        } else {
-            throw new IllegalArgumentException("key must be PendingIntent or PendingIntentKey");
-        }
-    }
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
deleted file mode 100644
index 4eca577..0000000
--- a/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.location.listeners;
-
-
-import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
-
-import android.annotation.Nullable;
-import android.location.util.identity.CallerIdentity;
-import android.os.Process;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.FgThread;
-
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * A listener registration representing a remote (possibly from a different process) listener.
- * Listeners from a different process will be run on a direct executor, since the x-process listener
- * invocation should already be asynchronous. Listeners from the same process will be run on a
- * normal executor, since in-process listener invocation may be synchronous.
- *
- * @param <TRequest>           request type
- * @param <TListener>          listener type
- */
-public abstract class RemoteListenerRegistration<TRequest, TListener> extends
-        RemovableListenerRegistration<TRequest, TListener> {
-
-    @VisibleForTesting
-    public static final Executor IN_PROCESS_EXECUTOR = FgThread.getExecutor();
-
-    private static Executor chooseExecutor(CallerIdentity identity) {
-        // if a client is in the same process as us, binder calls will execute synchronously and
-        // we shouldn't run callbacks directly since they might be run under lock and deadlock
-        if (identity.getPid() == Process.myPid()) {
-            // there's a slight loophole here for pending intents - pending intent callbacks can
-            // always be run on the direct executor since they're always asynchronous, but honestly
-            // you shouldn't be using pending intent callbacks within the same process anyways
-            return IN_PROCESS_EXECUTOR;
-        } else {
-            return DIRECT_EXECUTOR;
-        }
-    }
-
-    private final CallerIdentity mIdentity;
-
-    protected RemoteListenerRegistration(@Nullable TRequest request, CallerIdentity identity,
-            TListener listener) {
-        super(chooseExecutor(identity), request, listener);
-        mIdentity = Objects.requireNonNull(identity);
-    }
-
-    /**
-     * Returns the listener identity.
-     */
-    public final CallerIdentity getIdentity() {
-        return mIdentity;
-    }
-}
-
diff --git a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
index 618ff24..3c302fb 100644
--- a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
@@ -20,22 +20,23 @@
 
 import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * A listener registration that stores its own key, and thus can remove itself. By default it will
  * remove itself if any checked exception occurs on listener execution.
  *
- * @param <TRequest>           request type
+ * @param <TKey>               key type
  * @param <TListener>          listener type
  */
-public abstract class RemovableListenerRegistration<TRequest, TListener> extends
-        RequestListenerRegistration<TRequest, TListener> {
+public abstract class RemovableListenerRegistration<TKey, TListener> extends
+        ListenerRegistration<TListener> {
 
-    private volatile @Nullable Object mKey;
+    @Nullable private volatile TKey mKey;
+    private final AtomicBoolean mRemoved = new AtomicBoolean(false);
 
-    protected RemovableListenerRegistration(Executor executor, @Nullable TRequest request,
-            TListener listener) {
-        super(executor, request, listener);
+    protected RemovableListenerRegistration(Executor executor, TListener listener) {
+        super(executor, listener);
     }
 
     /**
@@ -43,46 +44,76 @@
      * with. Often this is easiest to accomplish by defining registration subclasses as non-static
      * inner classes of the multiplexer they are to be used with.
      */
-    protected abstract ListenerMultiplexer<?, ? super TListener, ?, ?> getOwner();
+    protected abstract ListenerMultiplexer<TKey, ? super TListener, ?, ?> getOwner();
 
     /**
      * Returns the key associated with this registration. May not be invoked before
      * {@link #onRegister(Object)} or after {@link #onUnregister()}.
      */
-    protected final Object getKey() {
+    protected final TKey getKey() {
         return Objects.requireNonNull(mKey);
     }
 
     /**
-     * Removes this registration. Does nothing if invoked before {@link #onRegister(Object)} or
-     * after {@link #onUnregister()}. It is safe to invoke this from within either function.
+     * Convenience method equivalent to invoking {@link #remove(boolean)} with the
+     * {@code immediately} parameter set to true.
      */
     public final void remove() {
-        Object key = mKey;
-        if (key != null) {
-            getOwner().removeRegistration(key, this);
+        remove(true);
+    }
+
+    /**
+     * Removes this registration. If the {@code immediately} parameter is true, all pending listener
+     * invocations will fail. If the {@code immediately} parameter is false, listener invocations
+     * that were scheduled before remove was invoked (including invocations scheduled within {@link
+     * #onRemove(boolean)}) will continue, but any listener invocations scheduled after remove was
+     * invoked will fail.
+     *
+     * <p>Only the first call to this method will ever go through (and so {@link #onRemove(boolean)}
+     * will only ever be invoked once).
+     *
+     * <p>Does nothing if invoked before {@link #onRegister()} or after {@link #onUnregister()}.
+     */
+    public final void remove(boolean immediately) {
+        TKey key = mKey;
+        if (key != null && !mRemoved.getAndSet(true)) {
+            onRemove(immediately);
+            if (immediately) {
+                getOwner().removeRegistration(key, this);
+            } else {
+                executeOperation(listener -> getOwner().removeRegistration(key, this));
+            }
         }
     }
 
+    /**
+     * Invoked just before this registration is removed due to {@link #remove(boolean)}, on the same
+     * thread as the responsible {@link #remove(boolean)} call.
+     *
+     * <p>This method will only ever be invoked once, no matter how many calls to {@link
+     * #remove(boolean)} are made, as any registration can only be removed once.
+     */
+    protected void onRemove(boolean immediately) {}
+
     @Override
     protected final void onRegister(Object key) {
-        mKey = Objects.requireNonNull(key);
-        onRemovableListenerRegister();
+        super.onRegister(key);
+        mKey = (TKey) Objects.requireNonNull(key);
+        onRegister();
     }
 
+    /**
+     * May be overridden by subclasses. Invoked when registration occurs. Invoked while holding the
+     * owning multiplexer's internal lock.
+     *
+     * <p>If overridden you must ensure the superclass method is invoked (usually as the first thing
+     * in the overridden method).
+     */
+    protected void onRegister() {}
+
     @Override
-    protected final void onUnregister() {
-        onRemovableListenerUnregister();
+    protected void onUnregister() {
         mKey = null;
+        super.onUnregister();
     }
-
-    /**
-     * May be overridden in place of {@link #onRegister(Object)}.
-     */
-    protected void onRemovableListenerRegister() {}
-
-    /**
-     * May be overridden in place of {@link #onUnregister()}.
-     */
-    protected void onRemovableListenerUnregister() {}
 }
diff --git a/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java
deleted file mode 100644
index 0c2fc91..0000000
--- a/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.location.listeners;
-
-import java.util.concurrent.Executor;
-
-/**
- * A listener registration object which includes an associated request.
- *
- * @param <TRequest>           request type
- * @param <TListener>          listener type
- */
-public class RequestListenerRegistration<TRequest, TListener> extends
-        ListenerRegistration<TListener> {
-
-    private final TRequest mRequest;
-
-    protected RequestListenerRegistration(Executor executor, TRequest request,
-            TListener listener) {
-        super(executor, listener);
-        mRequest = request;
-    }
-
-    /**
-     * Returns the request associated with this listener, or null if one wasn't supplied.
-     */
-    public TRequest getRequest() {
-        return mRequest;
-    }
-
-    @Override
-    public String toString() {
-        if (mRequest == null) {
-            return "[]";
-        } else {
-            return mRequest.toString();
-        }
-    }
-}
-
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 549fd49..a69a079 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -67,7 +67,6 @@
 import android.location.provider.ProviderRequest;
 import android.location.util.identity.CallerIdentity;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.IBinder;
@@ -115,7 +114,7 @@
 import com.android.server.location.injector.UserInfoHelper;
 import com.android.server.location.injector.UserInfoHelper.UserListener;
 import com.android.server.location.listeners.ListenerMultiplexer;
-import com.android.server.location.listeners.RemoteListenerRegistration;
+import com.android.server.location.listeners.RemovableListenerRegistration;
 import com.android.server.location.settings.LocationSettings;
 import com.android.server.location.settings.LocationUserSettings;
 
@@ -124,8 +123,10 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
 import java.util.function.Predicate;
 
 /**
@@ -354,44 +355,60 @@
         public void deliverOnFlushComplete(int requestCode) {}
     }
 
-    protected abstract class Registration extends RemoteListenerRegistration<LocationRequest,
-            LocationTransport> {
+    protected abstract class Registration extends RemovableListenerRegistration<Object,
+                LocationTransport> {
 
+        private final LocationRequest mBaseRequest;
+        private final CallerIdentity mIdentity;
         private final @PermissionLevel int mPermissionLevel;
 
         // we cache these values because checking/calculating on the fly is more expensive
+        @GuardedBy("mMultiplexerLock")
         private boolean mPermitted;
+        @GuardedBy("mMultiplexerLock")
         private boolean mForeground;
+        @GuardedBy("mMultiplexerLock")
         private LocationRequest mProviderLocationRequest;
+        @GuardedBy("mMultiplexerLock")
         private boolean mIsUsingHighPower;
 
-        private @Nullable Location mLastLocation = null;
+        @Nullable private Location mLastLocation = null;
 
-        protected Registration(LocationRequest request, CallerIdentity identity,
+        protected Registration(LocationRequest request, CallerIdentity identity, Executor executor,
                 LocationTransport transport, @PermissionLevel int permissionLevel) {
-            super(Objects.requireNonNull(request), identity, transport);
+            super(executor, transport);
 
             Preconditions.checkArgument(identity.getListenerId() != null);
             Preconditions.checkArgument(permissionLevel > PERMISSION_NONE);
             Preconditions.checkArgument(!request.getWorkSource().isEmpty());
 
+            mBaseRequest = Objects.requireNonNull(request);
+            mIdentity = Objects.requireNonNull(identity);
             mPermissionLevel = permissionLevel;
             mProviderLocationRequest = request;
         }
 
-        @GuardedBy("mLock")
-        @Override
-        protected final void onRemovableListenerRegister() {
-            if (Build.IS_DEBUGGABLE) {
-                Preconditions.checkState(Thread.holdsLock(mLock));
+        public final CallerIdentity getIdentity() {
+            return mIdentity;
+        }
+
+        public final LocationRequest getRequest() {
+            synchronized (mMultiplexerLock) {
+                return mProviderLocationRequest;
             }
+        }
+
+        @GuardedBy("mMultiplexerLock")
+        @Override
+        protected void onRegister() {
+            super.onRegister();
 
             if (D) {
                 Log.d(TAG, mName + " provider added registration from " + getIdentity() + " -> "
                         + getRequest());
             }
 
-            EVENT_LOG.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
+            EVENT_LOG.logProviderClientRegistered(mName, getIdentity(), mBaseRequest);
 
             // initialization order is important as there are ordering dependencies
             mPermitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
@@ -400,110 +417,72 @@
             mProviderLocationRequest = calculateProviderLocationRequest();
             mIsUsingHighPower = isUsingHighPower();
 
-            onProviderListenerRegister();
-
             if (mForeground) {
                 EVENT_LOG.logProviderClientForeground(mName, getIdentity());
             }
         }
 
-        @GuardedBy("mLock")
+        @GuardedBy("mMultiplexerLock")
         @Override
-        protected final void onRemovableListenerUnregister() {
-            if (Build.IS_DEBUGGABLE) {
-                Preconditions.checkState(Thread.holdsLock(mLock));
-            }
-
-            onProviderListenerUnregister();
-
+        protected void onUnregister() {
             EVENT_LOG.logProviderClientUnregistered(mName, getIdentity());
 
             if (D) {
                 Log.d(TAG, mName + " provider removed registration from " + getIdentity());
             }
+
+            super.onUnregister();
         }
 
-        /**
-         * Subclasses may override this instead of {@link #onRemovableListenerRegister()}.
-         */
-        @GuardedBy("mLock")
-        protected void onProviderListenerRegister() {}
-
-        /**
-         * Subclasses may override this instead of {@link #onRemovableListenerUnregister()}.
-         */
-        @GuardedBy("mLock")
-        protected void onProviderListenerUnregister() {}
-
+        @GuardedBy("mMultiplexerLock")
         @Override
-        protected final void onActive() {
-            if (Build.IS_DEBUGGABLE) {
-                Preconditions.checkState(Thread.holdsLock(mLock));
-            }
-
+        protected void onActive() {
             EVENT_LOG.logProviderClientActive(mName, getIdentity());
 
             if (!getRequest().isHiddenFromAppOps()) {
                 mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, getIdentity());
             }
             onHighPowerUsageChanged();
-
-            onProviderListenerActive();
         }
 
+        @GuardedBy("mMultiplexerLock")
         @Override
-        protected final void onInactive() {
-            if (Build.IS_DEBUGGABLE) {
-                Preconditions.checkState(Thread.holdsLock(mLock));
-            }
-
+        protected void onInactive() {
             onHighPowerUsageChanged();
             if (!getRequest().isHiddenFromAppOps()) {
                 mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, getIdentity());
             }
 
-            onProviderListenerInactive();
-
             EVENT_LOG.logProviderClientInactive(mName, getIdentity());
         }
 
-        /**
-         * Subclasses may override this instead of {@link #onActive()}.
-         */
-        @GuardedBy("mLock")
-        protected void onProviderListenerActive() {}
-
-        /**
-         * Subclasses may override this instead of {@link #onInactive()} ()}.
-         */
-        @GuardedBy("mLock")
-        protected void onProviderListenerInactive() {}
-
-        @Override
-        public final LocationRequest getRequest() {
-            return mProviderLocationRequest;
-        }
-
-        @GuardedBy("mLock")
+        @GuardedBy("mMultiplexerLock")
         final void setLastDeliveredLocation(@Nullable Location location) {
             mLastLocation = location;
         }
 
-        @GuardedBy("mLock")
         public final Location getLastDeliveredLocation() {
-            return mLastLocation;
+            synchronized (mMultiplexerLock) {
+                return mLastLocation;
+            }
         }
 
         public @PermissionLevel int getPermissionLevel() {
-            return mPermissionLevel;
+            synchronized (mMultiplexerLock) {
+                return mPermissionLevel;
+            }
         }
 
         public final boolean isForeground() {
-            return mForeground;
+            synchronized (mMultiplexerLock) {
+                return mForeground;
+            }
         }
 
         public final boolean isPermitted() {
-            return mPermitted;
+            synchronized (mMultiplexerLock) {
+                return mPermitted;
+            }
         }
 
         public final void flush(int requestCode) {
@@ -519,13 +498,14 @@
             return LocationProviderManager.this;
         }
 
-        @GuardedBy("mLock")
         final boolean onProviderPropertiesChanged() {
-            onHighPowerUsageChanged();
-            return false;
+            synchronized (mMultiplexerLock) {
+                onHighPowerUsageChanged();
+                return false;
+            }
         }
 
-        @GuardedBy("mLock")
+        @GuardedBy("mMultiplexerLock")
         private void onHighPowerUsageChanged() {
             boolean isUsingHighPower = isUsingHighPower();
             if (isUsingHighPower != mIsUsingHighPower) {
@@ -541,12 +521,7 @@
             }
         }
 
-        @GuardedBy("mLock")
         private boolean isUsingHighPower() {
-            if (Build.IS_DEBUGGABLE) {
-                Preconditions.checkState(Thread.holdsLock(mLock));
-            }
-
             ProviderProperties properties = getProperties();
             if (properties == null) {
                 return false;
@@ -557,30 +532,28 @@
                     && properties.getPowerUsage() == ProviderProperties.POWER_USAGE_HIGH;
         }
 
-        @GuardedBy("mLock")
         final boolean onLocationPermissionsChanged(@Nullable String packageName) {
-            if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
-                return onLocationPermissionsChanged();
-            }
+            synchronized (mMultiplexerLock) {
+                if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
+                    return onLocationPermissionsChanged();
+                }
 
-            return false;
+                return false;
+            }
         }
 
-        @GuardedBy("mLock")
         final boolean onLocationPermissionsChanged(int uid) {
-            if (getIdentity().getUid() == uid) {
-                return onLocationPermissionsChanged();
-            }
+            synchronized (mMultiplexerLock) {
+                if (getIdentity().getUid() == uid) {
+                    return onLocationPermissionsChanged();
+                }
 
-            return false;
+                return false;
+            }
         }
 
-        @GuardedBy("mLock")
+        @GuardedBy("mMultiplexerLock")
         private boolean onLocationPermissionsChanged() {
-            if (Build.IS_DEBUGGABLE) {
-                Preconditions.checkState(Thread.holdsLock(mLock));
-            }
-
             boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
                     getIdentity());
             if (permitted != mPermitted) {
@@ -603,82 +576,73 @@
             return false;
         }
 
-        @GuardedBy("mLock")
         final boolean onAdasGnssLocationEnabledChanged(int userId) {
-            if (Build.IS_DEBUGGABLE) {
-                Preconditions.checkState(Thread.holdsLock(mLock));
-            }
-
-            if (getIdentity().getUserId() == userId) {
-                return onProviderLocationRequestChanged();
-            }
-
-            return false;
-        }
-
-        @GuardedBy("mLock")
-        final boolean onForegroundChanged(int uid, boolean foreground) {
-            if (Build.IS_DEBUGGABLE) {
-                Preconditions.checkState(Thread.holdsLock(mLock));
-            }
-
-            if (getIdentity().getUid() == uid && foreground != mForeground) {
-                if (D) {
-                    Log.v(TAG, mName + " provider uid " + uid + " foreground = " + foreground);
+            synchronized (mMultiplexerLock) {
+                if (getIdentity().getUserId() == userId) {
+                    return onProviderLocationRequestChanged();
                 }
 
-                mForeground = foreground;
-
-                if (mForeground) {
-                    EVENT_LOG.logProviderClientForeground(mName, getIdentity());
-                } else {
-                    EVENT_LOG.logProviderClientBackground(mName, getIdentity());
-                }
-
-                // note that onProviderLocationRequestChanged() is always called
-                return onProviderLocationRequestChanged()
-                        || mLocationPowerSaveModeHelper.getLocationPowerSaveMode()
-                        == LOCATION_MODE_FOREGROUND_ONLY;
-            }
-
-            return false;
-        }
-
-        @GuardedBy("mLock")
-        final boolean onProviderLocationRequestChanged() {
-            if (Build.IS_DEBUGGABLE) {
-                Preconditions.checkState(Thread.holdsLock(mLock));
-            }
-
-            LocationRequest newRequest = calculateProviderLocationRequest();
-            if (mProviderLocationRequest.equals(newRequest)) {
                 return false;
             }
-
-            LocationRequest oldRequest = mProviderLocationRequest;
-            mProviderLocationRequest = newRequest;
-            onHighPowerUsageChanged();
-            updateService();
-
-            // if bypass state has changed then the active state may have changed
-            return oldRequest.isBypass() != newRequest.isBypass();
         }
 
+        final boolean onForegroundChanged(int uid, boolean foreground) {
+            synchronized (mMultiplexerLock) {
+                if (getIdentity().getUid() == uid && foreground != mForeground) {
+                    if (D) {
+                        Log.v(TAG, mName + " provider uid " + uid + " foreground = " + foreground);
+                    }
+
+                    mForeground = foreground;
+
+                    if (mForeground) {
+                        EVENT_LOG.logProviderClientForeground(mName, getIdentity());
+                    } else {
+                        EVENT_LOG.logProviderClientBackground(mName, getIdentity());
+                    }
+
+                    // note that onProviderLocationRequestChanged() is always called
+                    return onProviderLocationRequestChanged()
+                            || mLocationPowerSaveModeHelper.getLocationPowerSaveMode()
+                            == LOCATION_MODE_FOREGROUND_ONLY;
+                }
+
+                return false;
+            }
+        }
+
+        final boolean onProviderLocationRequestChanged() {
+            synchronized (mMultiplexerLock) {
+                LocationRequest newRequest = calculateProviderLocationRequest();
+                if (mProviderLocationRequest.equals(newRequest)) {
+                    return false;
+                }
+
+                LocationRequest oldRequest = mProviderLocationRequest;
+                mProviderLocationRequest = newRequest;
+                onHighPowerUsageChanged();
+                updateService();
+
+                // if bypass state has changed then the active state may have changed
+                return oldRequest.isBypass() != newRequest.isBypass();
+            }
+        }
+
+        @GuardedBy("mMultiplexerLock")
         private LocationRequest calculateProviderLocationRequest() {
-            LocationRequest baseRequest = super.getRequest();
-            LocationRequest.Builder builder = new LocationRequest.Builder(baseRequest);
+            LocationRequest.Builder builder = new LocationRequest.Builder(mBaseRequest);
 
             if (mPermissionLevel < PERMISSION_FINE) {
                 builder.setQuality(LocationRequest.QUALITY_LOW_POWER);
-                if (baseRequest.getIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
+                if (mBaseRequest.getIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
                     builder.setIntervalMillis(MIN_COARSE_INTERVAL_MS);
                 }
-                if (baseRequest.getMinUpdateIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
+                if (mBaseRequest.getMinUpdateIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
                     builder.setMinUpdateIntervalMillis(MIN_COARSE_INTERVAL_MS);
                 }
             }
 
-            boolean locationSettingsIgnored = baseRequest.isLocationSettingsIgnored();
+            boolean locationSettingsIgnored = mBaseRequest.isLocationSettingsIgnored();
             if (locationSettingsIgnored) {
                 // if we are not currently allowed use location settings ignored, disable it
                 if (!mSettingsHelper.getIgnoreSettingsAllowlist().contains(
@@ -690,7 +654,7 @@
                 builder.setLocationSettingsIgnored(locationSettingsIgnored);
             }
 
-            boolean adasGnssBypass = baseRequest.isAdasGnssBypass();
+            boolean adasGnssBypass = mBaseRequest.isAdasGnssBypass();
             if (adasGnssBypass) {
                 // if we are not currently allowed use adas gnss bypass, disable it
                 if (!GPS_PROVIDER.equals(mName)) {
@@ -710,7 +674,7 @@
             if (!locationSettingsIgnored && !isThrottlingExempt()) {
                 // throttle in the background
                 if (!mForeground) {
-                    builder.setIntervalMillis(max(baseRequest.getIntervalMillis(),
+                    builder.setIntervalMillis(max(mBaseRequest.getIntervalMillis(),
                             mSettingsHelper.getBackgroundThrottleIntervalMs()));
                 }
             }
@@ -727,8 +691,7 @@
             return mLocationManagerInternal.isProvider(null, getIdentity());
         }
 
-        @GuardedBy("mLock")
-        abstract @Nullable ListenerOperation<LocationTransport> acceptLocationChange(
+        @Nullable abstract ListenerOperation<LocationTransport> acceptLocationChange(
                 LocationResult fineLocationResult);
 
         @Override
@@ -769,13 +732,19 @@
         final ExternalWakeLockReleaser mWakeLockReleaser;
 
         private volatile ProviderTransport mProviderTransport;
+
+        @GuardedBy("mMultiplexerLock")
         private int mNumLocationsDelivered = 0;
+        @GuardedBy("mMultiplexerLock")
         private long mExpirationRealtimeMs = Long.MAX_VALUE;
 
         protected <TTransport extends LocationTransport & ProviderTransport> LocationRegistration(
-                LocationRequest request, CallerIdentity identity, TTransport transport,
+                LocationRequest request,
+                CallerIdentity identity,
+                Executor executor,
+                TTransport transport,
                 @PermissionLevel int permissionLevel) {
-            super(request, identity, transport, permissionLevel);
+            super(request, identity, executor, transport, permissionLevel);
             mProviderTransport = transport;
             mWakeLock = Objects.requireNonNull(mContext.getSystemService(PowerManager.class))
                     .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
@@ -789,9 +758,13 @@
             mProviderTransport = null;
         }
 
-        @GuardedBy("mLock")
+        // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+        @SuppressWarnings("GuardedBy")
+        @GuardedBy("mMultiplexerLock")
         @Override
-        protected final void onProviderListenerRegister() {
+        protected void onRegister() {
+            super.onRegister();
+
             long registerTimeMs = SystemClock.elapsedRealtime();
             mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(registerTimeMs);
 
@@ -810,8 +783,6 @@
             // start listening for provider enabled/disabled events
             addEnabledListener(this);
 
-            onLocationListenerRegister();
-
             // if the provider is currently disabled, let the client know immediately
             int userId = getIdentity().getUserId();
             if (!isEnabled(userId)) {
@@ -819,9 +790,11 @@
             }
         }
 
-        @GuardedBy("mLock")
+        // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+        @SuppressWarnings("GuardedBy")
+        @GuardedBy("mMultiplexerLock")
         @Override
-        protected final void onProviderListenerUnregister() {
+        protected void onUnregister() {
             // stop listening for provider enabled/disabled events
             removeEnabledListener(this);
 
@@ -830,24 +803,16 @@
                 mAlarmHelper.cancel(this);
             }
 
-            onLocationListenerUnregister();
+            super.onUnregister();
         }
 
-        /**
-         * Subclasses may override this instead of {@link #onRemovableListenerRegister()}.
-         */
-        @GuardedBy("mLock")
-        protected void onLocationListenerRegister() {}
-
-        /**
-         * Subclasses may override this instead of {@link #onRemovableListenerUnregister()}.
-         */
-        @GuardedBy("mLock")
-        protected void onLocationListenerUnregister() {}
-
-        @GuardedBy("mLock")
+        // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+        @SuppressWarnings("GuardedBy")
+        @GuardedBy("mMultiplexerLock")
         @Override
-        protected final void onProviderListenerActive() {
+        protected void onActive() {
+            super.onActive();
+
             // a new registration may not get a location immediately, the provider request may be
             // delayed. therefore we deliver a historical location if available. since delivering an
             // older location could be considered a breaking change for some applications, we only
@@ -883,21 +848,17 @@
                         + " expired at " + TimeUtils.formatRealtime(mExpirationRealtimeMs));
             }
 
-            synchronized (mLock) {
+            synchronized (mMultiplexerLock) {
                 // no need to remove alarm after it's fired
                 mExpirationRealtimeMs = Long.MAX_VALUE;
                 remove();
             }
         }
 
-        @GuardedBy("mLock")
+        @GuardedBy("mMultiplexerLock")
         @Override
         @Nullable ListenerOperation<LocationTransport> acceptLocationChange(
                 LocationResult fineLocationResult) {
-            if (Build.IS_DEBUGGABLE) {
-                Preconditions.checkState(Thread.holdsLock(mLock));
-            }
-
             // check expiration time - alarm is not guaranteed to go off at the right time,
             // especially for short intervals
             if (SystemClock.elapsedRealtime() >= mExpirationRealtimeMs) {
@@ -1017,9 +978,7 @@
                                         + " finished after " + mNumLocationsDelivered + " updates");
                             }
 
-                            synchronized (mLock) {
-                                remove();
-                            }
+                            remove();
                         }
                     }
                 }
@@ -1049,12 +1008,18 @@
 
         LocationListenerRegistration(LocationRequest request, CallerIdentity identity,
                 LocationListenerTransport transport, @PermissionLevel int permissionLevel) {
-            super(request, identity, transport, permissionLevel);
+            super(request, identity,
+                    identity.isMyProcess() ? FgThread.getExecutor() : DIRECT_EXECUTOR, transport,
+                    permissionLevel);
         }
 
-        @GuardedBy("mLock")
+        // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+        @SuppressWarnings("GuardedBy")
+        @GuardedBy("mMultiplexerLock")
         @Override
-        protected void onLocationListenerRegister() {
+        protected void onRegister() {
+            super.onRegister();
+
             try {
                 ((IBinder) getKey()).linkToDeath(this, 0);
             } catch (RemoteException e) {
@@ -1062,10 +1027,22 @@
             }
         }
 
-        @GuardedBy("mLock")
+        // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+        @SuppressWarnings("GuardedBy")
+        @GuardedBy("mMultiplexerLock")
         @Override
-        protected void onLocationListenerUnregister() {
-            ((IBinder) getKey()).unlinkToDeath(this, 0);
+        protected void onUnregister() {
+            try {
+                ((IBinder) getKey()).unlinkToDeath(this, 0);
+            } catch (NoSuchElementException e) {
+                // the only way this exception can occur should be if another exception has been
+                // thrown prior to registration completing, and that exception is currently
+                // unwinding the call stack and causing this cleanup. since that exception should
+                // crash us anyways, drop this exception so we're not hiding the original exception.
+                Log.w(getTag(), "failed to unregister binder death listener", e);
+            }
+
+            super.onUnregister();
         }
 
         @Override
@@ -1083,9 +1060,7 @@
         private void onTransportFailure(Exception e) {
             if (e instanceof RemoteException) {
                 Log.w(TAG, mName + " provider registration " + getIdentity() + " removed", e);
-                synchronized (mLock) {
-                    remove();
-                }
+                remove();
             } else {
                 throw new AssertionError(e);
             }
@@ -1098,9 +1073,7 @@
                     Log.d(TAG, mName + " provider registration " + getIdentity() + " died");
                 }
 
-                synchronized (mLock) {
-                    remove();
-                }
+                remove();
             } catch (RuntimeException e) {
                 // the caller may swallow runtime exceptions, so we rethrow as assertion errors to
                 // ensure the crash is seen
@@ -1115,21 +1088,27 @@
         LocationPendingIntentRegistration(LocationRequest request,
                 CallerIdentity identity, LocationPendingIntentTransport transport,
                 @PermissionLevel int permissionLevel) {
-            super(request, identity, transport, permissionLevel);
+            super(request, identity, DIRECT_EXECUTOR, transport, permissionLevel);
         }
 
-        @GuardedBy("mLock")
+        // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+        @SuppressWarnings("GuardedBy")
+        @GuardedBy("mMultiplexerLock")
         @Override
-        protected void onLocationListenerRegister() {
+        protected void onRegister() {
+            super.onRegister();
             if (!((PendingIntent) getKey()).addCancelListener(DIRECT_EXECUTOR, this)) {
                 remove();
             }
         }
 
-        @GuardedBy("mLock")
+        // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+        @SuppressWarnings("GuardedBy")
+        @GuardedBy("mMultiplexerLock")
         @Override
-        protected void onLocationListenerUnregister() {
+        protected void onUnregister() {
             ((PendingIntent) getKey()).removeCancelListener(this);
+            super.onUnregister();
         }
 
         @Override
@@ -1147,9 +1126,7 @@
         private void onTransportFailure(Exception e) {
             if (e instanceof PendingIntent.CanceledException) {
                 Log.w(TAG, mName + " provider registration " + getIdentity() + " removed", e);
-                synchronized (mLock) {
-                    remove();
-                }
+                remove();
             } else {
                 throw new AssertionError(e);
             }
@@ -1161,25 +1138,32 @@
                 Log.d(TAG, mName + " provider registration " + getIdentity() + " canceled");
             }
 
-            synchronized (mLock) {
-                remove();
-            }
+            remove();
         }
     }
 
     protected final class GetCurrentLocationListenerRegistration extends Registration implements
             IBinder.DeathRecipient, OnAlarmListener {
 
+        @GuardedBy("mMultiplexerLock")
         private long mExpirationRealtimeMs = Long.MAX_VALUE;
 
         GetCurrentLocationListenerRegistration(LocationRequest request,
                 CallerIdentity identity, LocationTransport transport, int permissionLevel) {
-            super(request, identity, transport, permissionLevel);
+            super(request,
+                    identity,
+                    identity.isMyProcess() ? FgThread.getExecutor() : DIRECT_EXECUTOR,
+                    transport,
+                    permissionLevel);
         }
 
-        @GuardedBy("mLock")
+        // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+        @SuppressWarnings("GuardedBy")
+        @GuardedBy("mMultiplexerLock")
         @Override
-        protected void onProviderListenerRegister() {
+        protected void onRegister() {
+            super.onRegister();
+
             try {
                 ((IBinder) getKey()).linkToDeath(this, 0);
             } catch (RemoteException e) {
@@ -1202,20 +1186,36 @@
             }
         }
 
-        @GuardedBy("mLock")
+        // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+        @SuppressWarnings("GuardedBy")
+        @GuardedBy("mMultiplexerLock")
         @Override
-        protected void onProviderListenerUnregister() {
+        protected void onUnregister() {
             // remove alarm for expiration
             if (mExpirationRealtimeMs < Long.MAX_VALUE) {
                 mAlarmHelper.cancel(this);
             }
 
-            ((IBinder) getKey()).unlinkToDeath(this, 0);
+            try {
+                ((IBinder) getKey()).unlinkToDeath(this, 0);
+            } catch (NoSuchElementException e) {
+                // the only way this exception can occur should be if another exception has been
+                // thrown prior to registration completing, and that exception is currently
+                // unwinding the call stack and causing this cleanup. since that exception should
+                // crash us anyways, drop this exception so we're not hiding the original exception.
+                Log.w(getTag(), "failed to unregister binder death listener", e);
+            }
+
+            super.onUnregister();
         }
 
-        @GuardedBy("mLock")
+        // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+        @SuppressWarnings("GuardedBy")
+        @GuardedBy("mMultiplexerLock")
         @Override
-        protected void onProviderListenerActive() {
+        protected void onActive() {
+            super.onActive();
+
             Location lastLocation = getLastLocationUnsafe(
                     getIdentity().getUserId(),
                     getPermissionLevel(),
@@ -1226,17 +1226,19 @@
             }
         }
 
-        @GuardedBy("mLock")
+        // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+        @SuppressWarnings("GuardedBy")
+        @GuardedBy("mMultiplexerLock")
         @Override
-        protected void onProviderListenerInactive() {
+        protected void onInactive() {
             // if we go inactive for any reason, fail immediately
             executeOperation(acceptLocationChange(null));
+            super.onInactive();
         }
 
+        @GuardedBy("mMultiplexerLock")
         void deliverNull() {
-            synchronized (mLock) {
-                executeOperation(acceptLocationChange(null));
-            }
+            executeOperation(acceptLocationChange(null));
         }
 
         @Override
@@ -1246,21 +1248,17 @@
                         + " expired at " + TimeUtils.formatRealtime(mExpirationRealtimeMs));
             }
 
-            synchronized (mLock) {
+            synchronized (mMultiplexerLock) {
                 // no need to remove alarm after it's fired
                 mExpirationRealtimeMs = Long.MAX_VALUE;
                 executeOperation(acceptLocationChange(null));
             }
         }
 
-        @GuardedBy("mLock")
+        @GuardedBy("mMultiplexerLock")
         @Override
         @Nullable ListenerOperation<LocationTransport> acceptLocationChange(
                 @Nullable LocationResult fineLocationResult) {
-            if (Build.IS_DEBUGGABLE) {
-                Preconditions.checkState(Thread.holdsLock(mLock));
-            }
-
             // check expiration time - alarm is not guaranteed to go off at the right time,
             // especially for short intervals
             if (SystemClock.elapsedRealtime() >= mExpirationRealtimeMs) {
@@ -1311,9 +1309,7 @@
                     // on failure we're automatically removed anyways, no need to attempt removal
                     // again
                     if (success) {
-                        synchronized (mLock) {
-                            remove();
-                        }
+                        remove();
                     }
                 }
             };
@@ -1324,9 +1320,7 @@
                 Exception e) {
             if (e instanceof RemoteException) {
                 Log.w(TAG, mName + " provider registration " + getIdentity() + " removed", e);
-                synchronized (mLock) {
-                    remove();
-                }
+                remove();
             } else {
                 throw new AssertionError(e);
             }
@@ -1339,9 +1333,7 @@
                     Log.d(TAG, mName + " provider registration " + getIdentity() + " died");
                 }
 
-                synchronized (mLock) {
-                    remove();
-                }
+                remove();
             } catch (RuntimeException e) {
                 // the caller may swallow runtime exceptions, so we rethrow as assertion errors to
                 // ensure the crash is seen
@@ -1350,23 +1342,21 @@
         }
     }
 
-    protected final Object mLock = new Object();
-
     protected final String mName;
-    private final @Nullable PassiveLocationProviderManager mPassiveManager;
+    @Nullable private final PassiveLocationProviderManager mPassiveManager;
 
     protected final Context mContext;
 
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     private @State int mState;
 
     // maps of user id to value
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     private final SparseBooleanArray mEnabled; // null or not present means unknown
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     private final SparseArray<LastLocation> mLastLocations;
 
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     private final ArrayList<ProviderEnabledListener> mEnabledListeners;
 
     private final CopyOnWriteArrayList<IProviderRequestListener> mProviderRequestListeners;
@@ -1418,14 +1408,14 @@
     private final ScreenInteractiveChangedListener mScreenInteractiveChangedListener =
             this::onScreenInteractiveChanged;
 
-    // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
+    // acquiring mMultiplexerLock makes operations on mProvider atomic, but is otherwise unnecessary
     protected final MockableLocationProvider mProvider;
 
-    @GuardedBy("mLock")
-    private @Nullable OnAlarmListener mDelayedRegister;
+    @GuardedBy("mMultiplexerLock")
+    @Nullable private OnAlarmListener mDelayedRegister;
 
-    @GuardedBy("mLock")
-    private @Nullable StateChangedListener mStateChangedListener;
+    @GuardedBy("mMultiplexerLock")
+    @Nullable private StateChangedListener mStateChangedListener;
 
     public LocationProviderManager(Context context, Injector injector,
             String name, @Nullable PassiveLocationProviderManager passiveManager) {
@@ -1453,19 +1443,14 @@
         mLocationUsageLogger = injector.getLocationUsageLogger();
         mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
 
-        mProvider = new MockableLocationProvider(mLock);
+        mProvider = new MockableLocationProvider(mMultiplexerLock);
 
         // set listener last, since this lets our reference escape
         mProvider.getController().setListener(this);
     }
 
-    @Override
-    public String getTag() {
-        return TAG;
-    }
-
     public void startManager(@Nullable StateChangedListener listener) {
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             Preconditions.checkState(mState == STATE_STOPPED);
             mState = STATE_STARTED;
             mStateChangedListener = listener;
@@ -1485,7 +1470,7 @@
     }
 
     public void stopManager() {
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             Preconditions.checkState(mState == STATE_STARTED);
             mState = STATE_STOPPING;
 
@@ -1522,11 +1507,11 @@
         return mProvider.getState();
     }
 
-    public @Nullable CallerIdentity getProviderIdentity() {
+    @Nullable public CallerIdentity getProviderIdentity() {
         return mProvider.getState().identity;
     }
 
-    public @Nullable ProviderProperties getProperties() {
+    @Nullable public ProviderProperties getProperties() {
         return mProvider.getState().properties;
     }
 
@@ -1543,7 +1528,7 @@
 
         Preconditions.checkArgument(userId >= 0);
 
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             int index = mEnabled.indexOfKey(userId);
             if (index < 0) {
                 // this generally shouldn't occur, but might be possible due to race conditions
@@ -1558,14 +1543,14 @@
     }
 
     public void addEnabledListener(ProviderEnabledListener listener) {
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             Preconditions.checkState(mState != STATE_STOPPED);
             mEnabledListeners.add(listener);
         }
     }
 
     public void removeEnabledListener(ProviderEnabledListener listener) {
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             Preconditions.checkState(mState != STATE_STOPPED);
             mEnabledListeners.remove(listener);
         }
@@ -1582,7 +1567,7 @@
     }
 
     public void setRealProvider(@Nullable AbstractLocationProvider provider) {
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             Preconditions.checkState(mState != STATE_STOPPED);
 
             final long identity = Binder.clearCallingIdentity();
@@ -1595,7 +1580,7 @@
     }
 
     public void setMockProvider(@Nullable MockLocationProvider provider) {
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             Preconditions.checkState(mState != STATE_STOPPED);
 
             EVENT_LOG.logProviderMocked(mName, provider != null);
@@ -1622,7 +1607,7 @@
     }
 
     public void setMockProviderAllowed(boolean enabled) {
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             if (!mProvider.isMock()) {
                 throw new IllegalArgumentException(mName + " provider is not a test provider");
             }
@@ -1637,7 +1622,7 @@
     }
 
     public void setMockProviderLocation(Location location) {
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             if (!mProvider.isMock()) {
                 throw new IllegalArgumentException(mName + " provider is not a test provider");
             }
@@ -1659,7 +1644,7 @@
         }
     }
 
-    public @Nullable Location getLastLocation(LastLocationRequest request,
+    @Nullable public Location getLastLocation(LastLocationRequest request,
             CallerIdentity identity, @PermissionLevel int permissionLevel) {
         request = calculateLastLocationRequest(request, identity);
 
@@ -1732,7 +1717,7 @@
      * location, even if the permissionLevel is coarse. You are responsible for coarsening the
      * location if necessary.
      */
-    public @Nullable Location getLastLocationUnsafe(int userId,
+    @Nullable public Location getLastLocationUnsafe(int userId,
             @PermissionLevel int permissionLevel, boolean isBypass,
             long maximumAgeMs) {
         if (userId == UserHandle.USER_ALL) {
@@ -1756,7 +1741,7 @@
         Preconditions.checkArgument(userId >= 0);
 
         Location location;
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             Preconditions.checkState(mState != STATE_STOPPED);
             LastLocation lastLocation = mLastLocations.get(userId);
             if (lastLocation == null) {
@@ -1778,7 +1763,7 @@
     }
 
     public void injectLastLocation(Location location, int userId) {
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             Preconditions.checkState(mState != STATE_STOPPED);
             if (getLastLocationUnsafe(userId, PERMISSION_FINE, false, Long.MAX_VALUE) == null) {
                 setLastLocation(location, userId);
@@ -1800,7 +1785,7 @@
 
         Preconditions.checkArgument(userId >= 0);
 
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             LastLocation lastLocation = mLastLocations.get(userId);
             if (lastLocation == null) {
                 lastLocation = new LastLocation();
@@ -1814,7 +1799,7 @@
         }
     }
 
-    public @Nullable ICancellationSignal getCurrentLocation(LocationRequest request,
+    @Nullable public ICancellationSignal getCurrentLocation(LocationRequest request,
             CallerIdentity identity, int permissionLevel, ILocationCallback callback) {
         if (request.getDurationMillis() > MAX_GET_CURRENT_LOCATION_TIMEOUT_MS) {
             request = new LocationRequest.Builder(request)
@@ -1829,7 +1814,7 @@
                         new GetCurrentLocationTransport(callback),
                         permissionLevel);
 
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             Preconditions.checkState(mState != STATE_STOPPED);
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -1849,9 +1834,7 @@
                         () -> {
                             final long ident = Binder.clearCallingIdentity();
                             try {
-                                synchronized (mLock) {
-                                    removeRegistration(callback.asBinder(), registration);
-                                }
+                                removeRegistration(callback.asBinder(), registration);
                             } catch (RuntimeException e) {
                                 // since this is within a oneway binder transaction there is nowhere
                                 // for exceptions to go - move onto another thread to crash system
@@ -1885,7 +1868,7 @@
                 new LocationListenerTransport(listener),
                 permissionLevel);
 
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             Preconditions.checkState(mState != STATE_STOPPED);
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -1904,7 +1887,7 @@
                 new LocationPendingIntentTransport(mContext, pendingIntent),
                 permissionLevel);
 
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             Preconditions.checkState(mState != STATE_STOPPED);
             final long identity = Binder.clearCallingIdentity();
             try {
@@ -1916,42 +1899,38 @@
     }
 
     public void flush(ILocationListener listener, int requestCode) {
-        synchronized (mLock) {
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                boolean flushed = updateRegistration(listener.asBinder(), registration -> {
-                    registration.flush(requestCode);
-                    return false;
-                });
-                if (!flushed) {
-                    throw new IllegalArgumentException("unregistered listener cannot be flushed");
-                }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            boolean flushed = updateRegistration(listener.asBinder(), registration -> {
+                registration.flush(requestCode);
+                return false;
+            });
+            if (!flushed) {
+                throw new IllegalArgumentException("unregistered listener cannot be flushed");
             }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
     }
 
     public void flush(PendingIntent pendingIntent, int requestCode) {
-        synchronized (mLock) {
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                boolean flushed = updateRegistration(pendingIntent, registration -> {
-                    registration.flush(requestCode);
-                    return false;
-                });
-                if (!flushed) {
-                    throw new IllegalArgumentException(
-                            "unregistered pending intent cannot be flushed");
-                }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            boolean flushed = updateRegistration(pendingIntent, registration -> {
+                registration.flush(requestCode);
+                return false;
+            });
+            if (!flushed) {
+                throw new IllegalArgumentException(
+                        "unregistered pending intent cannot be flushed");
             }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
     }
 
     public void unregisterLocationRequest(ILocationListener listener) {
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             Preconditions.checkState(mState != STATE_STOPPED);
             final long identity = Binder.clearCallingIdentity();
             try {
@@ -1963,7 +1942,7 @@
     }
 
     public void unregisterLocationRequest(PendingIntent pendingIntent) {
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             Preconditions.checkState(mState != STATE_STOPPED);
             final long identity = Binder.clearCallingIdentity();
             try {
@@ -1974,13 +1953,9 @@
         }
     }
 
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     @Override
     protected void onRegister() {
-        if (Build.IS_DEBUGGABLE) {
-            Preconditions.checkState(Thread.holdsLock(mLock));
-        }
-
         mSettingsHelper.addOnBackgroundThrottleIntervalChangedListener(
                 mBackgroundThrottleIntervalChangedListener);
         mSettingsHelper.addOnBackgroundThrottlePackageWhitelistChangedListener(
@@ -1997,13 +1972,9 @@
         mScreenInteractiveHelper.addListener(mScreenInteractiveChangedListener);
     }
 
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     @Override
     protected void onUnregister() {
-        if (Build.IS_DEBUGGABLE) {
-            Preconditions.checkState(Thread.holdsLock(mLock));
-        }
-
         mSettingsHelper.removeOnBackgroundThrottleIntervalChangedListener(
                 mBackgroundThrottleIntervalChangedListener);
         mSettingsHelper.removeOnBackgroundThrottlePackageWhitelistChangedListener(
@@ -2019,13 +1990,9 @@
         mScreenInteractiveHelper.removeListener(mScreenInteractiveChangedListener);
     }
 
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     @Override
     protected void onRegistrationAdded(Object key, Registration registration) {
-        if (Build.IS_DEBUGGABLE) {
-            Preconditions.checkState(Thread.holdsLock(mLock));
-        }
-
         mLocationUsageLogger.logLocationApiUsage(
                 LocationStatsEnums.USAGE_STARTED,
                 LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
@@ -2038,23 +2005,21 @@
                 null, registration.isForeground());
     }
 
-    @GuardedBy("mLock")
+    // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+    @SuppressWarnings("GuardedBy")
+    @GuardedBy("mMultiplexerLock")
     @Override
-    protected void onRegistrationReplaced(Object key, Registration oldRegistration,
-            Registration newRegistration) {
+    protected void onRegistrationReplaced(Object oldKey, Registration oldRegistration,
+            Object newKey, Registration newRegistration) {
         // by saving the last delivered location state we are able to potentially delay the
         // resulting provider request longer and save additional power
         newRegistration.setLastDeliveredLocation(oldRegistration.getLastDeliveredLocation());
-        super.onRegistrationReplaced(key, oldRegistration, newRegistration);
+        super.onRegistrationReplaced(oldKey, oldRegistration, newKey, newRegistration);
     }
 
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     @Override
     protected void onRegistrationRemoved(Object key, Registration registration) {
-        if (Build.IS_DEBUGGABLE) {
-            Preconditions.checkState(Thread.holdsLock(mLock));
-        }
-
         mLocationUsageLogger.logLocationApiUsage(
                 LocationStatsEnums.USAGE_ENDED,
                 LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
@@ -2067,21 +2032,17 @@
                 null, registration.isForeground());
     }
 
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     @Override
     protected boolean registerWithService(ProviderRequest request,
             Collection<Registration> registrations) {
         return reregisterWithService(ProviderRequest.EMPTY_REQUEST, request, registrations);
     }
 
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     @Override
     protected boolean reregisterWithService(ProviderRequest oldRequest,
             ProviderRequest newRequest, Collection<Registration> registrations) {
-        if (Build.IS_DEBUGGABLE) {
-            Preconditions.checkState(Thread.holdsLock(mLock));
-        }
-
         // calculate how long the new request should be delayed before sending it off to the
         // provider, under the assumption that once we send the request off, the provider will
         // immediately attempt to deliver a new location satisfying that request.
@@ -2117,7 +2078,7 @@
             mDelayedRegister = new OnAlarmListener() {
                 @Override
                 public void onAlarm() {
-                    synchronized (mLock) {
+                    synchronized (mMultiplexerLock) {
                         if (mDelayedRegister == this) {
                             mDelayedRegister = null;
                             setProviderRequest(newRequest);
@@ -2135,17 +2096,13 @@
         return true;
     }
 
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     @Override
     protected void unregisterWithService() {
-        if (Build.IS_DEBUGGABLE) {
-            Preconditions.checkState(Thread.holdsLock(mLock));
-        }
-
         setProviderRequest(ProviderRequest.EMPTY_REQUEST);
     }
 
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     void setProviderRequest(ProviderRequest request) {
         if (mDelayedRegister != null) {
             mAlarmHelper.cancel(mDelayedRegister);
@@ -2166,13 +2123,9 @@
         });
     }
 
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     @Override
     protected boolean isActive(Registration registration) {
-        if (Build.IS_DEBUGGABLE) {
-            Preconditions.checkState(Thread.holdsLock(mLock));
-        }
-
         if (!registration.isPermitted()) {
             return false;
         }
@@ -2236,13 +2189,9 @@
         return true;
     }
 
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     @Override
     protected ProviderRequest mergeRegistrations(Collection<Registration> registrations) {
-        if (Build.IS_DEBUGGABLE) {
-            Preconditions.checkState(Thread.holdsLock(mLock));
-        }
-
         long intervalMs = ProviderRequest.INTERVAL_DISABLED;
         int quality = LocationRequest.QUALITY_LOW_POWER;
         long maxUpdateDelayMs = Long.MAX_VALUE;
@@ -2307,7 +2256,7 @@
                 .build();
     }
 
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     protected long calculateRequestDelayMillis(long newIntervalMs,
             Collection<Registration> registrations) {
         // calculate the minimum delay across all registrations, ensuring that it is not more than
@@ -2349,7 +2298,7 @@
     }
 
     private void onUserChanged(int userId, int change) {
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             if (mState == STATE_STOPPED) {
                 return;
             }
@@ -2372,15 +2321,13 @@
     private void onLocationUserSettingsChanged(int userId, LocationUserSettings oldSettings,
             LocationUserSettings newSettings) {
         if (oldSettings.isAdasGnssLocationEnabled() != newSettings.isAdasGnssLocationEnabled()) {
-            synchronized (mLock) {
-                updateRegistrations(
-                        registration -> registration.onAdasGnssLocationEnabledChanged(userId));
-            }
+            updateRegistrations(
+                    registration -> registration.onAdasGnssLocationEnabledChanged(userId));
         }
     }
 
     private void onLocationEnabledChanged(int userId) {
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             if (mState == STATE_STOPPED) {
                 return;
             }
@@ -2390,88 +2337,64 @@
     }
 
     private void onScreenInteractiveChanged(boolean screenInteractive) {
-        synchronized (mLock) {
-            switch (mLocationPowerSaveModeHelper.getLocationPowerSaveMode()) {
-                case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
-                    if (!GPS_PROVIDER.equals(mName)) {
-                        break;
-                    }
-                    // fall through
-                case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
-                    // fall through
-                case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
-                    updateRegistrations(registration -> true);
+        switch (mLocationPowerSaveModeHelper.getLocationPowerSaveMode()) {
+            case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
+                if (!GPS_PROVIDER.equals(mName)) {
                     break;
-                default:
-                    break;
-            }
+                }
+                // fall through
+            case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
+                // fall through
+            case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
+                updateRegistrations(registration -> true);
+                break;
+            default:
+                break;
         }
     }
 
     private void onBackgroundThrottlePackageWhitelistChanged() {
-        synchronized (mLock) {
-            updateRegistrations(Registration::onProviderLocationRequestChanged);
-        }
+        updateRegistrations(Registration::onProviderLocationRequestChanged);
     }
 
     private void onBackgroundThrottleIntervalChanged() {
-        synchronized (mLock) {
-            updateRegistrations(Registration::onProviderLocationRequestChanged);
-        }
+        updateRegistrations(Registration::onProviderLocationRequestChanged);
     }
 
     private void onLocationPowerSaveModeChanged(@LocationPowerSaveMode int locationPowerSaveMode) {
-        synchronized (mLock) {
-            // this is rare, just assume everything has changed to keep it simple
-            updateRegistrations(registration -> true);
-        }
+        // this is rare, just assume everything has changed to keep it simple
+        updateRegistrations(registration -> true);
     }
 
     private void onAppForegroundChanged(int uid, boolean foreground) {
-        synchronized (mLock) {
-            updateRegistrations(registration -> registration.onForegroundChanged(uid, foreground));
-        }
+        updateRegistrations(registration -> registration.onForegroundChanged(uid, foreground));
     }
 
     private void onAdasAllowlistChanged() {
-        synchronized (mLock) {
-            updateRegistrations(Registration::onProviderLocationRequestChanged);
-        }
+        updateRegistrations(Registration::onProviderLocationRequestChanged);
     }
 
     private void onIgnoreSettingsWhitelistChanged() {
-        synchronized (mLock) {
-            updateRegistrations(Registration::onProviderLocationRequestChanged);
-        }
+        updateRegistrations(Registration::onProviderLocationRequestChanged);
     }
 
     private void onLocationPackageBlacklistChanged(int userId) {
-        synchronized (mLock) {
-            updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
-        }
+        updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
     }
 
     private void onLocationPermissionsChanged(@Nullable String packageName) {
-        synchronized (mLock) {
-            updateRegistrations(
-                    registration -> registration.onLocationPermissionsChanged(packageName));
-        }
+        updateRegistrations(
+                registration -> registration.onLocationPermissionsChanged(packageName));
     }
 
     private void onLocationPermissionsChanged(int uid) {
-        synchronized (mLock) {
-            updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid));
-        }
+        updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid));
     }
 
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     @Override
     public void onStateChanged(
             AbstractLocationProvider.State oldState, AbstractLocationProvider.State newState) {
-        if (Build.IS_DEBUGGABLE) {
-            Preconditions.checkState(Thread.holdsLock(mLock));
-        }
-
         if (oldState.allowed != newState.allowed) {
             onEnabledChanged(UserHandle.USER_ALL);
         }
@@ -2487,13 +2410,9 @@
         }
     }
 
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     @Override
     public void onReportLocation(LocationResult locationResult) {
-        if (Build.IS_DEBUGGABLE) {
-            Preconditions.checkState(Thread.holdsLock(mLock));
-        }
-
         LocationResult filtered;
         if (mPassiveManager != null) {
             filtered = locationResult.filter(location -> {
@@ -2549,12 +2468,8 @@
         }
     }
 
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     private void onUserStarted(int userId) {
-        if (Build.IS_DEBUGGABLE) {
-            Preconditions.checkState(Thread.holdsLock(mLock));
-        }
-
         if (userId == UserHandle.USER_NULL) {
             return;
         }
@@ -2572,12 +2487,8 @@
         }
     }
 
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     private void onUserStopped(int userId) {
-        if (Build.IS_DEBUGGABLE) {
-            Preconditions.checkState(Thread.holdsLock(mLock));
-        }
-
         if (userId == UserHandle.USER_NULL) {
             return;
         }
@@ -2592,12 +2503,8 @@
         }
     }
 
-    @GuardedBy("mLock")
+    @GuardedBy("mMultiplexerLock")
     private void onEnabledChanged(int userId) {
-        if (Build.IS_DEBUGGABLE) {
-            Preconditions.checkState(Thread.holdsLock(mLock));
-        }
-
         if (userId == UserHandle.USER_NULL) {
             // used during initialization - ignore since many lower level operations (checking
             // settings for instance) do not support the null user
@@ -2697,7 +2604,7 @@
     }
 
     public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             ipw.print(mName);
             ipw.print(" provider");
             if (mProvider.isMock()) {
@@ -2738,10 +2645,10 @@
 
     private static class LastLocation {
 
-        private @Nullable Location mFineLocation;
-        private @Nullable Location mCoarseLocation;
-        private @Nullable Location mFineBypassLocation;
-        private @Nullable Location mCoarseBypassLocation;
+        @Nullable private Location mFineLocation;
+        @Nullable private Location mCoarseLocation;
+        @Nullable private Location mFineBypassLocation;
+        @Nullable private Location mCoarseBypassLocation;
 
         LastLocation() {}
 
@@ -2765,7 +2672,7 @@
             mCoarseLocation = null;
         }
 
-        public @Nullable Location get(@PermissionLevel int permissionLevel,
+        @Nullable public Location get(@PermissionLevel int permissionLevel,
                 boolean isBypass) {
             switch (permissionLevel) {
                 case PERMISSION_FINE:
@@ -2862,7 +2769,7 @@
         private static class GatedCallback implements Runnable {
 
             @GuardedBy("this")
-            private @Nullable Runnable mCallback;
+            @Nullable private Runnable mCallback;
 
             @GuardedBy("this")
             private boolean mGate;
diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
index b35af4f..0cb4f9e 100644
--- a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
@@ -54,7 +54,7 @@
      * Reports a new location to passive location provider clients.
      */
     public void updateLocation(LocationResult locationResult) {
-        synchronized (mLock) {
+        synchronized (mMultiplexerLock) {
             PassiveLocationProvider passive = (PassiveLocationProvider) mProvider.getProvider();
             Preconditions.checkState(passive != null);
 
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index c5f73625..8ab3a94 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1587,7 +1587,7 @@
                 if (!savedCredential.isNone()) {
                     throw new IllegalStateException("Saved credential given, but user has no SP");
                 }
-                initializeSyntheticPasswordLocked(savedCredential, userId);
+                initializeSyntheticPasswordLocked(userId);
             } else if (savedCredential.isNone() && isProfileWithUnifiedLock(userId)) {
                 // get credential from keystore when profile has unified lock
                 try {
@@ -2513,35 +2513,21 @@
     }
 
     /**
-     * Creates the synthetic password (SP) for the given user and protects it with the user's LSKF.
+     * Creates the synthetic password (SP) for the given user and protects it with an empty LSKF.
      * This is called just once in the lifetime of the user: the first time a nonempty LSKF is set,
      * or when an escrow token is activated on a device with an empty LSKF.
-     *
-     * Maintains the SP invariants described in {@link SyntheticPasswordManager}.
      */
     @GuardedBy("mSpManager")
     @VisibleForTesting
-    SyntheticPassword initializeSyntheticPasswordLocked(LockscreenCredential credential,
-            int userId) {
+    SyntheticPassword initializeSyntheticPasswordLocked(int userId) {
         Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
         Preconditions.checkState(getCurrentLskfBasedProtectorId(userId) ==
                 SyntheticPasswordManager.NULL_PROTECTOR_ID,
                 "Cannot reinitialize SP");
 
         final SyntheticPassword sp = mSpManager.newSyntheticPassword(userId);
-        long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(), credential,
-                sp, userId);
-        if (!credential.isNone()) {
-            mSpManager.newSidForUser(getGateKeeperService(), sp, userId);
-            mSpManager.verifyChallenge(getGateKeeperService(), sp, 0L, userId);
-            setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
-            setKeystorePassword(sp.deriveKeyStorePassword(), userId);
-        } else {
-            clearUserKeyProtection(userId, null);
-            setKeystorePassword(null, userId);
-            gateKeeperClearSecureUserId(userId);
-        }
-        fixateNewestUserKeyAuth(userId);
+        final long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
+                LockscreenCredential.createNone(), sp, userId);
         setCurrentLskfBasedProtectorId(protectorId, userId);
         onSyntheticPasswordKnown(userId, sp);
         return sp;
@@ -2818,8 +2804,7 @@
             if (!isUserSecure(userId)) {
                 long protectorId = getCurrentLskfBasedProtectorId(userId);
                 if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
-                    sp = initializeSyntheticPasswordLocked(LockscreenCredential.createNone(),
-                            userId);
+                    sp = initializeSyntheticPasswordLocked(userId);
                 } else {
                     sp = mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
                             LockscreenCredential.createNone(), userId, null).syntheticPassword;
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index e27cbea..bfa8af9 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -927,8 +927,9 @@
                         routerRecord.mUserRecord.mHandler, routerRecord, manager));
         }
 
-        userRecord.mHandler.sendMessage(obtainMessage(UserHandler::notifyRoutesToManager,
-                userRecord.mHandler, manager));
+        userRecord.mHandler.sendMessage(
+                obtainMessage(
+                        UserHandler::notifyInitialRoutesToManager, userRecord.mHandler, manager));
     }
 
     private void unregisterManagerLocked(@NonNull IMediaRouter2Manager manager, boolean died) {
@@ -1311,6 +1312,36 @@
                 new CopyOnWriteArrayList<>();
         private final Map<String, RouterRecord> mSessionToRouterMap = new ArrayMap<>();
 
+        /**
+         * Latest list of routes sent to privileged {@link android.media.MediaRouter2 routers} and
+         * {@link android.media.MediaRouter2Manager managers}.
+         *
+         * <p>Privileged routers are instances of {@link android.media.MediaRouter2 MediaRouter2}
+         * that have {@code MODIFY_AUDIO_ROUTING} permission.
+         *
+         * <p>This list contains all routes exposed by route providers. This includes routes from
+         * both system route providers and user route providers.
+         *
+         * <p>See {@link #getRouters(boolean hasModifyAudioRoutingPermission)}.
+         */
+        private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToPrivilegedRouters =
+                new ArrayMap<>();
+
+        /**
+         * Latest list of routes sent to non-privileged {@link android.media.MediaRouter2 routers}.
+         *
+         * <p>Non-privileged routers are instances of {@link android.media.MediaRouter2
+         * MediaRouter2} that do <i><b>not</b></i> have {@code MODIFY_AUDIO_ROUTING} permission.
+         *
+         * <p>This list contains all routes exposed by user route providers. It might also include
+         * the current default route from {@link #mSystemProvider} to expose local route updates
+         * (e.g. volume changes) to non-privileged routers.
+         *
+         * <p>See {@link SystemMediaRoute2Provider#mDefaultRoute}.
+         */
+        private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToNonPrivilegedRouters =
+                new ArrayMap<>();
+
         private boolean mRunning;
 
         // TODO: (In Android S+) Pull out SystemMediaRoute2Provider out of UserHandler.
@@ -1425,91 +1456,182 @@
         }
 
         private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) {
-            int providerInfoIndex = getLastProviderInfoIndex(provider.getUniqueId());
             MediaRoute2ProviderInfo currentInfo = provider.getProviderInfo();
-            MediaRoute2ProviderInfo prevInfo =
-                    (providerInfoIndex < 0) ? null : mLastProviderInfos.get(providerInfoIndex);
-            if (Objects.equals(prevInfo, currentInfo)) return;
 
-            List<MediaRoute2Info> addedRoutes = new ArrayList<>();
-            List<MediaRoute2Info> removedRoutes = new ArrayList<>();
-            List<MediaRoute2Info> changedRoutes = new ArrayList<>();
+            int providerInfoIndex =
+                    indexOfRouteProviderInfoByUniqueId(provider.getUniqueId(), mLastProviderInfos);
+
+            MediaRoute2ProviderInfo prevInfo =
+                    providerInfoIndex == -1 ? null : mLastProviderInfos.get(providerInfoIndex);
+
+            // Ignore if no changes
+            if (Objects.equals(prevInfo, currentInfo)) {
+                return;
+            }
+
+            boolean hasAddedOrModifiedRoutes = false;
+            boolean hasRemovedRoutes = false;
+
+            boolean isSystemProvider = provider.mIsSystemRouteProvider;
+
             if (prevInfo == null) {
+                // Provider is being added.
                 mLastProviderInfos.add(currentInfo);
-                addedRoutes.addAll(currentInfo.getRoutes());
+                addToRoutesMap(currentInfo.getRoutes(), isSystemProvider);
+                // Check if new provider exposes routes.
+                hasAddedOrModifiedRoutes = !currentInfo.getRoutes().isEmpty();
             } else if (currentInfo == null) {
+                // Provider is being removed.
+                hasRemovedRoutes = true;
                 mLastProviderInfos.remove(prevInfo);
-                removedRoutes.addAll(prevInfo.getRoutes());
+                removeFromRoutesMap(prevInfo.getRoutes(), isSystemProvider);
             } else {
+                // Provider is being updated.
                 mLastProviderInfos.set(providerInfoIndex, currentInfo);
-                final Collection<MediaRoute2Info> prevRoutes = prevInfo.getRoutes();
                 final Collection<MediaRoute2Info> currentRoutes = currentInfo.getRoutes();
 
+                // Checking for individual routes.
                 for (MediaRoute2Info route : currentRoutes) {
                     if (!route.isValid()) {
-                        Slog.w(TAG, "onProviderStateChangedOnHandler: Ignoring invalid route : "
-                                + route);
+                        Slog.w(
+                                TAG,
+                                "onProviderStateChangedOnHandler: Ignoring invalid route : "
+                                        + route);
                         continue;
                     }
+
                     MediaRoute2Info prevRoute = prevInfo.getRoute(route.getOriginalId());
-                    if (prevRoute == null) {
-                        addedRoutes.add(route);
-                    } else if (!Objects.equals(prevRoute, route)) {
-                        changedRoutes.add(route);
+                    if (prevRoute == null || !Objects.equals(prevRoute, route)) {
+                        hasAddedOrModifiedRoutes = true;
+                        mLastNotifiedRoutesToPrivilegedRouters.put(route.getId(), route);
+                        if (!isSystemProvider) {
+                            mLastNotifiedRoutesToNonPrivilegedRouters.put(route.getId(), route);
+                        }
                     }
                 }
 
+                // Checking for individual removals
                 for (MediaRoute2Info prevRoute : prevInfo.getRoutes()) {
                     if (currentInfo.getRoute(prevRoute.getOriginalId()) == null) {
-                        removedRoutes.add(prevRoute);
+                        hasRemovedRoutes = true;
+                        mLastNotifiedRoutesToPrivilegedRouters.remove(prevRoute.getId());
+                        if (!isSystemProvider) {
+                            mLastNotifiedRoutesToNonPrivilegedRouters.remove(prevRoute.getId());
+                        }
                     }
                 }
             }
 
+            dispatchUpdates(
+                    hasAddedOrModifiedRoutes,
+                    hasRemovedRoutes,
+                    isSystemProvider,
+                    mSystemProvider.getDefaultRoute());
+        }
+
+        /**
+         * Adds provided routes to {@link #mLastNotifiedRoutesToPrivilegedRouters}. Also adds them
+         * to {@link #mLastNotifiedRoutesToNonPrivilegedRouters} if they were provided by a
+         * non-system route provider. Overwrites any route with matching id that already exists.
+         *
+         * @param routes list of routes to be added.
+         * @param isSystemRoutes indicates whether routes come from a system route provider.
+         */
+        private void addToRoutesMap(
+                @NonNull Collection<MediaRoute2Info> routes, boolean isSystemRoutes) {
+            for (MediaRoute2Info route : routes) {
+                if (!isSystemRoutes) {
+                    mLastNotifiedRoutesToNonPrivilegedRouters.put(route.getId(), route);
+                }
+                mLastNotifiedRoutesToPrivilegedRouters.put(route.getId(), route);
+            }
+        }
+
+        /**
+         * Removes provided routes from {@link #mLastNotifiedRoutesToPrivilegedRouters}. Also
+         * removes them from {@link #mLastNotifiedRoutesToNonPrivilegedRouters} if they were
+         * provided by a non-system route provider.
+         *
+         * @param routes list of routes to be removed.
+         * @param isSystemRoutes whether routes come from a system route provider.
+         */
+        private void removeFromRoutesMap(
+                @NonNull Collection<MediaRoute2Info> routes, boolean isSystemRoutes) {
+            for (MediaRoute2Info route : routes) {
+                if (!isSystemRoutes) {
+                    mLastNotifiedRoutesToNonPrivilegedRouters.remove(route.getId());
+                }
+                mLastNotifiedRoutesToPrivilegedRouters.remove(route.getId());
+            }
+        }
+
+        /**
+         * Dispatches the latest route updates in {@link #mLastNotifiedRoutesToPrivilegedRouters}
+         * and {@link #mLastNotifiedRoutesToNonPrivilegedRouters} to registered {@link
+         * android.media.MediaRouter2 routers} and {@link MediaRouter2Manager managers} after a call
+         * to {@link #onProviderStateChangedOnHandler(MediaRoute2Provider)}. Ignores if no changes
+         * were made.
+         *
+         * @param hasAddedOrModifiedRoutes whether routes were added or modified.
+         * @param hasRemovedRoutes whether routes were removed.
+         * @param isSystemProvider whether the latest update was caused by a system provider.
+         * @param defaultRoute the current default route in {@link #mSystemProvider}.
+         */
+        private void dispatchUpdates(
+                boolean hasAddedOrModifiedRoutes,
+                boolean hasRemovedRoutes,
+                boolean isSystemProvider,
+                MediaRoute2Info defaultRoute) {
+
+            // Ignore if no changes.
+            if (!hasAddedOrModifiedRoutes && !hasRemovedRoutes) {
+                return;
+            }
+
             List<IMediaRouter2> routersWithModifyAudioRoutingPermission = getRouters(true);
             List<IMediaRouter2> routersWithoutModifyAudioRoutingPermission = getRouters(false);
             List<IMediaRouter2Manager> managers = getManagers();
-            List<MediaRoute2Info> defaultRoute = new ArrayList<>();
-            defaultRoute.add(mSystemProvider.getDefaultRoute());
 
-            if (addedRoutes.size() > 0) {
-                notifyRoutesAddedToRouters(routersWithModifyAudioRoutingPermission, addedRoutes);
-                if (!provider.mIsSystemRouteProvider) {
-                    notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission,
-                            addedRoutes);
-                } else if (prevInfo == null) {
-                    notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission,
-                            defaultRoute);
-                } // 'else' is handled as changed routes
-                notifyRoutesAddedToManagers(managers, addedRoutes);
-            }
-            if (removedRoutes.size() > 0) {
-                notifyRoutesRemovedToRouters(routersWithModifyAudioRoutingPermission,
-                        removedRoutes);
-                if (!provider.mIsSystemRouteProvider) {
-                    notifyRoutesRemovedToRouters(routersWithoutModifyAudioRoutingPermission,
-                            removedRoutes);
-                }
-                notifyRoutesRemovedToManagers(managers, removedRoutes);
-            }
-            if (changedRoutes.size() > 0) {
-                notifyRoutesChangedToRouters(routersWithModifyAudioRoutingPermission,
-                        changedRoutes);
-                if (!provider.mIsSystemRouteProvider) {
-                    notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission,
-                            changedRoutes);
-                } else if (prevInfo != null) {
-                    notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission,
-                            defaultRoute);
-                } // 'else' is handled as added routes
-                notifyRoutesChangedToManagers(managers, changedRoutes);
+            // Managers receive all provider updates with all routes.
+            notifyRoutesUpdatedToManagers(
+                    managers, new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
+
+            // Routers with modify audio permission (usually system routers) receive all provider
+            // updates with all routes.
+            notifyRoutesUpdatedToRouters(
+                    routersWithModifyAudioRoutingPermission,
+                    new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
+
+            if (!isSystemProvider) {
+                // Regular routers receive updates from all non-system providers with all non-system
+                // routes.
+                notifyRoutesUpdatedToRouters(
+                        routersWithoutModifyAudioRoutingPermission,
+                        new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
+            } else if (hasAddedOrModifiedRoutes) {
+                // On system provider updates, regular routers receive the updated default route.
+                // This is the only system route they should receive.
+                mLastNotifiedRoutesToNonPrivilegedRouters.put(defaultRoute.getId(), defaultRoute);
+                notifyRoutesUpdatedToRouters(
+                        routersWithoutModifyAudioRoutingPermission,
+                        new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
             }
         }
 
-        private int getLastProviderInfoIndex(@NonNull String providerId) {
-            for (int i = 0; i < mLastProviderInfos.size(); i++) {
-                MediaRoute2ProviderInfo providerInfo = mLastProviderInfos.get(i);
-                if (TextUtils.equals(providerInfo.getUniqueId(), providerId)) {
+        /**
+         * Returns the index of the first element in {@code lastProviderInfos} that matches the
+         * specified unique id.
+         *
+         * @param uniqueId unique id of {@link MediaRoute2ProviderInfo} to be found.
+         * @param lastProviderInfos list of {@link MediaRoute2ProviderInfo}.
+         * @return index of found element, or -1 if not found.
+         */
+        private static int indexOfRouteProviderInfoByUniqueId(
+                @NonNull String uniqueId,
+                @NonNull List<MediaRoute2ProviderInfo> lastProviderInfos) {
+            for (int i = 0; i < lastProviderInfos.size(); i++) {
+                MediaRoute2ProviderInfo providerInfo = lastProviderInfos.get(i);
+                if (TextUtils.equals(providerInfo.getUniqueId(), uniqueId)) {
                     return i;
                 }
             }
@@ -1989,41 +2111,19 @@
             }
         }
 
-        private void notifyRoutesAddedToRouters(@NonNull List<IMediaRouter2> routers,
-                @NonNull List<MediaRoute2Info> routes) {
+        private void notifyRoutesUpdatedToRouters(
+                @NonNull List<IMediaRouter2> routers, @NonNull List<MediaRoute2Info> routes) {
             for (IMediaRouter2 router : routers) {
                 try {
-                    router.notifyRoutesAdded(routes);
+                    router.notifyRoutesUpdated(routes);
                 } catch (RemoteException ex) {
-                    Slog.w(TAG, "Failed to notify routes added. Router probably died.", ex);
+                    Slog.w(TAG, "Failed to notify routes updated. Router probably died.", ex);
                 }
             }
         }
 
-        private void notifyRoutesRemovedToRouters(@NonNull List<IMediaRouter2> routers,
-                @NonNull List<MediaRoute2Info> routes) {
-            for (IMediaRouter2 router : routers) {
-                try {
-                    router.notifyRoutesRemoved(routes);
-                } catch (RemoteException ex) {
-                    Slog.w(TAG, "Failed to notify routes removed. Router probably died.", ex);
-                }
-            }
-        }
-
-        private void notifyRoutesChangedToRouters(@NonNull List<IMediaRouter2> routers,
-                @NonNull List<MediaRoute2Info> routes) {
-            for (IMediaRouter2 router : routers) {
-                try {
-                    router.notifyRoutesChanged(routes);
-                } catch (RemoteException ex) {
-                    Slog.w(TAG, "Failed to notify routes changed. Router probably died.", ex);
-                }
-            }
-        }
-
-        private void notifySessionInfoChangedToRouters(@NonNull List<IMediaRouter2> routers,
-                @NonNull RoutingSessionInfo sessionInfo) {
+        private void notifySessionInfoChangedToRouters(
+                @NonNull List<IMediaRouter2> routers, @NonNull RoutingSessionInfo sessionInfo) {
             for (IMediaRouter2 router : routers) {
                 try {
                     router.notifySessionInfoChanged(sessionInfo);
@@ -2033,48 +2133,31 @@
             }
         }
 
-        private void notifyRoutesToManager(@NonNull IMediaRouter2Manager manager) {
-            List<MediaRoute2Info> routes = new ArrayList<>();
-            for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {
-                routes.addAll(providerInfo.getRoutes());
-            }
-            if (routes.size() == 0) {
+        /**
+         * Notifies {@code manager} with all known routes. This only happens once after {@code
+         * manager} is registered through {@link #registerManager(IMediaRouter2Manager, String)
+         * registerManager()}.
+         *
+         * @param manager {@link IMediaRouter2Manager} to be notified.
+         */
+        private void notifyInitialRoutesToManager(@NonNull IMediaRouter2Manager manager) {
+            if (mLastNotifiedRoutesToPrivilegedRouters.isEmpty()) {
                 return;
             }
             try {
-                manager.notifyRoutesAdded(routes);
+                manager.notifyRoutesUpdated(
+                        new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
             } catch (RemoteException ex) {
                 Slog.w(TAG, "Failed to notify all routes. Manager probably died.", ex);
             }
         }
 
-        private void notifyRoutesAddedToManagers(@NonNull List<IMediaRouter2Manager> managers,
+        private void notifyRoutesUpdatedToManagers(
+                @NonNull List<IMediaRouter2Manager> managers,
                 @NonNull List<MediaRoute2Info> routes) {
             for (IMediaRouter2Manager manager : managers) {
                 try {
-                    manager.notifyRoutesAdded(routes);
-                } catch (RemoteException ex) {
-                    Slog.w(TAG, "Failed to notify routes added. Manager probably died.", ex);
-                }
-            }
-        }
-
-        private void notifyRoutesRemovedToManagers(@NonNull List<IMediaRouter2Manager> managers,
-                @NonNull List<MediaRoute2Info> routes) {
-            for (IMediaRouter2Manager manager : managers) {
-                try {
-                    manager.notifyRoutesRemoved(routes);
-                } catch (RemoteException ex) {
-                    Slog.w(TAG, "Failed to notify routes removed. Manager probably died.", ex);
-                }
-            }
-        }
-
-        private void notifyRoutesChangedToManagers(@NonNull List<IMediaRouter2Manager> managers,
-                @NonNull List<MediaRoute2Info> routes) {
-            for (IMediaRouter2Manager manager : managers) {
-                try {
-                    manager.notifyRoutesChanged(routes);
+                    manager.notifyRoutesUpdated(routes);
                 } catch (RemoteException ex) {
                     Slog.w(TAG, "Failed to notify routes changed. Manager probably died.", ex);
                 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 2070c2b..aa2a25b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1445,6 +1445,11 @@
                     }
 
                     if (flags != data.getFlags()) {
+                        int changedFlags = data.getFlags() ^ flags;
+                        if ((changedFlags & FLAG_SUPPRESS_NOTIFICATION) != 0) {
+                            // Suppress notification flag changed, clear any effects
+                            clearEffectsLocked(key);
+                        }
                         data.setFlags(flags);
                         // Shouldn't alert again just because of a flag change.
                         r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
@@ -1596,6 +1601,20 @@
         updateLightsLocked();
     }
 
+    @GuardedBy("mNotificationLock")
+    private void clearEffectsLocked(String key) {
+        if (key.equals(mSoundNotificationKey)) {
+            clearSoundLocked();
+        }
+        if (key.equals(mVibrateNotificationKey)) {
+            clearVibrateLocked();
+        }
+        boolean removed = mLights.remove(key);
+        if (removed) {
+            updateLightsLocked();
+        }
+    }
+
     protected final BroadcastReceiver mLocaleChangeReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -5170,7 +5189,8 @@
                     extras,
                     mRankingHelper.findExtractor(ValidateNotificationPeople.class),
                     MATCHES_CALL_FILTER_CONTACTS_TIMEOUT_MS,
-                    MATCHES_CALL_FILTER_TIMEOUT_AFFINITY);
+                    MATCHES_CALL_FILTER_TIMEOUT_AFFINITY,
+                    Binder.getCallingUid());
         }
 
         @Override
@@ -6664,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);
     }
@@ -7813,7 +7847,8 @@
                 && (record.getSuppressedVisualEffects() & SUPPRESSED_EFFECT_STATUS_BAR) != 0;
         if (!record.isUpdate
                 && record.getImportance() > IMPORTANCE_MIN
-                && !suppressedByDnd) {
+                && !suppressedByDnd
+                && isNotificationForCurrentUser(record)) {
             sendAccessibilityEvent(record);
             sentAccessibilityEvent = true;
         }
diff --git a/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java
index fde45f71..acb36a0 100644
--- a/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java
+++ b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java
@@ -42,10 +42,6 @@
      */
     public static void scheduleJob(Context context, long rescheduleTimeMillis) {
         JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
-        // if the job already exists for some reason, cancel & reschedule
-        if (jobScheduler.getPendingJob(JOB_ID) != null) {
-            jobScheduler.cancel(JOB_ID);
-        }
         ComponentName component = new ComponentName(
                 context, ReviewNotificationPermissionsJobService.class);
         JobInfo newJob = new JobInfo.Builder(JOB_ID, component)
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/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 7d7f3a9..c0bc474 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -66,6 +66,8 @@
     private static final int TYPE_SET_NOTIFICATION_POLICY = 16;
     private static final int TYPE_SET_CONSOLIDATED_ZEN_POLICY = 17;
     private static final int TYPE_MATCHES_CALL_FILTER = 18;
+    private static final int TYPE_RECORD_CALLER = 19;
+    private static final int TYPE_CHECK_REPEAT_CALLER = 20;
 
     private static int sNext;
     private static int sSize;
@@ -167,11 +169,28 @@
             + hintsToString(newHints) + ",listeners=" + listenerCount);
     }
 
-    /*
+    /**
      * Trace calls to matchesCallFilter with the result of the call and the reason for the result.
      */
-    public static void traceMatchesCallFilter(boolean result, String reason) {
-        append(TYPE_MATCHES_CALL_FILTER, "result=" + result + ", reason=" + reason);
+    public static void traceMatchesCallFilter(boolean result, String reason, int callingUid) {
+        append(TYPE_MATCHES_CALL_FILTER, "result=" + result + ", reason=" + reason
+                + ", calling uid=" + callingUid);
+    }
+
+    /**
+     * Trace what information is available about an incoming call when it's recorded
+     */
+    public static void traceRecordCaller(boolean hasPhone, boolean hasUri) {
+        append(TYPE_RECORD_CALLER, "has phone number=" + hasPhone + ", has uri=" + hasUri);
+    }
+
+    /**
+     * Trace what information was provided about a caller when checking whether it is from a repeat
+     * caller
+     */
+    public static void traceCheckRepeatCaller(boolean found, boolean hasPhone, boolean hasUri) {
+        append(TYPE_CHECK_REPEAT_CALLER, "res=" + found + ", given phone number=" + hasPhone
+                + ", given uri=" + hasUri);
     }
 
     private static String subscribeResult(IConditionProvider provider, RemoteException e) {
@@ -198,6 +217,8 @@
             case TYPE_SET_NOTIFICATION_POLICY: return "set_notification_policy";
             case TYPE_SET_CONSOLIDATED_ZEN_POLICY: return "set_consolidated_policy";
             case TYPE_MATCHES_CALL_FILTER: return "matches_call_filter";
+            case TYPE_RECORD_CALLER: return "record_caller";
+            case TYPE_CHECK_REPEAT_CALLER: return "check_repeat_caller";
             default: return "unknown";
         }
     }
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index b0d40ef..7e36aed 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -101,23 +101,24 @@
      */
     public static boolean matchesCallFilter(Context context, int zen, NotificationManager.Policy
             consolidatedPolicy, UserHandle userHandle, Bundle extras,
-            ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
+            ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity,
+            int callingUid) {
         if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
-            ZenLog.traceMatchesCallFilter(false, "no interruptions");
+            ZenLog.traceMatchesCallFilter(false, "no interruptions", callingUid);
             return false; // nothing gets through
         }
         if (zen == Global.ZEN_MODE_ALARMS) {
-            ZenLog.traceMatchesCallFilter(false, "alarms only");
+            ZenLog.traceMatchesCallFilter(false, "alarms only", callingUid);
             return false; // not an alarm
         }
         if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
             if (consolidatedPolicy.allowRepeatCallers()
                     && REPEAT_CALLERS.isRepeat(context, extras, null)) {
-                ZenLog.traceMatchesCallFilter(true, "repeat caller");
+                ZenLog.traceMatchesCallFilter(true, "repeat caller", callingUid);
                 return true;
             }
             if (!consolidatedPolicy.allowCalls()) {
-                ZenLog.traceMatchesCallFilter(false, "calls not allowed");
+                ZenLog.traceMatchesCallFilter(false, "calls not allowed", callingUid);
                 return false; // no other calls get through
             }
             if (validator != null) {
@@ -125,11 +126,12 @@
                         contactsTimeoutMs, timeoutAffinity);
                 boolean match =
                         audienceMatches(consolidatedPolicy.allowCallsFrom(), contactAffinity);
-                ZenLog.traceMatchesCallFilter(match, "contact affinity " + contactAffinity);
+                ZenLog.traceMatchesCallFilter(match, "contact affinity " + contactAffinity,
+                        callingUid);
                 return match;
             }
         }
-        ZenLog.traceMatchesCallFilter(true, "no restrictions");
+        ZenLog.traceMatchesCallFilter(true, "no restrictions", callingUid);
         return true;
     }
 
@@ -419,6 +421,7 @@
 
         private synchronized void recordCallers(String[] people, ArraySet<String> phoneNumbers,
                 long now) {
+            boolean recorded = false, hasTel = false, hasOther = false;
             for (int i = 0; i < people.length; i++) {
                 String person = people[i];
                 if (person == null) continue;
@@ -427,10 +430,16 @@
                     // while ideally we should not need to decode this, sometimes we have seen tel
                     // numbers given in an encoded format
                     String tel = Uri.decode(uri.getSchemeSpecificPart());
-                    if (tel != null) mTelCalls.put(tel, now);
+                    if (tel != null) {
+                        mTelCalls.put(tel, now);
+                        recorded = true;
+                        hasTel = true;
+                    }
                 } else {
                     // for non-tel calls, store the entire string, uri-component and all
                     mOtherCalls.put(person, now);
+                    recorded = true;
+                    hasOther = true;
                 }
             }
 
@@ -438,9 +447,16 @@
             // provided; these are in the format of just a phone number string
             if (phoneNumbers != null) {
                 for (String num : phoneNumbers) {
-                    if (num != null) mTelCalls.put(num, now);
+                    if (num != null) {
+                        mTelCalls.put(num, now);
+                        recorded = true;
+                        hasTel = true;
+                    }
                 }
             }
+            if (recorded) {
+                ZenLog.traceRecordCaller(hasTel, hasOther);
+            }
         }
 
         // helper function to check mTelCalls array for a number, and also check its decoded
@@ -468,6 +484,8 @@
         // previously recorded phone call.
         private synchronized boolean checkCallers(Context context, String[] people,
                 ArraySet<String> phoneNumbers) {
+            boolean found = false, checkedTel = false, checkedOther = false;
+
             // get the default country code for checking telephone numbers
             final String defaultCountryCode =
                     context.getSystemService(TelephonyManager.class).getNetworkCountryIso();
@@ -477,12 +495,14 @@
                 final Uri uri = Uri.parse(person);
                 if ("tel".equals(uri.getScheme())) {
                     String number = uri.getSchemeSpecificPart();
+                    checkedTel = true;
                     if (checkForNumber(number, defaultCountryCode)) {
-                        return true;
+                        found = true;
                     }
                 } else {
+                    checkedOther = true;
                     if (mOtherCalls.containsKey(person)) {
-                        return true;
+                        found = true;
                     }
                 }
             }
@@ -490,14 +510,16 @@
             // also check any passed-in phone numbers
             if (phoneNumbers != null) {
                 for (String num : phoneNumbers) {
+                    checkedTel = true;
                     if (checkForNumber(num, defaultCountryCode)) {
-                        return true;
+                        found = true;
                     }
                 }
             }
 
             // no matches
-            return false;
+            ZenLog.traceCheckRepeatCaller(found, checkedTel, checkedOther);
+            return found;
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 6135fe8..d426679 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -177,10 +177,12 @@
     }
 
     public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
-            ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
+            ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity,
+            int callingUid) {
         synchronized (mConfig) {
             return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConsolidatedPolicy,
-                    userHandle, extras, validator, contactsTimeoutMs, timeoutAffinity);
+                    userHandle, extras, validator, contactsTimeoutMs, timeoutAffinity,
+                    callingUid);
         }
     }
 
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index b05e44f..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;
@@ -306,8 +310,8 @@
         } else {
             final String resourceName = getNextArgRequired();
             final String typeStr = getNextArgRequired();
-            final String strData = getNextArgRequired();
-            addOverlayValue(overlayBuilder, resourceName, typeStr, strData);
+            final String strData = String.join(" ", peekRemainingArgs());
+            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/AppsFilterBase.java b/services/core/java/com/android/server/pm/AppsFilterBase.java
index 8501c5e..5b3eff9 100644
--- a/services/core/java/com/android/server/pm/AppsFilterBase.java
+++ b/services/core/java/com/android/server/pm/AppsFilterBase.java
@@ -28,6 +28,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Process;
+import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -340,7 +341,9 @@
                 return !isForceQueryable(targetPkgSetting.getAppId())
                       && !isImplicitlyQueryable(callingAppId, targetPkgSetting.getAppId());
             }
-            if (mCacheReady) { // use cache
+            // use cache
+            if (mCacheReady && SystemProperties.getBoolean("debug.pm.use_app_filter_cache",
+                    true)) {
                 if (!shouldFilterApplicationUsingCache(callingUid,
                         targetPkgSetting.getAppId(),
                         userId)) {
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 2aec187..4e9c472 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -28,7 +28,6 @@
 import android.annotation.AppIdInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.BroadcastOptions;
@@ -41,19 +40,20 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.PowerExemptionManager;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.text.TextUtils;
+import android.util.IntArray;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
 
 /**
  * Helper class to send broadcasts for various situations.
@@ -80,6 +80,7 @@
             final int flags, final String targetPkg, final IIntentReceiver finishedReceiver,
             final int[] userIds, int[] instantUserIds,
             @Nullable SparseArray<int[]> broadcastAllowList,
+            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
             @Nullable Bundle bOptions) {
         try {
             final IActivityManager am = ActivityManager.getService();
@@ -93,11 +94,13 @@
 
             if (ArrayUtils.isEmpty(instantUserIds)) {
                 doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
-                        resolvedUserIds, false /* isInstantApp */, broadcastAllowList, bOptions);
+                        resolvedUserIds, false /* isInstantApp */, broadcastAllowList,
+                        filterExtrasForReceiver, bOptions);
             } else {
                 // send restricted broadcasts for instant apps
                 doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
-                        instantUserIds, true /* isInstantApp */, null, bOptions);
+                        instantUserIds, true /* isInstantApp */, null,
+                        null /* filterExtrasForReceiver */, bOptions);
             }
         } catch (RemoteException ex) {
         }
@@ -113,6 +116,7 @@
     public void doSendBroadcast(String action, String pkg, Bundle extras,
             int flags, String targetPkg, IIntentReceiver finishedReceiver,
             int[] userIds, boolean isInstantApp, @Nullable SparseArray<int[]> broadcastAllowList,
+            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
             @Nullable Bundle bOptions) {
         for (int userId : userIds) {
             final Intent intent = new Intent(action,
@@ -148,45 +152,30 @@
                     intent, finishedReceiver, requiredPermissions,
                     finishedReceiver != null, userId,
                     broadcastAllowList == null ? null : broadcastAllowList.get(userId),
-                    bOptions);
+                    filterExtrasForReceiver, bOptions);
         }
     }
 
-    public void sendResourcesChangedBroadcast(@NonNull Computer snapshot, boolean mediaStatus,
-            boolean replacing, @NonNull String[] pkgNames, @NonNull int[] uids) {
+    public void sendResourcesChangedBroadcast(@NonNull Supplier<Computer> snapshotComputer,
+            boolean mediaStatus, boolean replacing, @NonNull String[] pkgNames,
+            @NonNull int[] uids) {
         if (ArrayUtils.isEmpty(pkgNames) || ArrayUtils.isEmpty(uids)) {
             return;
         }
-
-        try {
-            final IActivityManager am = ActivityManager.getService();
-            if (am == null) {
-                return;
-            }
-
-            final int[] resolvedUserIds = am.getRunningUserIds();
-            for (int userId : resolvedUserIds) {
-                final var lists = getBroadcastParams(snapshot, pkgNames, uids, userId);
-                for (int i = 0; i < lists.size(); i++) {
-                    // Send broadcasts here
-                    final Bundle extras = new Bundle(3);
-                    final BroadcastParams list = lists.get(i);
-                    extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
-                            list.getPackageNames());
-                    extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, list.getUids());
-                    extras.putBoolean(Intent.EXTRA_REPLACING, replacing);
-                    final SparseArray<int[]> allowList = list.getAllowList().size() == 0
-                            ? null : list.getAllowList();
-                    final String action =
-                            mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
-                                    : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
-                    sendPackageBroadcast(action, null /* pkg */, extras, 0 /* flags */,
-                            null /* targetPkg */, null /* finishedReceiver */, new int[]{userId},
-                            null /* instantUserIds */, allowList, null /* bOptions */);
-                }
-            }
-        } catch (RemoteException ex) {
+        Bundle extras = new Bundle();
+        extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgNames);
+        extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uids);
+        if (replacing) {
+            extras.putBoolean(Intent.EXTRA_REPLACING, replacing);
         }
+        String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
+                : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
+        sendPackageBroadcast(action, null /* pkg */, extras, 0 /* flags */,
+                null /* targetPkg */, null /* finishedReceiver */, null /* userIds */,
+                null /* instantUserIds */, null /* broadcastAllowList */,
+                (callingUid, intentExtras) -> filterExtrasChangedPackageList(
+                        snapshotComputer.get(), callingUid, intentExtras),
+                null /* bOptions */);
     }
 
     /**
@@ -266,7 +255,8 @@
         final int flags = !componentNames.contains(packageName)
                 ? Intent.FLAG_RECEIVER_REGISTERED_ONLY : 0;
         sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, flags, null, null,
-                userIds, instantUserIds, broadcastAllowList, null);
+                userIds, instantUserIds, broadcastAllowList, null /* filterExtrasForReceiver */,
+                null /* bOptions */);
     }
 
     public static void sendDeviceCustomizationReadyBroadcast() {
@@ -334,53 +324,72 @@
 
         sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
                 packageName, extras, 0, null, null, userIds, instantUserIds,
-                broadcastAllowlist, null);
+                broadcastAllowlist, null /* filterExtrasForReceiver */, null);
     }
 
     public void sendFirstLaunchBroadcast(String pkgName, String installerPkg,
             int[] userIds, int[] instantUserIds) {
         sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, pkgName, null, 0,
-                installerPkg, null, userIds, instantUserIds, null /* broadcastAllowList */, null);
+                installerPkg, null, userIds, instantUserIds, null /* broadcastAllowList */,
+                null /* filterExtrasForReceiver */, null);
     }
 
     /**
-     * Get broadcast params list based on the given package and uid list. The broadcast params are
-     * used to send broadcast separately if the given packages have different visibility allow list.
+     * Filter package names for the intent extras {@link Intent#EXTRA_CHANGED_PACKAGE_LIST} and
+     * {@link Intent#EXTRA_CHANGED_UID_LIST} by using the rules of the package visibility.
      *
-     * @param pkgList The names of packages which have changes.
-     * @param uidList The uids of packages which have changes.
-     * @param userId The user where packages reside.
-     * @return The list of {@link BroadcastParams} object.
+     * @param callingUid The uid that is going to access the intent extras.
+     * @param extras The intent extras to filter
+     * @return An extras that have been filtered, or {@code null} if the given uid is unable to
+     * access all the packages in the extras.
      */
-    public List<BroadcastParams> getBroadcastParams(@NonNull Computer snapshot,
-            @NonNull String[] pkgList, @NonNull int[] uidList, @UserIdInt int userId) {
-        final List<BroadcastParams> lists = new ArrayList<>(pkgList.length);
-        // Get allow lists for the pkg in the pkgList. Merge into the existed pkgs and uids if
-        // allow lists are the same.
-        for (int i = 0; i < pkgList.length; i++) {
-            final String pkgName = pkgList[i];
-            final int uid = uidList[i];
-            if (TextUtils.isEmpty(pkgName) || Process.INVALID_UID == uid) {
+    @Nullable
+    public static Bundle filterExtrasChangedPackageList(@NonNull Computer snapshot, int callingUid,
+            @NonNull Bundle extras) {
+        if (UserHandle.isCore(callingUid)) {
+            // see all
+            return extras;
+        }
+        final String[] pkgs = extras.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+        if (ArrayUtils.isEmpty(pkgs)) {
+            return extras;
+        }
+        final int userId = extras.getInt(
+                Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(callingUid));
+        final int[] uids = extras.getIntArray(Intent.EXTRA_CHANGED_UID_LIST);
+        final Pair<String[], int[]> filteredPkgs =
+                filterPackages(snapshot, pkgs, uids, callingUid, userId);
+        if (ArrayUtils.isEmpty(filteredPkgs.first)) {
+            // caller is unable to access this intent
+            return null;
+        }
+        final Bundle filteredExtras = new Bundle(extras);
+        filteredExtras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, filteredPkgs.first);
+        filteredExtras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, filteredPkgs.second);
+        return filteredExtras;
+    }
+
+    @NonNull
+    private static Pair<String[], int[]> filterPackages(@NonNull Computer snapshot,
+            @NonNull String[] pkgs, @Nullable int[] uids, int callingUid, int userId) {
+        final int pkgSize = pkgs.length;
+        final int uidSize = !ArrayUtils.isEmpty(uids) ? uids.length : 0;
+
+        final ArrayList<String> pkgList = new ArrayList<>(pkgSize);
+        final IntArray uidList = uidSize > 0 ? new IntArray(uidSize) : null;
+        for (int i = 0; i < pkgSize; i++) {
+            final String packageName = pkgs[i];
+            if (snapshot.shouldFilterApplication(
+                    snapshot.getPackageStateInternal(packageName), callingUid, userId)) {
                 continue;
             }
-            int[] allowList = snapshot.getVisibilityAllowList(pkgName, userId);
-            if (allowList == null) {
-                allowList = new int[0];
-            }
-            boolean merged = false;
-            for (int j = 0; j < lists.size(); j++) {
-                final BroadcastParams list = lists.get(j);
-                if (Arrays.equals(list.getAllowList().get(userId), allowList)) {
-                    list.addPackage(pkgName, uid);
-                    merged = true;
-                    break;
-                }
-            }
-            if (!merged) {
-                lists.add(new BroadcastParams(pkgName, uid, allowList, userId));
+            pkgList.add(packageName);
+            if (uidList != null && i < uidSize) {
+                uidList.add(uids[i]);
             }
         }
-
-        return lists;
+        return new Pair<>(
+                pkgList.size() > 0 ? pkgList.toArray(new String[pkgList.size()]) : null,
+                uidList != null && uidList.size() > 0 ? uidList.toArray() : null);
     }
 }
diff --git a/services/core/java/com/android/server/pm/BroadcastParams.java b/services/core/java/com/android/server/pm/BroadcastParams.java
deleted file mode 100644
index 279aab0..0000000
--- a/services/core/java/com/android/server/pm/BroadcastParams.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.UserIdInt;
-import android.util.IntArray;
-import android.util.SparseArray;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * A helper class that contains information about package names and uids that share the same allow
- * list for sending broadcasts. Used by various package helpers.
- */
-final class BroadcastParams {
-    private final @NonNull List<String> mPackageNames;
-    private final @NonNull IntArray mUids;
-    private final @NonNull SparseArray<int[]> mAllowList;
-
-    BroadcastParams(@NonNull String packageName, @IntRange(from = 0) int uid,
-            @NonNull int[] allowList, @UserIdInt int userId) {
-        mPackageNames = new ArrayList<>(Arrays.asList(packageName));
-        mUids = IntArray.wrap(new int[]{uid});
-        mAllowList = new SparseArray<>(1);
-        mAllowList.put(userId, allowList);
-    }
-
-    public void addPackage(@NonNull String packageName, @IntRange(from = 0) int uid) {
-        mPackageNames.add(packageName);
-        mUids.add(uid);
-    }
-
-    public @NonNull String[] getPackageNames() {
-        return mPackageNames.toArray(new String[0]);
-    }
-
-    public @NonNull int[] getUids() {
-        return mUids.toArray();
-    }
-
-    public @NonNull SparseArray<int[]> getAllowList() {
-        return mAllowList;
-    }
-}
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/DistractingPackageHelper.java b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
index 53e9754..dbf0d29 100644
--- a/services/core/java/com/android/server/pm/DistractingPackageHelper.java
+++ b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
@@ -27,7 +27,6 @@
 import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Slog;
-import android.util.SparseArray;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.pkg.PackageStateInternal;
@@ -127,7 +126,7 @@
         if (!changedPackagesList.isEmpty()) {
             final String[] changedPackages = changedPackagesList.toArray(
                     new String[changedPackagesList.size()]);
-            sendDistractingPackagesChanged(snapshot, changedPackages, changedUids.toArray(), userId,
+            sendDistractingPackagesChanged(changedPackages, changedUids.toArray(), userId,
                     restrictionFlags);
             mPm.scheduleWritePackageRestrictions(userId);
         }
@@ -169,7 +168,7 @@
         if (!changedPackages.isEmpty()) {
             final String[] packageArray = changedPackages.toArray(
                     new String[changedPackages.size()]);
-            sendDistractingPackagesChanged(snapshot, packageArray, changedUids.toArray(), userId,
+            sendDistractingPackagesChanged(packageArray, changedUids.toArray(), userId,
                     RESTRICTION_NONE);
             mPm.scheduleWritePackageRestrictions(userId);
         }
@@ -182,24 +181,21 @@
      * @param uidList The uids of packages which have suspension changes.
      * @param userId The user where packages reside.
      */
-    void sendDistractingPackagesChanged(@NonNull Computer snapshot, @NonNull String[] pkgList,
-            int[] uidList, int userId, int distractionFlags) {
-        final List<BroadcastParams> lists = mBroadcastHelper.getBroadcastParams(
-                snapshot, pkgList, uidList, userId);
+    void sendDistractingPackagesChanged(@NonNull String[] pkgList, int[] uidList, int userId,
+            int distractionFlags) {
+        final Bundle extras = new Bundle();
+        extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
+        extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
+        extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, distractionFlags);
+
         final Handler handler = mInjector.getHandler();
-        for (int i = 0; i < lists.size(); i++) {
-            final Bundle extras = new Bundle(3);
-            final BroadcastParams list = lists.get(i);
-            extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, list.getPackageNames());
-            extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, list.getUids());
-            extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, distractionFlags);
-            final SparseArray<int[]> allowList = list.getAllowList().size() == 0
-                    ? null : list.getAllowList();
-            handler.post(() -> mBroadcastHelper.sendPackageBroadcast(
-                    Intent.ACTION_DISTRACTING_PACKAGES_CHANGED, null /* pkg */,
-                    extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
-                    null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
-                    allowList, null /* bOptions */));
-        }
+        handler.post(() -> mBroadcastHelper.sendPackageBroadcast(
+                Intent.ACTION_DISTRACTING_PACKAGES_CHANGED, null /* pkg */,
+                extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
+                null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
+                null /* broadcastAllowList */,
+                (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
+                        mPm.snapshotComputer(), callingUid, intentExtras),
+                null /* bOptions */));
     }
 }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 728d2d1..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);
@@ -2626,7 +2626,7 @@
                     }
                     final String[] pkgNames = new String[]{res.mRemovedInfo.mRemovedPackage};
                     final int[] uids = new int[]{res.mRemovedInfo.mUid};
-                    mBroadcastHelper.sendResourcesChangedBroadcast(mPm.snapshotComputer(),
+                    mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer,
                             false /* mediaStatus */, true /* replacing */, pkgNames, uids);
                 }
                 res.mRemovedInfo.sendPackageRemovedBroadcasts(killApp, false /*removedBySystem*/);
@@ -2804,7 +2804,7 @@
                     }
                     final String[] pkgNames = new String[]{packageName};
                     final int[] uids = new int[]{res.mPkg.getUid()};
-                    mBroadcastHelper.sendResourcesChangedBroadcast(mPm.snapshotComputer(),
+                    mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer,
                             true /* mediaStatus */, true /* replacing */, pkgNames, uids);
                 }
             } else if (!ArrayUtils.isEmpty(res.mLibraryConsumers)) { // if static shared lib
@@ -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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 72f3850..5ad39f2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2947,7 +2947,7 @@
             @Nullable Bundle bOptions) {
         mHandler.post(() -> mBroadcastHelper.sendPackageBroadcast(action, pkg, extras, flags,
                 targetPkg, finishedReceiver, userIds, instantUserIds, broadcastAllowList,
-                bOptions));
+                null /* filterExtrasForReceiver */, bOptions));
     }
 
     @Override
@@ -6309,21 +6309,6 @@
             }
         }
 
-        /**
-         * Ask the package manager to compile layouts in the given package.
-         */
-        @Override
-        public boolean compileLayouts(String packageName) {
-            AndroidPackage pkg;
-            synchronized (mLock) {
-                pkg = mPackages.get(packageName);
-                if (pkg == null) {
-                    return false;
-                }
-            }
-            return mArtManagerService.compileLayouts(pkg);
-        }
-
         @Nullable
         @Override
         public String removeLegacyDefaultBrowserPackageName(int userId) {
@@ -7077,7 +7062,8 @@
             mResolveActivity.processName = pkg.getProcessName();
             mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
             mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
-                    | ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
+                    | ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS
+                    | ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
             mResolveActivity.theme = 0;
             mResolveActivity.exported = true;
             mResolveActivity.enabled = true;
@@ -7110,7 +7096,8 @@
                 mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
                 mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER;
                 mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
-                        | ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
+                        | ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY
+                        | ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
                 mResolveActivity.theme = R.style.Theme_Material_Dialog_Alert;
                 mResolveActivity.exported = true;
                 mResolveActivity.enabled = true;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index dba7dcd..da89b9e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1792,7 +1792,6 @@
         String checkProfilesRaw = null;
         boolean secondaryDex = false;
         String split = null;
-        boolean compileLayouts = false;
 
         String opt;
         while ((opt = getNextOption()) != null) {
@@ -1812,9 +1811,6 @@
                 case "-r":
                     compilationReason = getNextArgRequired();
                     break;
-                case "--compile-layouts":
-                    compileLayouts = true;
-                    break;
                 case "--check-prof":
                     checkProfilesRaw = getNextArgRequired();
                     break;
@@ -1848,14 +1844,15 @@
 
         final boolean compilerFilterGiven = compilerFilter != null;
         final boolean compilationReasonGiven = compilationReason != null;
-        // Make sure exactly one of -m, -r, or --compile-layouts is given.
-        if ((!compilerFilterGiven && !compilationReasonGiven && !compileLayouts)
-            || (!compilerFilterGiven && compilationReasonGiven && compileLayouts)
-            || (compilerFilterGiven && !compilationReasonGiven && compileLayouts)
-            || (compilerFilterGiven && compilationReasonGiven && !compileLayouts)
-            || (compilerFilterGiven && compilationReasonGiven && compileLayouts)) {
-            pw.println("Must specify exactly one of compilation filter (\"-m\"), compilation " +
-                    "reason (\"-r\"), or compile layouts (\"--compile-layouts\")");
+        // Make sure exactly one of -m, or -r is given.
+        if (compilerFilterGiven && compilationReasonGiven) {
+            pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") "
+                    + "at the same time");
+            return 1;
+        }
+        if (!compilerFilterGiven && !compilationReasonGiven) {
+            pw.println("Cannot run without any of compilation filter (\"-m\") and compilation "
+                    + "reason (\"-r\")");
             return 1;
         }
 
@@ -1920,19 +1917,12 @@
                 pw.flush();
             }
 
-            boolean result = true;
-            if (compileLayouts) {
-                PackageManagerInternal internal = LocalServices.getService(
-                        PackageManagerInternal.class);
-                result = internal.compileLayouts(packageName);
-            } else {
-                result = secondaryDex
+            final boolean result = secondaryDex
                     ? mInterface.performDexOptSecondary(packageName,
                             targetCompilerFilter, forceCompilation)
                     : mInterface.performDexOptMode(packageName,
                             checkProfiles, targetCompilerFilter, forceCompilation,
                             true /* bootComplete */, split);
-            }
             if (!result) {
                 failedPackages.add(packageName);
             }
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/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index f621d8b..41c6c0f 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -298,7 +298,7 @@
             packageNames[i] = pkg.getPackageName();
             packageUids[i] = pkg.getUid();
         }
-        mBroadcastHelper.sendResourcesChangedBroadcast(mPm.snapshotComputer(), mediaStatus,
+        mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer, mediaStatus,
                 replacing, packageNames, packageUids);
     }
 
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 01aecd9..6847b70 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -38,7 +38,6 @@
 import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Slog;
-import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
@@ -184,11 +183,9 @@
             }
         });
 
-        final Computer newSnapshot = mPm.snapshotComputer();
-
         if (!changedPackagesList.isEmpty()) {
             final String[] changedPackages = changedPackagesList.toArray(new String[0]);
-            sendPackagesSuspendedForUser(newSnapshot,
+            sendPackagesSuspendedForUser(
                     suspended ? Intent.ACTION_PACKAGES_SUSPENDED
                             : Intent.ACTION_PACKAGES_UNSUSPENDED,
                     changedPackages, changedUids.toArray(), userId);
@@ -197,7 +194,7 @@
         }
         // Send the suspension changed broadcast to ensure suspension state is not stale.
         if (!modifiedPackages.isEmpty()) {
-            sendPackagesSuspendedForUser(newSnapshot, Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
+            sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
                     modifiedPackages.toArray(new String[0]), modifiedUids.toArray(), userId);
         }
         return unmodifiablePackages.toArray(new String[0]);
@@ -326,14 +323,12 @@
             }
         });
 
-        final Computer newSnapshot = mPm.snapshotComputer();
-
         mPm.scheduleWritePackageRestrictions(userId);
         if (!unsuspendedPackages.isEmpty()) {
             final String[] packageArray = unsuspendedPackages.toArray(
                     new String[unsuspendedPackages.size()]);
             sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId);
-            sendPackagesSuspendedForUser(newSnapshot, Intent.ACTION_PACKAGES_UNSUSPENDED,
+            sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_UNSUSPENDED,
                     packageArray, unsuspendedUids.toArray(), userId);
         }
     }
@@ -585,23 +580,19 @@
      * @param userId The user where packages reside.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    void sendPackagesSuspendedForUser(@NonNull Computer snapshot, @NonNull String intent,
-            @NonNull String[] pkgList, @NonNull int[] uidList, int userId) {
-        final List<BroadcastParams> lists = mBroadcastHelper.getBroadcastParams(
-                snapshot, pkgList, uidList, userId);
+    void sendPackagesSuspendedForUser(@NonNull String intent, @NonNull String[] pkgList,
+            @NonNull int[] uidList, int userId) {
         final Handler handler = mInjector.getHandler();
-        for (int i = 0; i < lists.size(); i++) {
-            final Bundle extras = new Bundle(3);
-            final BroadcastParams list = lists.get(i);
-            extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, list.getPackageNames());
-            extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, list.getUids());
-            final SparseArray<int[]> allowList = list.getAllowList().size() == 0
-                    ? null : list.getAllowList();
-            handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */,
-                    extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
-                    null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
-                    allowList, null /* bOptions */));
-        }
+        final Bundle extras = new Bundle(3);
+        extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
+        extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
+        handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */,
+                extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
+                null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
+                null /* broadcastAllowList */,
+                (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
+                        mPm.snapshotComputer(), callingUid, intentExtras),
+                null /* bOptions */));
     }
 
     private String getKnownPackageName(@NonNull Computer snapshot,
@@ -652,7 +643,7 @@
                 }
                 mBroadcastHelper.doSendBroadcast(action, null, intentExtras,
                         Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null,
-                        targetUserIds, false, null, null);
+                        targetUserIds, false, null, null, null);
             }
         });
     }
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 8dc9428..297439f 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -21,6 +21,7 @@
 import android.annotation.UserIdInt;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
 import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.os.UserManager;
@@ -305,4 +306,19 @@
      * for users that already existed on-disk from an older version of Android.
      */
     public abstract boolean shouldIgnorePrepareStorageErrors(int userId);
+
+    /**
+     * Returns the {@link UserProperties} of the given user, or {@code null} if it is not found.
+     * NB: The actual object is returned. So do NOT modify it!
+     */
+    public abstract @Nullable UserProperties getUserProperties(@UserIdInt int userId);
+
+    /** 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 a0335e8..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;
@@ -52,6 +53,7 @@
 import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.UserInfo;
 import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.pm.UserProperties;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -107,6 +109,7 @@
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
+import android.view.Display;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -180,7 +183,7 @@
     private static final String TAG_NAME = "name";
     private static final String TAG_ACCOUNT = "account";
     private static final String ATTR_FLAGS = "flags";
-    private static final String ATTR_TYPE = "type";
+    private static final String ATTR_TYPE = "type"; // userType
     private static final String ATTR_ICON_PATH = "icon";
     private static final String ATTR_ID = "id";
     private static final String ATTR_CREATION_TIME = "created";
@@ -215,6 +218,7 @@
     private static final String TAG_ENTRY = "entry";
     private static final String TAG_VALUE = "value";
     private static final String TAG_SEED_ACCOUNT_OPTIONS = "seedAccountOptions";
+    private static final String TAG_USER_PROPERTIES = "userProperties";
     private static final String TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL =
             "lastRequestQuietModeEnabledCall";
     private static final String TAG_IGNORE_PREPARE_STORAGE_ERRORS =
@@ -259,7 +263,7 @@
     @VisibleForTesting
     static final int MAX_RECENTLY_REMOVED_IDS_SIZE = 100;
 
-    private static final int USER_VERSION = 9;
+    private static final int USER_VERSION = 10;
 
     private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
 
@@ -325,6 +329,9 @@
         // Whether to perist the seed account information to be available after a boot
         boolean persistSeedData;
 
+        /** Properties of the user whose default values originate from its user type. */
+        UserProperties userProperties;
+
         /** Elapsed realtime since boot when the user started. */
         long startRealtime;
 
@@ -620,6 +627,16 @@
     @GuardedBy("mUserStates")
     private final WatchedUserStates mUserStates = new WatchedUserStates();
 
+    /**
+     * Used on devices that support background users (key) running on secondary displays (value).
+     *
+     * <p>Is {@code null} by default and instantiated on demand when the users are started on
+     * secondary displays.
+     */
+    @Nullable
+    @GuardedBy("mUsersLock")
+    private SparseIntArray mUsersOnSecondaryDisplays;
+
     private static UserManagerService sInstance;
 
     public static UserManagerService getInstance() {
@@ -894,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;
@@ -1487,6 +1504,36 @@
         return userTypeDetails != null && userTypeDetails.isSystem();
     }
 
+    /**
+     * Returns a *copy* of the given user's UserProperties, stripping out any information for which
+     * the caller lacks permission.
+     */
+    @Override
+    public @NonNull UserProperties getUserPropertiesCopy(@UserIdInt int userId) {
+        checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getUserProperties");
+        final UserProperties origProperties = getUserPropertiesInternal(userId);
+        if (origProperties != null) {
+            int callingUid = Binder.getCallingUid();
+            boolean exposeAllFields = callingUid == Process.SYSTEM_UID;
+            boolean hasManage = hasPermissionGranted(Manifest.permission.MANAGE_USERS, callingUid);
+            boolean hasQuery = hasPermissionGranted(Manifest.permission.QUERY_USERS, callingUid);
+            return new UserProperties(origProperties, exposeAllFields, hasManage, hasQuery);
+        }
+        // A non-existent or partial user will reach here.
+        throw new IllegalArgumentException("Cannot access properties for user " + userId);
+    }
+
+    /** Returns the user's actual, canonical UserProperties object. Do not edit it externally. */
+    private @Nullable UserProperties getUserPropertiesInternal(@UserIdInt int userId) {
+        synchronized (mUsersLock) {
+            final UserData userData = getUserDataLU(userId);
+            if (userData != null) {
+                return userData.userProperties;
+            }
+        }
+        return null;
+    }
+
     @Override
     public boolean hasBadge(@UserIdInt int userId) {
         checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "hasBadge");
@@ -1576,6 +1623,10 @@
 
     public boolean isProfile(@UserIdInt int userId) {
         checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isProfile");
+        return isProfileUnchecked(userId);
+    }
+
+    private boolean isProfileUnchecked(@UserIdInt int userId) {
         synchronized (mUsersLock) {
             UserInfo userInfo = getUserInfoLU(userId);
             return userInfo != null && userInfo.isProfile();
@@ -1663,15 +1714,33 @@
                     + ") is visible");
         }
 
-        // First check current foreground user (on main display)
+        return isUserVisibleUnchecked(userId);
+    }
+
+    private boolean isUserVisibleUnchecked(@UserIdInt int userId) {
+        // First check current foreground user and their profiles (on main display)
+        if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
+            return true;
+        }
+
+        // TODO(b/239824814): STOPSHIP - add CTS tests (requires change on testing infra)
+        synchronized (mUsersLock) {
+            if (mUsersOnSecondaryDisplays != null) {
+                // TODO(b/239824814): make sure it handles profile as well
+                return (mUsersOnSecondaryDisplays.indexOfKey(userId) >= 0);
+            }
+        }
+
+        return false;
+    }
+
+    private boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
         int currentUserId = Binder.withCleanCallingIdentity(() -> ActivityManager.getCurrentUser());
 
         if (currentUserId == userId) {
             return true;
         }
 
-        // Then profiles of current user
-        // TODO(b/239824814): consider using AMInternal.isCurrentProfile() instead
         if (isProfile(userId)) {
             int parentId = Binder.withCleanCallingIdentity(() -> getProfileParentId(userId));
             if (parentId == currentUserId) {
@@ -1679,10 +1748,50 @@
             }
         }
 
-        // TODO(b/239824814): support background users on secondary display (and their profiles)
         return false;
     }
 
+    // TODO(b/239982558): currently used just by shell command, might need to move to
+    // UserManagerInternal if needed by other components (like WindowManagerService)
+    // TODO(b/239982558): add unit test
+    // TODO(b/239982558): try to merge with isUserVisibleUnchecked()
+    private boolean isUserVisibleOnDisplay(@UserIdInt int userId, int displayId) {
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            return isCurrentUserOrRunningProfileOfCurrentUser(userId);
+        }
+        synchronized (mUsersLock) {
+            // TODO(b/239824814): make sure it handles profile as well
+            return mUsersOnSecondaryDisplays != null && mUsersOnSecondaryDisplays.get(userId,
+                    Display.INVALID_DISPLAY) == displayId;
+        }
+    }
+
+    @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();
@@ -1803,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);
@@ -2195,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.
@@ -3268,6 +3389,7 @@
     @VisibleForTesting
     void upgradeIfNecessaryLP(Bundle oldGlobalUserRestrictions, int userVersion,
             int userTypeVersion) {
+        Slog.i(LOG_TAG, "Upgrading users from userVersion " + userVersion + " to " + USER_VERSION);
         Set<Integer> userIdsToWrite = new ArraySet<>();
         final int originalVersion = mUserVersion;
         final int originalUserTypeVersion = mUserTypeVersion;
@@ -3403,6 +3525,27 @@
             userVersion = 9;
         }
 
+        if (userVersion < 10) {
+            // Add UserProperties.
+            synchronized (mUsersLock) {
+                for (int i = 0; i < mUsers.size(); i++) {
+                    final UserData userData = mUsers.valueAt(i);
+                    final UserTypeDetails userTypeDetails = mUserTypes.get(userData.info.userType);
+                    if (userTypeDetails == null) {
+                        throw new IllegalStateException(
+                                "Cannot upgrade user because " + userData.info.userType
+                                        + " isn't defined on this device!");
+                    }
+                    userData.userProperties = new UserProperties(
+                            userTypeDetails.getDefaultUserPropertiesReference());
+                    userIdsToWrite.add(userData.info.id);
+                }
+            }
+            userVersion = 10;
+        }
+
+        // Reminder: If you add another upgrade, make sure to increment USER_VERSION too.
+
         // Done with userVersion changes, moving on to deal with userTypeVersion upgrades
         // Upgrade from previous user type to a new user type
         final int newUserTypeVersion = UserTypeFactory.getUserTypeVersion();
@@ -3417,6 +3560,11 @@
             Slog.w(LOG_TAG, "User version " + mUserVersion + " didn't upgrade as expected to "
                     + USER_VERSION);
         } else {
+            if (userVersion > USER_VERSION) {
+                Slog.wtf(LOG_TAG, "Upgraded user version " + mUserVersion + " is higher the SDK's "
+                        + "one of " + USER_VERSION + ". Someone forgot to update USER_VERSION?");
+            }
+
             mUserVersion = userVersion;
             mUserTypeVersion = newUserTypeVersion;
 
@@ -3536,6 +3684,8 @@
         flags |= mUserTypes.get(systemUserType).getDefaultUserInfoFlags();
         UserInfo system = new UserInfo(UserHandle.USER_SYSTEM, null, null, flags, systemUserType);
         UserData userData = putUserInfo(system);
+        userData.userProperties = new UserProperties(
+                mUserTypes.get(userData.info.userType).getDefaultUserPropertiesReference());
         mNextSerialNumber = MIN_USER_ID;
         mUserVersion = USER_VERSION;
         mUserTypeVersion = UserTypeFactory.getUserTypeVersion();
@@ -3710,6 +3860,12 @@
             serializer.endTag(null, TAG_SEED_ACCOUNT_OPTIONS);
         }
 
+        if (userData.userProperties != null) {
+            serializer.startTag(null, TAG_USER_PROPERTIES);
+            userData.userProperties.writeToXml(serializer);
+            serializer.endTag(null, TAG_USER_PROPERTIES);
+        }
+
         if (userData.getLastRequestQuietModeEnabledMillis() != 0L) {
             serializer.startTag(/* namespace */ null, TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL);
             serializer.text(String.valueOf(userData.getLastRequestQuietModeEnabledMillis()));
@@ -3829,6 +3985,7 @@
         String seedAccountName = null;
         String seedAccountType = null;
         PersistableBundle seedAccountOptions = null;
+        UserProperties userProperties = null;
         Bundle baseRestrictions = null;
         Bundle legacyLocalRestrictions = null;
         RestrictionsSet localRestrictions = null;
@@ -3907,6 +4064,17 @@
                 } else if (TAG_SEED_ACCOUNT_OPTIONS.equals(tag)) {
                     seedAccountOptions = PersistableBundle.restoreFromXml(parser);
                     persistSeedData = true;
+                } else if (TAG_USER_PROPERTIES.equals(tag)) {
+                    // We already got the userType above (if it exists), so we can use it.
+                    // And it must exist, since ATTR_TYPE historically predates PROPERTIES.
+                    final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
+                    if (userTypeDetails == null) {
+                        Slog.e(LOG_TAG, "User has properties but no user type!");
+                        return null;
+                    }
+                    final UserProperties defaultProps
+                            = userTypeDetails.getDefaultUserPropertiesReference();
+                    userProperties = new UserProperties(parser, defaultProps);
                 } else if (TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL.equals(tag)) {
                     type = parser.next();
                     if (type == XmlPullParser.TEXT) {
@@ -3943,6 +4111,7 @@
         userData.seedAccountType = seedAccountType;
         userData.persistSeedData = persistSeedData;
         userData.seedAccountOptions = seedAccountOptions;
+        userData.userProperties = userProperties;
         userData.setLastRequestQuietModeEnabledMillis(lastRequestQuietModeEnabledTimestamp);
         if (ignorePrepareStorageErrors) {
             userData.setIgnorePrepareStorageErrors();
@@ -4209,9 +4378,9 @@
                             UserManager.USER_OPERATION_ERROR_MAX_USERS);
                 }
                 // Keep logic in sync with getRemainingCreatableUserCount()
-                if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) {
+                if (!isGuest && !isProfile && !isDemo && isUserLimitReached()) {
                     // If the user limit has been reached, we cannot add a user (except guest/demo).
-                    // Note that managed profiles can bypass it in certain circumstances (taken
+                    // Note that profiles can bypass it in certain circumstances (taken
                     // into account in the profile check below).
                     throwCheckedUserOperationException(
                             "Cannot add user. Maximum user limit is reached.",
@@ -4278,6 +4447,8 @@
                     }
                     userData = new UserData();
                     userData.info = userInfo;
+                    userData.userProperties = new UserProperties(
+                            userTypeDetails.getDefaultUserPropertiesReference());
                     mUsers.put(userId, userData);
                 }
                 writeUserLP(userData);
@@ -4672,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;
     }
@@ -5604,6 +5793,7 @@
         }
         // If we got here, we probably recycled user ids, so invalidate any caches.
         UserManager.invalidateStaticUserProperties();
+        UserManager.invalidateUserPropertiesCache();
         if (nextId < 0) {
             throw new IllegalStateException("No user id available!");
         }
@@ -5776,6 +5966,11 @@
             pw.println("          --reboot (which does a full reboot) or");
             pw.println("          --no-restart (which requires a manual restart)");
             pw.println();
+            pw.println("  is-user-visible [--display DISPLAY_ID] <USER_ID>");
+            pw.println("    Checks if the given user is visible in the given display.");
+            pw.println("    If the display option is not set, it uses the user's context to check");
+            pw.println("    (so it emulates what apps would get from UserManager.isUserVisible())");
+            pw.println();
         }
 
         @Override
@@ -5792,6 +5987,8 @@
                         return runReportPackageAllowlistProblems();
                     case "set-system-user-mode-emulation":
                         return runSetSystemUserModeEmulation();
+                    case "is-user-visible":
+                        return runIsUserVisible();
                     default:
                         return handleDefaultCommands(cmd);
                 }
@@ -5841,9 +6038,6 @@
                 for (int i = 0; i < size; i++) {
                     final UserInfo user = users.get(i);
                     final boolean running = am.isUserRunning(user.id, 0);
-                    final boolean current = user.id == currentUser;
-                    final boolean hasParent = user.profileGroupId != user.id
-                            && user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID;
                     if (verbose) {
                         final DevicePolicyManagerInternal dpm = getDevicePolicyManagerInternal();
                         String deviceOwner = "";
@@ -5862,7 +6056,11 @@
                                 Binder.restoreCallingIdentity(ident);
                             }
                         }
-                        pw.printf("%d: id=%d, name=%s, type=%s, flags=%s%s%s%s%s%s%s%s%s\n",
+                        final boolean current = user.id == currentUser;
+                        final boolean hasParent = user.profileGroupId != user.id
+                                && user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID;
+                        final boolean visible = isUserVisibleUnchecked(user.id);
+                        pw.printf("%d: id=%d, name=%s, type=%s, flags=%s%s%s%s%s%s%s%s%s%s\n",
                                 i,
                                 user.id,
                                 user.name,
@@ -5874,7 +6072,9 @@
                                 user.preCreated ? " (pre-created)" : "",
                                 user.convertedFromPreCreated ? " (converted)" : "",
                                 deviceOwner, profileOwner,
-                                current ? " (current)" : "");
+                                current ? " (current)" : "",
+                                visible ? " (visible)" : ""
+                        );
                     } else {
                         // NOTE: the standard "list users" command is used by integration tests and
                         // hence should not be changed. If you need to add more info, use the
@@ -6031,6 +6231,56 @@
             return 0;
         }
 
+        private int runIsUserVisible() {
+            PrintWriter pw = getOutPrintWriter();
+            int displayId = Display.INVALID_DISPLAY;
+            String opt;
+            while ((opt = getNextOption()) != null) {
+                boolean invalidOption = false;
+                switch (opt) {
+                    case "--display":
+                        displayId = Integer.parseInt(getNextArgRequired());
+                        invalidOption = displayId == Display.INVALID_DISPLAY;
+                        break;
+                    default:
+                        invalidOption = true;
+                }
+                if (invalidOption) {
+                    pw.println("Invalid option: " + opt);
+                    return -1;
+                }
+            }
+            int userId = UserHandle.parseUserArg(getNextArgRequired());
+            switch (userId) {
+                case UserHandle.USER_ALL:
+                case UserHandle.USER_CURRENT_OR_SELF:
+                case UserHandle.USER_NULL:
+                    pw.printf("invalid value (%d) for --user option\n", userId);
+                    return -1;
+                case UserHandle.USER_CURRENT:
+                    userId = ActivityManager.getCurrentUser();
+                    break;
+            }
+
+            boolean isVisible;
+            if (displayId != Display.INVALID_DISPLAY) {
+                isVisible = isUserVisibleOnDisplay(userId, displayId);
+            } else {
+                isVisible = getUserManagerForUser(userId).isUserVisible();
+            }
+            pw.println(isVisible);
+            return 0;
+        }
+
+        /**
+         * Gets the {@link UserManager} associated with the context of the given user.
+         */
+        private UserManager getUserManagerForUser(int userId) {
+            UserHandle user = UserHandle.of(userId);
+            Context context = mContext.createContextAsUser(user, /* flags= */ 0);
+            return context.getSystemService(UserManager.class);
+        }
+
         /**
          * Confirms if the build is debuggable
          *
@@ -6130,12 +6380,17 @@
                 pw.print("  Cached user IDs (including pre-created): ");
                 pw.println(Arrays.toString(mUserIdsIncludingPreCreated));
             }
-
         } // synchronized (mPackagesLock)
 
         // Multiple Users on Multiple Display info
         pw.println("  Supports users on secondary displays: "
                 + UserManager.isUsersOnSecondaryDisplaysEnabled());
+        synchronized (mUsersLock) {
+            if (mUsersOnSecondaryDisplays != null) {
+                pw.print("  Users on secondary displays: ");
+                pw.println(mUsersOnSecondaryDisplays);
+            }
+        }
 
         // Dump some capabilities
         pw.println();
@@ -6300,6 +6555,10 @@
             }
         }
 
+        if (userData.userProperties != null) {
+            userData.userProperties.println(pw, "    ");
+        }
+
         pw.println("    Ignore errors preparing storage: "
                 + userData.getIgnorePrepareStorageErrors());
     }
@@ -6686,7 +6945,98 @@
                 return userData != null && userData.getIgnorePrepareStorageErrors();
             }
         }
-    }
+
+        @Override
+        public @Nullable UserProperties getUserProperties(@UserIdInt int userId) {
+            final UserProperties props = getUserPropertiesInternal(userId);
+            if (props == null) {
+                Slog.w(LOG_TAG, "A null UserProperties was returned for user " + userId);
+            }
+            return props;
+        }
+
+        @Override
+        public void assignUserToDisplay(int userId, int displayId) {
+            if (DBG) {
+                Slogf.d(LOG_TAG, "assignUserToDisplay(%d, %d): mUsersOnSecondaryDisplays=%s",
+                        userId, displayId, mUsersOnSecondaryDisplays);
+            }
+            // TODO(b/240613396) throw exception if feature not supported
+
+            if (displayId == Display.INVALID_DISPLAY) {
+                synchronized (mUsersLock) {
+                    if (mUsersOnSecondaryDisplays == null) {
+                        if (false) {
+                            // TODO(b/240613396): remove this if once we check for support above
+                            Slogf.wtf(LOG_TAG, "assignUserToDisplay(%d, %d): no "
+                                    + "mUsersOnSecondaryDisplays", userId, displayId);
+                        }
+                        return;
+                    }
+                    if (DBG) {
+                        Slogf.d(LOG_TAG, "Removing %d from mUsersOnSecondaryDisplays", userId);
+                    }
+                    mUsersOnSecondaryDisplays.delete(userId);
+                    if (mUsersOnSecondaryDisplays.size() == 0) {
+                        if (DBG) {
+                            Slogf.d(LOG_TAG, "Removing mUsersOnSecondaryDisplays");
+                        }
+                        mUsersOnSecondaryDisplays = null;
+                    }
+                }
+                return;
+            }
+
+            synchronized (mUsersLock) {
+                if (mUsersOnSecondaryDisplays == null) {
+                    if (DBG) {
+                        Slogf.d(LOG_TAG, "Creating mUsersOnSecondaryDisplays");
+                    }
+                    mUsersOnSecondaryDisplays = new SparseIntArray();
+                }
+                if (DBG) {
+                    Slogf.d(LOG_TAG, "Adding %d->%d to mUsersOnSecondaryDisplays",
+                            userId, displayId);
+                }
+
+                if (isProfileUnchecked(userId)) {
+                    // Profile can only start in the same display as parent
+                    int parentUserId = getProfileParentId(userId);
+                    int parentDisplayId = mUsersOnSecondaryDisplays.get(parentUserId);
+                    if (displayId != parentDisplayId) {
+                        throw new IllegalStateException("Cannot assign profile " + userId + " to "
+                                + "display " + displayId + " as its parent (user " + parentUserId
+                                + ") is assigned to display " + parentDisplayId);
+                    }
+                } else {
+                    // Check if display is available
+                    for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
+                        // Make sure display is not used by other users...
+                        // TODO(b/240736142); currently, if a user was started in a display, it
+                        // would need to be stopped first, so "switching" a user on secondary
+                        // diplay requires 2 non-atomic operations (stop and start). Once this logic
+                        // is refactored, it should be atomic.
+                        if (mUsersOnSecondaryDisplays.valueAt(i) == displayId) {
+                            throw new IllegalStateException("Cannot assign " + userId + " to "
+                                    + "display " + displayId + " as it's  already assigned to "
+                                    + "user " + mUsersOnSecondaryDisplays.keyAt(i));
+                        }
+                        // TODO(b/239982558) also check that user is not already assigned to other
+                        // display (including 0). That would be harder to tested under CTS though
+                        // (for example, would need to add a new AM method to start user in bg on
+                        // main display), so it's better to test on unit tests
+                    }
+                }
+
+                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/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java
index 2f3ca66..ebb9f98 100644
--- a/services/core/java/com/android/server/pm/UserTypeDetails.java
+++ b/services/core/java/com/android/server/pm/UserTypeDetails.java
@@ -23,6 +23,7 @@
 import android.annotation.StringRes;
 import android.content.pm.UserInfo;
 import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.pm.UserProperties;
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.UserManager;
@@ -171,6 +172,12 @@
     private final @CrossProfileIntentFilter.AccessControlLevel int
             mCrossProfileIntentFilterAccessControl;
 
+    /**
+     * The default {@link UserProperties} for the user type.
+     * <p> The uninitialized value of each property is implied by {@link UserProperties.Builder}.
+     */
+    private final @NonNull UserProperties mDefaultUserProperties;
+
     private UserTypeDetails(@NonNull String name, boolean enabled, int maxAllowed,
             @UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label,
             int maxAllowedPerParent,
@@ -183,7 +190,8 @@
             @Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters,
             boolean isMediaSharedWithParent,
             boolean isCredentialSharableWithParent,
-            @CrossProfileIntentFilter.AccessControlLevel int accessControlLevel) {
+            @CrossProfileIntentFilter.AccessControlLevel int accessControlLevel,
+            @NonNull UserProperties defaultUserProperties) {
         this.mName = name;
         this.mEnabled = enabled;
         this.mMaxAllowed = maxAllowed;
@@ -205,6 +213,7 @@
         this.mIsMediaSharedWithParent = isMediaSharedWithParent;
         this.mIsCredentialSharableWithParent = isCredentialSharableWithParent;
         this.mCrossProfileIntentFilterAccessControl = accessControlLevel;
+        this.mDefaultUserProperties = defaultUserProperties;
     }
 
     /**
@@ -310,18 +319,6 @@
         return mDarkThemeBadgeColors[Math.min(badgeIndex, mDarkThemeBadgeColors.length - 1)];
     }
 
-    public boolean isProfile() {
-        return (mBaseType & UserInfo.FLAG_PROFILE) != 0;
-    }
-
-    public boolean isFull() {
-        return (mBaseType & UserInfo.FLAG_FULL) != 0;
-    }
-
-    public boolean isSystem() {
-        return (mBaseType & UserInfo.FLAG_SYSTEM) != 0;
-    }
-
     /**
      * Returns true if the user has shared media with parent user or false otherwise.
      */
@@ -347,6 +344,26 @@
         return mCrossProfileIntentFilterAccessControl;
     }
 
+    /**
+     * Returns the reference to the default {@link UserProperties} for this type of user.
+     * This is not a copy. Do NOT modify this object.
+     */
+    public @NonNull UserProperties getDefaultUserPropertiesReference() {
+        return mDefaultUserProperties;
+    }
+
+    public boolean isProfile() {
+        return (mBaseType & UserInfo.FLAG_PROFILE) != 0;
+    }
+
+    public boolean isFull() {
+        return (mBaseType & UserInfo.FLAG_FULL) != 0;
+    }
+
+    public boolean isSystem() {
+        return (mBaseType & UserInfo.FLAG_SYSTEM) != 0;
+    }
+
     /** Returns a {@link Bundle} representing the default user restrictions. */
     @NonNull Bundle getDefaultRestrictions() {
         return BundleUtils.clone(mDefaultRestrictions);
@@ -384,6 +401,7 @@
         pw.print(prefix); pw.print("mDefaultUserInfoFlags: ");
         pw.println(UserInfo.flagsToString(mDefaultUserInfoPropertyFlags));
         pw.print(prefix); pw.print("mLabel: "); pw.println(mLabel);
+        mDefaultUserProperties.println(pw, prefix);
 
         final String restrictionsPrefix = prefix + "    ";
         if (isSystem()) {
@@ -442,6 +460,9 @@
         private boolean mIsCredentialSharableWithParent = false;
         private @CrossProfileIntentFilter.AccessControlLevel int
                 mCrossProfileIntentFilterAccessControl = CrossProfileIntentFilter.ACCESS_LEVEL_ALL;
+        // Default UserProperties cannot be null but for efficiency we don't initialize it now.
+        // If it isn't set explicitly, {@link UserProperties.Builder#build()} will be used.
+        private @Nullable UserProperties mDefaultUserProperties = null;
 
         public Builder setName(String name) {
             mName = name;
@@ -560,6 +581,23 @@
             return this;
         }
 
+        /**
+         * Sets (replacing if necessary) the default UserProperties object for this user type.
+         * Takes a builder, rather than a built object, to efficiently ensure that a fresh copy of
+         * properties is stored (since it later might be modified by UserProperties#updateFromXml).
+         */
+        public Builder setDefaultUserProperties(UserProperties.Builder userPropertiesBuilder) {
+            mDefaultUserProperties = userPropertiesBuilder.build();
+            return this;
+        }
+
+        public @NonNull UserProperties getDefaultUserProperties() {
+            if (mDefaultUserProperties == null) {
+                mDefaultUserProperties = new UserProperties.Builder().build();
+            }
+            return mDefaultUserProperties;
+        }
+
         @UserInfoFlag int getBaseType() {
             return mBaseType;
         }
@@ -604,7 +642,8 @@
                     mDefaultCrossProfileIntentFilters,
                     mIsMediaSharedWithParent,
                     mIsCredentialSharableWithParent,
-                    mCrossProfileIntentFilterAccessControl);
+                    mCrossProfileIntentFilterAccessControl,
+                    getDefaultUserProperties());
         }
 
         private boolean hasBadge() {
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 857a975..b98d20e 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -37,6 +37,7 @@
 import static com.android.server.pm.UserTypeDetails.UNLIMITED_NUMBER_OF_USERS;
 
 import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.os.Build;
@@ -124,7 +125,10 @@
                 .setIsMediaSharedWithParent(true)
                 .setCrossProfileIntentFilterAccessControl(
                         CrossProfileIntentFilter.ACCESS_LEVEL_SYSTEM)
-                .setIsCredentialSharableWithParent(true);
+                .setIsCredentialSharableWithParent(true)
+                .setDefaultUserProperties(new UserProperties.Builder()
+                        .setStartWithParent(true)
+                        .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT));
     }
 
     /**
@@ -156,7 +160,10 @@
                 .setDefaultRestrictions(getDefaultManagedProfileRestrictions())
                 .setDefaultSecureSettings(getDefaultManagedProfileSecureSettings())
                 .setDefaultCrossProfileIntentFilters(getDefaultManagedCrossProfileIntentFilter())
-                .setIsCredentialSharableWithParent(true);
+                .setIsCredentialSharableWithParent(true)
+                .setDefaultUserProperties(new UserProperties.Builder()
+                        .setStartWithParent(true)
+                        .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE));
     }
 
     /**
@@ -396,6 +403,9 @@
                         setResAttributeArray(parser, builder::setBadgeColors);
                     } else if (isProfile && "badge-colors-dark".equals(childName)) {
                         setResAttributeArray(parser, builder::setDarkThemeBadgeColors);
+                    } else if ("user-properties".equals(childName)) {
+                        builder.getDefaultUserProperties()
+                                .updateFromXml(XmlUtils.makeTyped(parser));
                     } else {
                         Slog.w(LOG_TAG, "Unrecognized tag " + childName + " in "
                                 + parser.getPositionDescription());
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/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 5b8f473..c129f37 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -1256,6 +1256,18 @@
         return input.success(pkg.addPermission(result.getResult()));
     }
 
+    private int parseMinOrMaxSdkVersion(TypedArray sa, int attr, int defaultValue) {
+        int val = defaultValue;
+        TypedValue peekVal = sa.peekValue(attr);
+        if (peekVal != null) {
+            if (peekVal.type >= TypedValue.TYPE_FIRST_INT
+                    && peekVal.type <= TypedValue.TYPE_LAST_INT) {
+                val = peekVal.data;
+            }
+        }
+        return val;
+    }
+
     private ParseResult<ParsingPackage> parseUsesPermission(ParseInput input,
             ParsingPackage pkg, Resources res, XmlResourceParser parser)
             throws IOException, XmlPullParserException {
@@ -1266,14 +1278,13 @@
             String name = sa.getNonResourceString(
                     R.styleable.AndroidManifestUsesPermission_name);
 
-            int maxSdkVersion = 0;
-            TypedValue val = sa.peekValue(
-                    R.styleable.AndroidManifestUsesPermission_maxSdkVersion);
-            if (val != null) {
-                if (val.type >= TypedValue.TYPE_FIRST_INT && val.type <= TypedValue.TYPE_LAST_INT) {
-                    maxSdkVersion = val.data;
-                }
-            }
+            int minSdkVersion =  parseMinOrMaxSdkVersion(sa,
+                    R.styleable.AndroidManifestUsesPermission_minSdkVersion,
+                    Integer.MIN_VALUE);
+
+            int maxSdkVersion =  parseMinOrMaxSdkVersion(sa,
+                    R.styleable.AndroidManifestUsesPermission_maxSdkVersion,
+                    Integer.MAX_VALUE);
 
             final ArraySet<String> requiredFeatures = new ArraySet<>();
             String feature = sa.getNonConfigurationString(
@@ -1338,7 +1349,8 @@
                 return success;
             }
 
-            if ((maxSdkVersion != 0) && (maxSdkVersion < Build.VERSION.RESOURCES_SDK_INT)) {
+            if (Build.VERSION.RESOURCES_SDK_INT < minSdkVersion
+                    || Build.VERSION.RESOURCES_SDK_INT > maxSdkVersion) {
                 return success;
             }
 
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
index 4b0a8e2..466c4c9 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
@@ -103,7 +103,6 @@
         pw.println("      <DOMAINS>: space separated list of domains to change, or \"all\" to");
         pw.println("        change every domain.");
         pw.println("  set-app-links-allowed --user <USER_ID> [--package <PACKAGE>] <ALLOWED>");
-        pw.println("      <ENABLED> <DOMAINS>...");
         pw.println("    Toggle the auto verified link handling setting for a package.");
         pw.println("      --user <USER_ID>: the user to change selections for");
         pw.println("      --package <PACKAGE>: the package to set, or \"all\" to set all packages");
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 5816e98..1a1de03 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -215,7 +215,6 @@
 import com.android.server.wm.DisplayRotation;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.WindowManagerInternal.AppTransitionListener;
-import com.android.server.wm.WindowManagerService;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -2082,22 +2081,19 @@
 
         mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() {
             @Override
-            public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
-                    boolean keyguardOccluding, long duration, long statusBarAnimationStartTime,
+            public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
                     long statusBarAnimationDuration) {
-                // 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);
+                return handleTransitionForKeyguardLw(false /* startKeyguardExitAnimation */,
+                        false /* notifyOccluded */);
             }
 
             @Override
-            public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
-                handleStartTransitionForKeyguardLw(
-                        keyguardGoingAway, false /* keyguardOccludingStarted */,
-                        0 /* duration */);
+            public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled) {
+                // When KEYGUARD_GOING_AWAY app transition is canceled, we need to trigger relevant
+                // IKeyguardService calls to sync keyguard status in WindowManagerService and SysUI.
+                handleTransitionForKeyguardLw(
+                        keyguardGoingAwayCancelled /* startKeyguardExitAnimation */,
+                        true /* notifyOccluded */);
             }
         });
 
@@ -3258,36 +3254,43 @@
 
     @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, false /* force */,
-                    false /* transitionStarted */);
+            setKeyguardOccludedLw(occluded, true /* notify */);
         }
     }
 
     @Override
-    public int applyKeyguardOcclusionChange(boolean transitionStarted) {
+    public int applyKeyguardOcclusionChange(boolean notify) {
         if (mKeyguardOccludedChanged) {
             if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded="
                     + mPendingKeyguardOccluded);
-            if (setKeyguardOccludedLw(mPendingKeyguardOccluded, false /* force */,
-                    transitionStarted)) {
+            if (setKeyguardOccludedLw(mPendingKeyguardOccluded, notify)) {
                 return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_WALLPAPER;
             }
         }
         return 0;
     }
 
-    private int handleStartTransitionForKeyguardLw(boolean keyguardGoingAway,
-            boolean keyguardOccluding, long duration) {
-        final int redoLayout = applyKeyguardOcclusionChange(keyguardOccluding);
+    /**
+     * 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);
         if (redoLayout != 0) return redoLayout;
-        if (keyguardGoingAway) {
+        if (startKeyguardExitAnimation) {
             if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation");
-            startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration);
+            startKeyguardExitAnimation(SystemClock.uptimeMillis());
         }
         return 0;
     }
@@ -3519,28 +3522,18 @@
      * Updates the occluded state of the Keyguard.
      *
      * @param isOccluded Whether the Keyguard is occluded by another window.
-     * @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.
+     * @param notify Notify keyguard occlude status change immediately via
+     *       {@link com.android.internal.policy.IKeyguardService}.
      * @return Whether the flags have changed and we have to redo the layout.
      */
-    private boolean setKeyguardOccludedLw(boolean isOccluded, boolean force,
-            boolean transitionStarted) {
+    private boolean setKeyguardOccludedLw(boolean isOccluded, boolean notify) {
         if (DEBUG_KEYGUARD) Slog.d(TAG, "setKeyguardOccluded occluded=" + isOccluded);
         mKeyguardOccludedChanged = false;
-        if (isKeyguardOccluded() == isOccluded && !force) {
+        if (isKeyguardOccluded() == isOccluded) {
             return false;
         }
-
-        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;
+        mKeyguardDelegate.setOccluded(isOccluded, notify);
+        return mKeyguardDelegate.isShowing();
     }
 
     /** {@inheritDoc} */
@@ -4936,10 +4929,10 @@
     }
 
     @Override
-    public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
+    public void startKeyguardExitAnimation(long startTime) {
         if (mKeyguardDelegate != null) {
             if (DEBUG_KEYGUARD) Slog.d(TAG, "PWM.startKeyguardExitAnimation");
-            mKeyguardDelegate.startKeyguardExitAnimation(startTime, fadeoutDuration);
+            mKeyguardDelegate.startKeyguardExitAnimation(startTime);
         }
     }
 
@@ -5121,18 +5114,10 @@
 
     /** {@inheritDoc} */
     @Override
-    public void userActivity() {
-        // ***************************************
-        // NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE
-        // ***************************************
-        // THIS IS CALLED FROM DEEP IN THE POWER MANAGER
-        // WITH ITS LOCKS HELD.
-        //
-        // This code must be VERY careful about the locks
-        // it acquires.
-        // In fact, the current code acquires way too many,
-        // and probably has lurking deadlocks.
-
+    public void userActivity(int displayGroupId, int event) {
+        if (displayGroupId == DEFAULT_DISPLAY && event == PowerManager.USER_ACTIVITY_EVENT_TOUCH) {
+            mDefaultDisplayPolicy.onUserActivityEventTouch();
+        }
         synchronized (mScreenLockTimeout) {
             if (mLockScreenTimerActive) {
                 // reset the timer
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 e8a3dcd..2b04050 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);
 
     /**
-     * Applies a keyguard occlusion change if one happened.
-     * @param transitionStarted Whether keyguard (un)occlude transition is starting or not.
+     * @param notify {@code true} if the status change should be immediately notified via
+     *        {@link com.android.internal.policy.IKeyguardService}
      */
-    int applyKeyguardOcclusionChange(boolean transitionStarted);
+    int applyKeyguardOcclusionChange(boolean notify);
 
     /**
      * Interface to the Window Manager state associated with a particular
@@ -1006,7 +1006,7 @@
      * Called when userActivity is signalled in the power manager.
      * This is safe to call from any thread, with any window manager locks held or not.
      */
-    public void userActivity();
+    void userActivity(int displayGroupId, int event);
 
     /**
      * Called when we have finished booting and can now display the home
@@ -1129,11 +1129,10 @@
 
     /**
      * 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, long fadeoutDuration);
+    void startKeyguardExitAnimation(long startTime);
 
     /**
      * 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 b79ac6f..7737421 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 animate, boolean notify) {
+    public void setOccluded(boolean isOccluded, boolean notify) {
         if (mKeyguardService != null && notify) {
-            if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ") animate=" + animate);
-            mKeyguardService.setOccluded(isOccluded, animate);
+            if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ")");
+            mKeyguardService.setOccluded(isOccluded, false /* animate */);
         }
         mKeyguardState.occluded = isOccluded;
     }
@@ -394,9 +394,9 @@
         }
     }
 
-    public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
+    public void startKeyguardExitAnimation(long startTime) {
         if (mKeyguardService != null) {
-            mKeyguardService.startKeyguardExitAnimation(startTime, fadeoutDuration);
+            mKeyguardService.startKeyguardExitAnimation(startTime, 0);
         }
     }
 
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 685b744..5a2fb18 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -712,7 +712,7 @@
         }
         TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
         tm.notifyUserActivity();
-        mPolicy.userActivity();
+        mPolicy.userActivity(displayGroupId, event);
         mFaceDownDetector.userActivity(event);
         mScreenUndimDetector.userActivity(displayGroupId);
     }
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 96823c8a..f378588 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -72,6 +72,12 @@
 public class ThermalManagerService extends SystemService {
     private static final String TAG = ThermalManagerService.class.getSimpleName();
 
+    private static final boolean DEBUG = false;
+
+    /** Input range limits for getThermalHeadroom API */
+    public static final int MIN_FORECAST_SEC = 0;
+    public static final int MAX_FORECAST_SEC = 60;
+
     /** Lock to protect listen list. */
     private final Object mLock = new Object();
 
@@ -478,6 +484,13 @@
                 return Float.NaN;
             }
 
+            if (forecastSeconds < MIN_FORECAST_SEC || forecastSeconds > MAX_FORECAST_SEC) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Invalid forecastSeconds: " + forecastSeconds);
+                }
+                return Float.NaN;
+            }
+
             return mTemperatureWatcher.getForecast(forecastSeconds);
         }
 
diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
index 12e68b1..be90530 100644
--- a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
@@ -31,9 +31,9 @@
     public final VibratorController controller;
     public final VibrationEffect.Composed effect;
     public final int segmentIndex;
-    public final long previousStepVibratorOffTimeout;
 
     long mVibratorOnResult;
+    long mPendingVibratorOffDeadline;
     boolean mVibratorCompleteCallbackReceived;
 
     /**
@@ -43,19 +43,19 @@
      * @param controller         The vibrator that is playing the effect.
      * @param effect             The effect being played in this step.
      * @param index              The index of the next segment to be played by this step
-     * @param previousStepVibratorOffTimeout The time the vibrator is expected to complete any
+     * @param pendingVibratorOffDeadline The time the vibrator is expected to complete any
      *                           previous vibration and turn off. This is used to allow this step to
      *                           be triggered when the completion callback is received, and can
      *                           be used to play effects back-to-back.
      */
     AbstractVibratorStep(VibrationStepConductor conductor, long startTime,
             VibratorController controller, VibrationEffect.Composed effect, int index,
-            long previousStepVibratorOffTimeout) {
+            long pendingVibratorOffDeadline) {
         super(conductor, startTime);
         this.controller = controller;
         this.effect = effect;
         this.segmentIndex = index;
-        this.previousStepVibratorOffTimeout = previousStepVibratorOffTimeout;
+        mPendingVibratorOffDeadline = pendingVibratorOffDeadline;
     }
 
     public int getVibratorId() {
@@ -69,33 +69,65 @@
 
     @Override
     public boolean acceptVibratorCompleteCallback(int vibratorId) {
-        boolean isSameVibrator = controller.getVibratorInfo().getId() == vibratorId;
-        mVibratorCompleteCallbackReceived |= isSameVibrator;
+        if (getVibratorId() != vibratorId) {
+            return false;
+        }
+
         // Only activate this step if a timeout was set to wait for the vibration to complete,
         // otherwise we are waiting for the correct time to play the next step.
-        return isSameVibrator && (previousStepVibratorOffTimeout > SystemClock.uptimeMillis());
+        boolean shouldAcceptCallback = mPendingVibratorOffDeadline > SystemClock.uptimeMillis();
+        if (VibrationThread.DEBUG) {
+            Slog.d(VibrationThread.TAG,
+                    "Received completion callback from " + vibratorId
+                            + ", accepted = " + shouldAcceptCallback);
+        }
+
+        // The callback indicates this vibrator has stopped, reset the timeout.
+        mPendingVibratorOffDeadline = 0;
+        mVibratorCompleteCallbackReceived = true;
+        return shouldAcceptCallback;
     }
 
     @Override
     public List<Step> cancel() {
         return Arrays.asList(new CompleteEffectVibratorStep(conductor, SystemClock.uptimeMillis(),
-                /* cancelled= */ true, controller, previousStepVibratorOffTimeout));
+                /* cancelled= */ true, controller, mPendingVibratorOffDeadline));
     }
 
     @Override
     public void cancelImmediately() {
-        if (previousStepVibratorOffTimeout > SystemClock.uptimeMillis()) {
+        if (mPendingVibratorOffDeadline > SystemClock.uptimeMillis()) {
             // Vibrator might be running from previous steps, so turn it off while canceling.
             stopVibrating();
         }
     }
 
+    protected long handleVibratorOnResult(long vibratorOnResult) {
+        mVibratorOnResult = vibratorOnResult;
+        if (VibrationThread.DEBUG) {
+            Slog.d(VibrationThread.TAG,
+                    "Turned on vibrator " + getVibratorId() + ", result = " + mVibratorOnResult);
+        }
+        if (mVibratorOnResult > 0) {
+            // Vibrator was turned on by this step, with vibratorOnResult as the duration.
+            // Set an extra timeout to wait for the vibrator completion callback.
+            mPendingVibratorOffDeadline = SystemClock.uptimeMillis() + mVibratorOnResult
+                    + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
+        } else {
+            // Vibrator does not support the request or failed to turn on, reset callback deadline.
+            mPendingVibratorOffDeadline = 0;
+        }
+        return mVibratorOnResult;
+    }
+
     protected void stopVibrating() {
         if (VibrationThread.DEBUG) {
             Slog.d(VibrationThread.TAG,
                     "Turning off vibrator " + getVibratorId());
         }
         controller.off();
+        getVibration().stats().reportVibratorOff();
+        mPendingVibratorOffDeadline = 0;
     }
 
     protected void changeAmplitude(float amplitude) {
@@ -104,53 +136,45 @@
                     "Amplitude changed on vibrator " + getVibratorId() + " to " + amplitude);
         }
         controller.setAmplitude(amplitude);
+        getVibration().stats().reportSetAmplitude();
     }
 
     /**
-     * Return the {@link VibrationStepConductor#nextVibrateStep} with same timings, only jumping
-     * the segments.
-     */
-    protected List<Step> skipToNextSteps(int segmentsSkipped) {
-        return nextSteps(startTime, previousStepVibratorOffTimeout, segmentsSkipped);
-    }
-
-    /**
-     * Return the {@link VibrationStepConductor#nextVibrateStep} with same start and off timings
-     * calculated from {@link #getVibratorOnDuration()}, jumping all played segments.
-     *
-     * <p>This method has same behavior as {@link #skipToNextSteps(int)} when the vibrator
-     * result is non-positive, meaning the vibrator has either ignored or failed to turn on.
+     * Return the {@link VibrationStepConductor#nextVibrateStep} with start and off timings
+     * calculated from {@link #getVibratorOnDuration()} based on the current
+     * {@link SystemClock#uptimeMillis()} and jumping all played segments from the effect.
      */
     protected List<Step> nextSteps(int segmentsPlayed) {
-        if (mVibratorOnResult <= 0) {
-            // Vibration was not started, so just skip the played segments and keep timings.
-            return skipToNextSteps(segmentsPlayed);
+        // Schedule next steps to run right away.
+        long nextStartTime = SystemClock.uptimeMillis();
+        if (mVibratorOnResult > 0) {
+            // Vibrator was turned on by this step, with mVibratorOnResult as the duration.
+            // Schedule next steps for right after the vibration finishes.
+            nextStartTime += mVibratorOnResult;
         }
-        long nextStartTime = SystemClock.uptimeMillis() + mVibratorOnResult;
-        long nextVibratorOffTimeout =
-                nextStartTime + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
-        return nextSteps(nextStartTime, nextVibratorOffTimeout, segmentsPlayed);
+        return nextSteps(nextStartTime, segmentsPlayed);
     }
 
     /**
-     * Return the {@link VibrationStepConductor#nextVibrateStep} with given start and off timings,
-     * which might be calculated independently, jumping all played segments.
+     * Return the {@link VibrationStepConductor#nextVibrateStep} with given start time,
+     * which might be calculated independently, and jumping all played segments from the effect.
      *
-     * <p>This should be used when the vibrator on/off state is not responsible for the steps
-     * execution timings, e.g. while playing the vibrator amplitudes.
+     * <p>This should be used when the vibrator on/off state is not responsible for the step
+     * execution timing, e.g. while playing the vibrator amplitudes.
      */
-    protected List<Step> nextSteps(long nextStartTime, long vibratorOffTimeout,
-            int segmentsPlayed) {
+    protected List<Step> nextSteps(long nextStartTime, int segmentsPlayed) {
         int nextSegmentIndex = segmentIndex + segmentsPlayed;
         int effectSize = effect.getSegments().size();
         int repeatIndex = effect.getRepeatIndex();
         if (nextSegmentIndex >= effectSize && repeatIndex >= 0) {
             // Count the loops that were played.
             int loopSize = effectSize - repeatIndex;
+            int loopSegmentsPlayed = nextSegmentIndex - repeatIndex;
+            getVibration().stats().reportRepetition(loopSegmentsPlayed / loopSize);
             nextSegmentIndex = repeatIndex + ((nextSegmentIndex - effectSize) % loopSize);
         }
         Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
-                nextSegmentIndex, vibratorOffTimeout);
+                nextSegmentIndex, mPendingVibratorOffDeadline);
         return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST : Arrays.asList(nextStep);
     }
 }
diff --git a/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
index 8585e34..fb5140d 100644
--- a/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
@@ -34,9 +34,9 @@
     private final boolean mCancelled;
 
     CompleteEffectVibratorStep(VibrationStepConductor conductor, long startTime, boolean cancelled,
-            VibratorController controller, long previousStepVibratorOffTimeout) {
+            VibratorController controller, long pendingVibratorOffDeadline) {
         super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1,
-                previousStepVibratorOffTimeout);
+                pendingVibratorOffDeadline);
         mCancelled = cancelled;
     }
 
@@ -73,10 +73,11 @@
                 return VibrationStepConductor.EMPTY_STEP_LIST;
             }
 
+            long now = SystemClock.uptimeMillis();
             float currentAmplitude = controller.getCurrentAmplitude();
             long remainingOnDuration =
-                    previousStepVibratorOffTimeout - VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT
-                            - SystemClock.uptimeMillis();
+                    mPendingVibratorOffDeadline - now
+                            - VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
             long rampDownDuration =
                     Math.min(remainingOnDuration,
                             conductor.vibrationSettings.getRampDownDuration());
@@ -89,8 +90,10 @@
                     stopVibrating();
                     return VibrationStepConductor.EMPTY_STEP_LIST;
                 } else {
+                    // Vibration is completing normally, turn off after the deadline in case we
+                    // don't receive the callback in time (callback also triggers it right away).
                     return Arrays.asList(new TurnOffVibratorStep(
-                            conductor, previousStepVibratorOffTimeout, controller));
+                            conductor, mPendingVibratorOffDeadline, controller));
                 }
             }
 
@@ -100,13 +103,18 @@
                                 + " from amplitude " + currentAmplitude
                                 + " for " + rampDownDuration + "ms");
             }
+
+            // If we are cancelling this vibration then make sure the vibrator will be turned off
+            // immediately after the ramp off duration. Otherwise, this is a planned ramp off for
+            // the remaining ON duration, then just propagate the mPendingVibratorOffDeadline so the
+            // turn off step will wait for the vibration completion callback and end gracefully.
+            long rampOffVibratorOffDeadline =
+                    mCancelled ? (now + rampDownDuration) : mPendingVibratorOffDeadline;
             float amplitudeDelta = currentAmplitude / (rampDownDuration / stepDownDuration);
             float amplitudeTarget = currentAmplitude - amplitudeDelta;
-            long newVibratorOffTimeout =
-                    mCancelled ? rampDownDuration : previousStepVibratorOffTimeout;
             return Arrays.asList(
                     new RampOffVibratorStep(conductor, startTime, amplitudeTarget, amplitudeDelta,
-                            controller, newVibratorOffTimeout));
+                            controller, rampOffVibratorOffDeadline));
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
diff --git a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
index 3bc11c8..545ec5b 100644
--- a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
@@ -40,11 +40,11 @@
 
     ComposePrimitivesVibratorStep(VibrationStepConductor conductor, long startTime,
             VibratorController controller, VibrationEffect.Composed effect, int index,
-            long previousStepVibratorOffTimeout) {
+            long pendingVibratorOffDeadline) {
         // This step should wait for the last vibration to finish (with the timeout) and for the
         // intended step start time (to respect the effect delays).
-        super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
-                index, previousStepVibratorOffTimeout);
+        super(conductor, Math.max(startTime, pendingVibratorOffDeadline), controller, effect,
+                index, pendingVibratorOffDeadline);
     }
 
     @Override
@@ -60,17 +60,22 @@
             if (primitives.isEmpty()) {
                 Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePrimitivesStep: "
                         + effect.getSegments().get(segmentIndex));
-                return skipToNextSteps(/* segmentsSkipped= */ 1);
+                // Skip this step and play the next one right away.
+                return nextSteps(/* segmentsPlayed= */ 1);
             }
 
             if (VibrationThread.DEBUG) {
                 Slog.d(VibrationThread.TAG, "Compose " + primitives + " primitives on vibrator "
-                        + controller.getVibratorInfo().getId());
+                        + getVibratorId());
             }
-            mVibratorOnResult = controller.on(
-                    primitives.toArray(new PrimitiveSegment[primitives.size()]),
-                    getVibration().id);
 
+            PrimitiveSegment[] primitivesArray =
+                    primitives.toArray(new PrimitiveSegment[primitives.size()]);
+            long vibratorOnResult = controller.on(primitivesArray, getVibration().id);
+            handleVibratorOnResult(vibratorOnResult);
+            getVibration().stats().reportComposePrimitives(vibratorOnResult, primitivesArray);
+
+            // The next start and off times will be calculated from mVibratorOnResult.
             return nextSteps(/* segmentsPlayed= */ primitives.size());
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
index 919f1be..8bfa2c3 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
@@ -41,11 +41,11 @@
 
     ComposePwleVibratorStep(VibrationStepConductor conductor, long startTime,
             VibratorController controller, VibrationEffect.Composed effect, int index,
-            long previousStepVibratorOffTimeout) {
+            long pendingVibratorOffDeadline) {
         // This step should wait for the last vibration to finish (with the timeout) and for the
         // intended step start time (to respect the effect delays).
-        super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
-                index, previousStepVibratorOffTimeout);
+        super(conductor, Math.max(startTime, pendingVibratorOffDeadline), controller, effect,
+                index, pendingVibratorOffDeadline);
     }
 
     @Override
@@ -61,16 +61,20 @@
             if (pwles.isEmpty()) {
                 Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePwleStep: "
                         + effect.getSegments().get(segmentIndex));
-                return skipToNextSteps(/* segmentsSkipped= */ 1);
+                // Skip this step and play the next one right away.
+                return nextSteps(/* segmentsPlayed= */ 1);
             }
 
             if (VibrationThread.DEBUG) {
                 Slog.d(VibrationThread.TAG, "Compose " + pwles + " PWLEs on vibrator "
                         + controller.getVibratorInfo().getId());
             }
-            mVibratorOnResult = controller.on(pwles.toArray(new RampSegment[pwles.size()]),
-                    getVibration().id);
+            RampSegment[] pwlesArray = pwles.toArray(new RampSegment[pwles.size()]);
+            long vibratorOnResult = controller.on(pwlesArray, getVibration().id);
+            handleVibratorOnResult(vibratorOnResult);
+            getVibration().stats().reportComposePwle(vibratorOnResult, pwlesArray);
 
+            // The next start and off times will be calculated from mVibratorOnResult.
             return nextSteps(/* segmentsPlayed= */ pwles.size());
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
diff --git a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
index 601ae97..d91bafa 100644
--- a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
@@ -35,11 +35,11 @@
 
     PerformPrebakedVibratorStep(VibrationStepConductor conductor, long startTime,
             VibratorController controller, VibrationEffect.Composed effect, int index,
-            long previousStepVibratorOffTimeout) {
+            long pendingVibratorOffDeadline) {
         // This step should wait for the last vibration to finish (with the timeout) and for the
         // intended step start time (to respect the effect delays).
-        super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
-                index, previousStepVibratorOffTimeout);
+        super(conductor, Math.max(startTime, pendingVibratorOffDeadline), controller, effect,
+                index, pendingVibratorOffDeadline);
     }
 
     @Override
@@ -50,7 +50,8 @@
             if (!(segment instanceof PrebakedSegment)) {
                 Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a "
                         + "PerformPrebakedVibratorStep: " + segment);
-                return skipToNextSteps(/* segmentsSkipped= */ 1);
+                // Skip this step and play the next one right away.
+                return nextSteps(/* segmentsPlayed= */ 1);
             }
 
             PrebakedSegment prebaked = (PrebakedSegment) segment;
@@ -61,9 +62,11 @@
             }
 
             VibrationEffect fallback = getVibration().getFallback(prebaked.getEffectId());
-            mVibratorOnResult = controller.on(prebaked, getVibration().id);
+            long vibratorOnResult = controller.on(prebaked, getVibration().id);
+            handleVibratorOnResult(vibratorOnResult);
+            getVibration().stats().reportPerformEffect(vibratorOnResult, prebaked);
 
-            if (mVibratorOnResult == 0 && prebaked.shouldFallback()
+            if (vibratorOnResult == 0 && prebaked.shouldFallback()
                     && (fallback instanceof VibrationEffect.Composed)) {
                 if (VibrationThread.DEBUG) {
                     Slog.d(VibrationThread.TAG, "Playing fallback for effect "
@@ -71,14 +74,15 @@
                 }
                 AbstractVibratorStep fallbackStep = conductor.nextVibrateStep(startTime, controller,
                         replaceCurrentSegment((VibrationEffect.Composed) fallback),
-                        segmentIndex, previousStepVibratorOffTimeout);
+                        segmentIndex, mPendingVibratorOffDeadline);
                 List<Step> fallbackResult = fallbackStep.play();
                 // Update the result with the fallback result so this step is seamlessly
                 // replaced by the fallback to any outer application of this.
-                mVibratorOnResult = fallbackStep.getVibratorOnDuration();
+                handleVibratorOnResult(fallbackStep.getVibratorOnDuration());
                 return fallbackResult;
             }
 
+            // The next start and off times will be calculated from mVibratorOnResult.
             return nextSteps(/* segmentsPlayed= */ 1);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
diff --git a/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
index 8cf5fb3..84da9f2 100644
--- a/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
@@ -30,9 +30,9 @@
 
     RampOffVibratorStep(VibrationStepConductor conductor, long startTime, float amplitudeTarget,
             float amplitudeDelta, VibratorController controller,
-            long previousStepVibratorOffTimeout) {
+            long pendingVibratorOffDeadline) {
         super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1,
-                previousStepVibratorOffTimeout);
+                pendingVibratorOffDeadline);
         mAmplitudeTarget = amplitudeTarget;
         mAmplitudeDelta = amplitudeDelta;
     }
@@ -68,15 +68,17 @@
 
             float newAmplitudeTarget = mAmplitudeTarget - mAmplitudeDelta;
             if (newAmplitudeTarget < VibrationStepConductor.RAMP_OFF_AMPLITUDE_MIN) {
-                // Vibrator amplitude cannot go further down, just turn it off.
+                // Vibrator amplitude cannot go further down, just turn it off with the configured
+                // deadline that has been adjusted for the scenario when this was triggered by a
+                // cancelled vibration.
                 return Arrays.asList(new TurnOffVibratorStep(
-                        conductor, previousStepVibratorOffTimeout, controller));
+                        conductor, mPendingVibratorOffDeadline, controller));
             }
             return Arrays.asList(new RampOffVibratorStep(
                     conductor,
                     startTime + conductor.vibrationSettings.getRampStepDuration(),
                     newAmplitudeTarget, mAmplitudeDelta, controller,
-                    previousStepVibratorOffTimeout));
+                    mPendingVibratorOffDeadline));
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
index 1f0d2d7..1672470 100644
--- a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
@@ -39,26 +39,34 @@
      */
     private static final int REPEATING_EFFECT_ON_DURATION = 5000; // 5s
 
-    private long mNextOffTime;
-
     SetAmplitudeVibratorStep(VibrationStepConductor conductor, long startTime,
             VibratorController controller, VibrationEffect.Composed effect, int index,
-            long previousStepVibratorOffTimeout) {
+            long pendingVibratorOffDeadline) {
         // This step has a fixed startTime coming from the timings of the waveform it's playing.
-        super(conductor, startTime, controller, effect, index, previousStepVibratorOffTimeout);
-        mNextOffTime = previousStepVibratorOffTimeout;
+        super(conductor, startTime, controller, effect, index, pendingVibratorOffDeadline);
     }
 
     @Override
     public boolean acceptVibratorCompleteCallback(int vibratorId) {
-        if (controller.getVibratorInfo().getId() == vibratorId) {
-            mVibratorCompleteCallbackReceived = true;
-            mNextOffTime = SystemClock.uptimeMillis();
+        // Ensure the super method is called and will reset the off timeout and boolean flag.
+        // This is true if the vibrator was ON and this callback has the same vibratorId.
+        if (!super.acceptVibratorCompleteCallback(vibratorId)) {
+            return false;
         }
+
         // Timings are tightly controlled here, so only trigger this step if the vibrator was
         // supposed to be ON but has completed prematurely, to turn it back on as soon as
-        // possible.
-        return mNextOffTime < startTime && controller.getCurrentAmplitude() > 0;
+        // possible. If the vibrator turned off during a zero-amplitude step, just wait for
+        // the correct start time of this step before playing it.
+        boolean shouldAcceptCallback =
+                (SystemClock.uptimeMillis() < startTime) && (controller.getCurrentAmplitude() > 0);
+
+        if (VibrationThread.DEBUG) {
+            Slog.d(VibrationThread.TAG,
+                    "Amplitude step received completion callback from " + vibratorId
+                            + ", accepted = " + shouldAcceptCallback);
+        }
+        return shouldAcceptCallback;
     }
 
     @Override
@@ -78,40 +86,38 @@
             if (mVibratorCompleteCallbackReceived && latency < 0) {
                 // This step was run early because the vibrator turned off prematurely.
                 // Turn it back on and return this same step to run at the exact right time.
-                mNextOffTime = turnVibratorBackOn(/* remainingDuration= */ -latency);
+                turnVibratorBackOn(/* remainingDuration= */ -latency);
                 return Arrays.asList(new SetAmplitudeVibratorStep(conductor, startTime, controller,
-                        effect, segmentIndex, mNextOffTime));
+                        effect, segmentIndex, mPendingVibratorOffDeadline));
             }
 
             VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
             if (!(segment instanceof StepSegment)) {
                 Slog.w(VibrationThread.TAG,
                         "Ignoring wrong segment for a SetAmplitudeVibratorStep: " + segment);
-                return skipToNextSteps(/* segmentsSkipped= */ 1);
+                // Use original startTime to avoid propagating latencies to the waveform.
+                return nextSteps(startTime, /* segmentsPlayed= */ 1);
             }
 
             StepSegment stepSegment = (StepSegment) segment;
             if (stepSegment.getDuration() == 0) {
-                // Skip waveform entries with zero timing.
-                return skipToNextSteps(/* segmentsSkipped= */ 1);
+                // Use original startTime to avoid propagating latencies to the waveform.
+                return nextSteps(startTime, /* segmentsPlayed= */ 1);
             }
 
             float amplitude = stepSegment.getAmplitude();
             if (amplitude == 0) {
-                if (previousStepVibratorOffTimeout > now) {
+                if (mPendingVibratorOffDeadline > now) {
                     // Amplitude cannot be set to zero, so stop the vibrator.
                     stopVibrating();
-                    mNextOffTime = now;
                 }
             } else {
-                if (startTime >= mNextOffTime) {
+                if (startTime >= mPendingVibratorOffDeadline) {
                     // Vibrator is OFF. Turn vibrator back on for the duration of another
                     // cycle before setting the amplitude.
                     long onDuration = getVibratorOnDuration(effect, segmentIndex);
                     if (onDuration > 0) {
-                        mVibratorOnResult = startVibrating(onDuration);
-                        mNextOffTime = now + onDuration
-                                + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
+                        startVibrating(onDuration);
                     }
                 }
                 changeAmplitude(amplitude);
@@ -119,27 +125,32 @@
 
             // Use original startTime to avoid propagating latencies to the waveform.
             long nextStartTime = startTime + segment.getDuration();
-            return nextSteps(nextStartTime, mNextOffTime, /* segmentsPlayed= */ 1);
+            return nextSteps(nextStartTime, /* segmentsPlayed= */ 1);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
-    private long turnVibratorBackOn(long remainingDuration) {
+    private void turnVibratorBackOn(long remainingDuration) {
         long onDuration = getVibratorOnDuration(effect, segmentIndex);
         if (onDuration <= 0) {
             // Vibrator is supposed to go back off when this step starts, so just leave it off.
-            return previousStepVibratorOffTimeout;
+            return;
         }
         onDuration += remainingDuration;
+
+        if (VibrationThread.DEBUG) {
+            Slog.d(VibrationThread.TAG,
+                    "Turning the vibrator back ON using the remaining duration of "
+                            + remainingDuration + "ms, for a total of " + onDuration + "ms");
+        }
+
         float expectedAmplitude = controller.getCurrentAmplitude();
-        mVibratorOnResult = startVibrating(onDuration);
-        if (mVibratorOnResult > 0) {
+        long vibratorOnResult = startVibrating(onDuration);
+        if (vibratorOnResult > 0) {
             // Set the amplitude back to the value it was supposed to be playing at.
             changeAmplitude(expectedAmplitude);
         }
-        return SystemClock.uptimeMillis() + onDuration
-                + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
     }
 
     private long startVibrating(long duration) {
@@ -148,7 +159,10 @@
                     "Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
                             + duration + "ms");
         }
-        return controller.on(duration, getVibration().id);
+        long vibratorOnResult = controller.on(duration, getVibration().id);
+        handleVibratorOnResult(vibratorOnResult);
+        getVibration().stats().reportVibratorOn(vibratorOnResult);
+        return vibratorOnResult;
     }
 
     /**
diff --git a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
index 080a36c..2c6fbbc9 100644
--- a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
+++ b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
@@ -93,10 +93,8 @@
             }
 
             mVibratorsOnMaxDuration = startVibrating(effectMapping, nextSteps);
-            if (mVibratorsOnMaxDuration > 0) {
-                conductor.vibratorManagerHooks.noteVibratorOn(conductor.getVibration().uid,
-                        mVibratorsOnMaxDuration);
-            }
+            conductor.vibratorManagerHooks.noteVibratorOn(conductor.getVibration().uid,
+                    mVibratorsOnMaxDuration);
         } finally {
             if (mVibratorsOnMaxDuration >= 0) {
                 // It least one vibrator was started then add a finish step to wait for all
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index d79837b..a375d0a 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -16,10 +16,10 @@
 
 package com.android.server.vibrator;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.CombinedVibration;
 import android.os.IBinder;
-import android.os.SystemClock;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.vibrator.PrebakedSegment;
@@ -30,48 +30,60 @@
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.util.FrameworkStatsLog;
+
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.function.Function;
 
 /** Represents a vibration request to the vibrator service. */
 final class Vibration {
-    private static final String TAG = "Vibration";
     private static final SimpleDateFormat DEBUG_DATE_FORMAT =
             new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
 
+    /** Vibration status with reference to values from vibratormanagerservice.proto for logging. */
     enum Status {
-        RUNNING,
-        FINISHED,
-        FINISHED_UNEXPECTED,  // Didn't terminate in the usual way.
-        FORWARDED_TO_INPUT_DEVICES,
-        CANCELLED_BINDER_DIED,
-        CANCELLED_BY_SCREEN_OFF,
-        CANCELLED_BY_SETTINGS_UPDATE,
-        CANCELLED_BY_USER,
-        CANCELLED_BY_UNKNOWN_REASON,
-        CANCELLED_SUPERSEDED,
-        IGNORED_ERROR_APP_OPS,
-        IGNORED_ERROR_CANCELLING,
-        IGNORED_ERROR_SCHEDULING,
-        IGNORED_ERROR_TOKEN,
-        IGNORED_APP_OPS,
-        IGNORED_BACKGROUND,
-        IGNORED_UNKNOWN_VIBRATION,
-        IGNORED_UNSUPPORTED,
-        IGNORED_FOR_EXTERNAL,
-        IGNORED_FOR_HIGHER_IMPORTANCE,
-        IGNORED_FOR_ONGOING,
-        IGNORED_FOR_POWER,
-        IGNORED_FOR_RINGER_MODE,
-        IGNORED_FOR_SETTINGS,
-        IGNORED_SUPERSEDED,
+        UNKNOWN(VibrationProto.UNKNOWN),
+        RUNNING(VibrationProto.RUNNING),
+        FINISHED(VibrationProto.FINISHED),
+        FINISHED_UNEXPECTED(VibrationProto.FINISHED_UNEXPECTED),
+        FORWARDED_TO_INPUT_DEVICES(VibrationProto.FORWARDED_TO_INPUT_DEVICES),
+        CANCELLED_BINDER_DIED(VibrationProto.CANCELLED_BINDER_DIED),
+        CANCELLED_BY_SCREEN_OFF(VibrationProto.CANCELLED_BY_SCREEN_OFF),
+        CANCELLED_BY_SETTINGS_UPDATE(VibrationProto.CANCELLED_BY_SETTINGS_UPDATE),
+        CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER),
+        CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON),
+        CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED),
+        IGNORED_ERROR_APP_OPS(VibrationProto.IGNORED_ERROR_APP_OPS),
+        IGNORED_ERROR_CANCELLING(VibrationProto.IGNORED_ERROR_CANCELLING),
+        IGNORED_ERROR_SCHEDULING(VibrationProto.IGNORED_ERROR_SCHEDULING),
+        IGNORED_ERROR_TOKEN(VibrationProto.IGNORED_ERROR_TOKEN),
+        IGNORED_APP_OPS(VibrationProto.IGNORED_APP_OPS),
+        IGNORED_BACKGROUND(VibrationProto.IGNORED_BACKGROUND),
+        IGNORED_UNKNOWN_VIBRATION(VibrationProto.IGNORED_UNKNOWN_VIBRATION),
+        IGNORED_UNSUPPORTED(VibrationProto.IGNORED_UNSUPPORTED),
+        IGNORED_FOR_EXTERNAL(VibrationProto.IGNORED_FOR_EXTERNAL),
+        IGNORED_FOR_HIGHER_IMPORTANCE(VibrationProto.IGNORED_FOR_HIGHER_IMPORTANCE),
+        IGNORED_FOR_ONGOING(VibrationProto.IGNORED_FOR_ONGOING),
+        IGNORED_FOR_POWER(VibrationProto.IGNORED_FOR_POWER),
+        IGNORED_FOR_RINGER_MODE(VibrationProto.IGNORED_FOR_RINGER_MODE),
+        IGNORED_FOR_SETTINGS(VibrationProto.IGNORED_FOR_SETTINGS),
+        IGNORED_SUPERSEDED(VibrationProto.IGNORED_SUPERSEDED);
+
+        private final int mProtoEnumValue;
+
+        Status(int value) {
+            mProtoEnumValue = value;
+        }
+
+        public int getProtoEnumValue() {
+            return mProtoEnumValue;
+        }
     }
 
-    /** Start time using {@link SystemClock#uptimeMillis()}, for calculations. */
-    public final long startUptimeMillis;
     public final VibrationAttributes attrs;
     public final long id;
     public final int uid;
@@ -91,17 +103,11 @@
     @Nullable
     private CombinedVibration mOriginalEffect;
 
-    /**
-     * Start/end times in unix epoch time. Only to be used for debugging purposes and to correlate
-     * with other system events, any duration calculations should be done use
-     * {@link #startUptimeMillis} so as not to be affected by discontinuities created by RTC
-     * adjustments.
-     */
-    private final long mStartTimeDebug;
-    private long mEndTimeDebug;
-    /** End time using {@link SystemClock#uptimeMillis()}, for calculations. */
-    private long mEndUptimeMillis;
-    private Status mStatus;
+    /** Vibration status. */
+    private Vibration.Status mStatus;
+
+    /** Vibration runtime stats. */
+    private final VibrationStats mStats = new VibrationStats();
 
     /** A {@link CountDownLatch} to enable waiting for completion. */
     private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
@@ -111,34 +117,35 @@
         this.token = token;
         this.mEffect = effect;
         this.id = id;
-        this.startUptimeMillis = SystemClock.uptimeMillis();
         this.attrs = attrs;
         this.uid = uid;
         this.opPkg = opPkg;
         this.reason = reason;
-        mStartTimeDebug = System.currentTimeMillis();
-        mStatus = Status.RUNNING;
+        mStatus = Vibration.Status.RUNNING;
+    }
+
+    VibrationStats stats() {
+        return mStats;
     }
 
     /**
-     * Set the {@link Status} of this vibration and the current system time as this
+     * Set the {@link Status} of this vibration and reports the current system time as this
      * vibration end time, for debugging purposes.
      *
      * <p>This method will only accept given value if the current status is {@link
      * Status#RUNNING}.
      */
-    public void end(Status status) {
+    public void end(EndInfo info) {
         if (hasEnded()) {
             // Vibration already ended, keep first ending status set and ignore this one.
             return;
         }
-        mStatus = status;
-        mEndUptimeMillis = SystemClock.uptimeMillis();
-        mEndTimeDebug = System.currentTimeMillis();
+        mStatus = info.status;
+        mStats.reportEnded(info.endedByUid, info.endedByUsage);
         mCompletionLatch.countDown();
     }
 
-    /** Waits indefinitely until another thread calls {@link #end(Status)} on this vibration. */
+    /** Waits indefinitely until another thread calls {@link #end} on this vibration. */
     public void waitForEnd() throws InterruptedException {
         mCompletionLatch.await();
     }
@@ -228,16 +235,69 @@
 
     /** Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */
     public Vibration.DebugInfo getDebugInfo() {
-        long durationMs = hasEnded() ? mEndUptimeMillis - startUptimeMillis : -1;
-        return new Vibration.DebugInfo(
-                mStartTimeDebug, mEndTimeDebug, durationMs, mEffect, mOriginalEffect,
-                /* scale= */ 0, attrs, uid, opPkg, reason, mStatus);
+        return new Vibration.DebugInfo(mStatus, mStats, mEffect, mOriginalEffect, /* scale= */ 0,
+                attrs, uid, opPkg, reason);
+    }
+
+    /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
+    public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
+        int vibrationType = isRepeating()
+                ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
+                : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
+        return new VibrationStats.StatsInfo(
+                uid, vibrationType, attrs.getUsage(), mStatus, mStats, completionUptimeMillis);
+    }
+
+    /** Immutable info passed as a signal to end a vibration. */
+    static final class EndInfo {
+        /** The {@link Status} to be set to the vibration when it ends with this info. */
+        @NonNull
+        public final Status status;
+        /** The UID that triggered the vibration that ended this, or -1 if undefined. */
+        public final int endedByUid;
+        /** The VibrationAttributes.USAGE_* of the vibration that ended this, or -1 if undefined. */
+        public final int endedByUsage;
+
+        EndInfo(@NonNull Vibration.Status status) {
+            this(status, /* endedByUid= */ -1, /* endedByUsage= */ -1);
+        }
+
+        EndInfo(@NonNull Vibration.Status status, int endedByUid, int endedByUsage) {
+            this.status = status;
+            this.endedByUid = endedByUid;
+            this.endedByUsage = endedByUsage;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof EndInfo)) return false;
+            EndInfo that = (EndInfo) o;
+            return endedByUid == that.endedByUid
+                    && endedByUsage == that.endedByUsage
+                    && status == that.status;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(status, endedByUid, endedByUsage);
+        }
+
+        @Override
+        public String toString() {
+            return "EndInfo{"
+                    + "status=" + status
+                    + ", endedByUid=" + endedByUid
+                    + ", endedByUsage=" + endedByUsage
+                    + '}';
+        }
     }
 
     /** Debug information about vibrations. */
     static final class DebugInfo {
-        private final long mStartTimeDebug;
-        private final long mEndTimeDebug;
+        private final long mCreateTime;
+        private final long mStartTime;
+        private final long mEndTime;
         private final long mDurationMs;
         private final CombinedVibration mEffect;
         private final CombinedVibration mOriginalEffect;
@@ -248,12 +308,13 @@
         private final String mReason;
         private final Status mStatus;
 
-        DebugInfo(long startTimeDebug, long endTimeDebug, long durationMs,
-                CombinedVibration effect, CombinedVibration originalEffect, float scale,
-                VibrationAttributes attrs, int uid, String opPkg, String reason, Status status) {
-            mStartTimeDebug = startTimeDebug;
-            mEndTimeDebug = endTimeDebug;
-            mDurationMs = durationMs;
+        DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration effect,
+                @Nullable CombinedVibration originalEffect, float scale, VibrationAttributes attrs,
+                int uid, String opPkg, String reason) {
+            mCreateTime = stats.getCreateTimeDebug();
+            mStartTime = stats.getStartTimeDebug();
+            mEndTime = stats.getEndTimeDebug();
+            mDurationMs = stats.getDurationDebug();
             mEffect = effect;
             mOriginalEffect = originalEffect;
             mScale = scale;
@@ -267,11 +328,13 @@
         @Override
         public String toString() {
             return new StringBuilder()
-                    .append("startTime: ")
-                    .append(DEBUG_DATE_FORMAT.format(new Date(mStartTimeDebug)))
+                    .append("createTime: ")
+                    .append(DEBUG_DATE_FORMAT.format(new Date(mCreateTime)))
+                    .append(", startTime: ")
+                    .append(DEBUG_DATE_FORMAT.format(new Date(mStartTime)))
                     .append(", endTime: ")
-                    .append(mEndTimeDebug == 0 ? null
-                            : DEBUG_DATE_FORMAT.format(new Date(mEndTimeDebug)))
+                    .append(mEndTime == 0 ? null
+                            : DEBUG_DATE_FORMAT.format(new Date(mEndTime)))
                     .append(", durationMs: ")
                     .append(mDurationMs)
                     .append(", status: ")
@@ -296,8 +359,8 @@
         /** Write this info into given {@code fieldId} on {@link ProtoOutputStream}. */
         public void dumpProto(ProtoOutputStream proto, long fieldId) {
             final long token = proto.start(fieldId);
-            proto.write(VibrationProto.START_TIME, mStartTimeDebug);
-            proto.write(VibrationProto.END_TIME, mEndTimeDebug);
+            proto.write(VibrationProto.START_TIME, mStartTime);
+            proto.write(VibrationProto.END_TIME, mEndTime);
             proto.write(VibrationProto.DURATION_MS, mDurationMs);
             proto.write(VibrationProto.STATUS, mStatus.ordinal());
 
@@ -421,4 +484,5 @@
             proto.end(token);
         }
     }
+
 }
diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java
new file mode 100644
index 0000000..931be1d
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibrationStats.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator;
+
+import android.os.SystemClock;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+/** Holds basic stats about the vibration playback and interaction with the vibrator HAL. */
+final class VibrationStats {
+    static final String TAG = "VibrationStats";
+
+    // Milestone timestamps, using SystemClock.uptimeMillis(), for calculations.
+    // - Create: time a vibration object was created, which is closer to when the service receives a
+    //           vibrate request.
+    // - Start: time a vibration started to play, which is closer to the time that the
+    //          VibrationEffect started playing the very first segment.
+    // - End: time a vibration ended, even if it never started to play. This can be as soon as the
+    //        vibrator HAL reports it has finished the last command, or before it has even started
+    //        when the vibration is ignored or cancelled.
+    // Create and end times set by VibratorManagerService only, guarded by its lock.
+    // Start times set by VibrationThread only (single-threaded).
+    private long mCreateUptimeMillis;
+    private long mStartUptimeMillis;
+    private long mEndUptimeMillis;
+
+    // Milestone timestamps, using unix epoch time, only to be used for debugging purposes and
+    // to correlate with other system events. Any duration calculations should be done with the
+    // {create/start/end}UptimeMillis counterparts so as not to be affected by discontinuities
+    // created by RTC adjustments.
+    // Set together with the *UptimeMillis counterparts.
+    private long mCreateTimeDebug;
+    private long mStartTimeDebug;
+    private long mEndTimeDebug;
+
+    // Vibration interruption tracking.
+    // Set by VibratorManagerService only, guarded by its lock.
+    private int mEndedByUid;
+    private int mEndedByUsage;
+    private int mInterruptedUsage;
+
+    // All following counters are set by VibrationThread only (single-threaded):
+    // Counts how many times the VibrationEffect was repeated.
+    private int mRepeatCount;
+    // Total duration, in milliseconds, the vibrator was active with non-zero amplitude.
+    private int mVibratorOnTotalDurationMillis;
+    // Total number of primitives used in compositions.
+    private int mVibrationCompositionTotalSize;
+    private int mVibrationPwleTotalSize;
+    // Counts how many times each IVibrator method was triggered by this vibration.
+    private int mVibratorOnCount;
+    private int mVibratorOffCount;
+    private int mVibratorSetAmplitudeCount;
+    private int mVibratorSetExternalControlCount;
+    private int mVibratorPerformCount;
+    private int mVibratorComposeCount;
+    private int mVibratorComposePwleCount;
+
+    // Ids of vibration effects and primitives used by this vibration, with support flag.
+    // Set by VibrationThread only (single-threaded).
+    private SparseBooleanArray mVibratorEffectsUsed = new SparseBooleanArray();
+    private SparseBooleanArray mVibratorPrimitivesUsed = new SparseBooleanArray();
+
+    VibrationStats() {
+        mCreateUptimeMillis = SystemClock.uptimeMillis();
+        mCreateTimeDebug = System.currentTimeMillis();
+        // Set invalid UID and VibrationAttributes.USAGE values to indicate fields are unset.
+        mEndedByUid = -1;
+        mEndedByUsage = -1;
+        mInterruptedUsage = -1;
+    }
+
+    long getCreateUptimeMillis() {
+        return mCreateUptimeMillis;
+    }
+
+    long getStartUptimeMillis() {
+        return mStartUptimeMillis;
+    }
+
+    long getEndUptimeMillis() {
+        return mEndUptimeMillis;
+    }
+
+    long getCreateTimeDebug() {
+        return mCreateTimeDebug;
+    }
+
+    long getStartTimeDebug() {
+        return mStartTimeDebug;
+    }
+
+    long getEndTimeDebug() {
+        return mEndTimeDebug;
+    }
+
+    /**
+     * Duration calculated for debugging purposes, between the creation of a vibration and the
+     * end time being reported, or -1 if the vibration has not ended.
+     */
+    long getDurationDebug() {
+        return hasEnded() ? (mEndUptimeMillis - mCreateUptimeMillis) : -1;
+    }
+
+    /** Return true if vibration reported it has ended. */
+    boolean hasEnded() {
+        return mEndUptimeMillis > 0;
+    }
+
+    /** Return true if vibration reported it has started triggering the vibrator. */
+    boolean hasStarted() {
+        return mStartUptimeMillis > 0;
+    }
+
+    /**
+     * Set the current system time as this vibration start time, for debugging purposes.
+     *
+     * <p>This indicates the vibration has started to interact with the vibrator HAL and the
+     * device may start vibrating after this point.
+     *
+     * <p>This method will only accept given value if the start timestamp was never set.
+     */
+    void reportStarted() {
+        if (hasEnded() || (mStartUptimeMillis != 0)) {
+            // Vibration already started or ended, keep first time set and ignore this one.
+            return;
+        }
+        mStartUptimeMillis = SystemClock.uptimeMillis();
+        mStartTimeDebug = System.currentTimeMillis();
+    }
+
+    /**
+     * Set status and end cause for this vibration to end, and the current system time as this
+     * vibration end time, for debugging purposes.
+     *
+     * <p>This might be triggered before {@link #reportStarted()}, which indicates this
+     * vibration was cancelled or ignored before it started triggering the vibrator.
+     *
+     * @return true if the status was accepted. This method will only accept given values if
+     * the end timestamp was never set.
+     */
+    boolean reportEnded(int endedByUid, int endedByUsage) {
+        if (hasEnded()) {
+            // Vibration already ended, keep first ending stats set and ignore this one.
+            return false;
+        }
+        mEndedByUid = endedByUid;
+        mEndedByUsage = endedByUsage;
+        mEndUptimeMillis = SystemClock.uptimeMillis();
+        mEndTimeDebug = System.currentTimeMillis();
+        return true;
+    }
+
+    /**
+     * Report this vibration has interrupted another vibration.
+     *
+     * <p>This method will only accept the first value as the one that was interrupted by this
+     * vibration, and will ignore all successive calls.
+     */
+    void reportInterruptedAnotherVibration(int interruptedUsage) {
+        if (mInterruptedUsage < 0) {
+            mInterruptedUsage = interruptedUsage;
+        }
+    }
+
+    /** Report the vibration has looped a few more times. */
+    void reportRepetition(int loops) {
+        mRepeatCount += loops;
+    }
+
+    /** Report a call to vibrator method to turn on for given duration. */
+    void reportVibratorOn(long halResult) {
+        mVibratorOnCount++;
+
+        if (halResult > 0) {
+            // If HAL result is positive then it represents the actual duration it will be ON.
+            mVibratorOnTotalDurationMillis += (int) halResult;
+        }
+    }
+
+    /** Report a call to vibrator method to turn off. */
+    void reportVibratorOff() {
+        mVibratorOffCount++;
+    }
+
+    /** Report a call to vibrator method to change the vibration amplitude. */
+    void reportSetAmplitude() {
+        mVibratorSetAmplitudeCount++;
+    }
+
+    /** Report a call to vibrator method to trigger a vibration effect. */
+    void reportPerformEffect(long halResult, PrebakedSegment prebaked) {
+        mVibratorPerformCount++;
+
+        if (halResult > 0) {
+            // If HAL result is positive then it represents the actual duration of the vibration.
+            mVibratorEffectsUsed.put(prebaked.getEffectId(), true);
+            mVibratorOnTotalDurationMillis += (int) halResult;
+        } else {
+            // Effect unsupported or request failed.
+            mVibratorEffectsUsed.put(prebaked.getEffectId(), false);
+        }
+    }
+
+    /** Report a call to vibrator method to trigger a vibration as a composition of primitives. */
+    void reportComposePrimitives(long halResult, PrimitiveSegment[] primitives) {
+        mVibratorComposeCount++;
+        mVibrationCompositionTotalSize += primitives.length;
+
+        if (halResult > 0) {
+            // If HAL result is positive then it represents the actual duration of the vibration.
+            // Remove the requested delays to update the total time the vibrator was ON.
+            for (PrimitiveSegment primitive : primitives) {
+                halResult -= primitive.getDelay();
+                mVibratorPrimitivesUsed.put(primitive.getPrimitiveId(), true);
+            }
+            if (halResult > 0) {
+                mVibratorOnTotalDurationMillis += (int) halResult;
+            }
+        } else {
+            // One or more primitives were unsupported, or request failed.
+            for (PrimitiveSegment primitive : primitives) {
+                mVibratorPrimitivesUsed.put(primitive.getPrimitiveId(), false);
+            }
+        }
+    }
+
+    /** Report a call to vibrator method to trigger a vibration as a PWLE. */
+    void reportComposePwle(long halResult, RampSegment[] segments) {
+        mVibratorComposePwleCount++;
+        mVibrationPwleTotalSize += segments.length;
+
+        if (halResult > 0) {
+            // If HAL result is positive then it represents the actual duration of the vibration.
+            // Remove the zero-amplitude segments to update the total time the vibrator was ON.
+            for (RampSegment ramp : segments) {
+                if ((ramp.getStartAmplitude() == 0) && (ramp.getEndAmplitude() == 0)) {
+                    halResult -= ramp.getDuration();
+                }
+            }
+            if (halResult > 0) {
+                mVibratorOnTotalDurationMillis += (int) halResult;
+            }
+        }
+    }
+
+    /**
+     * Increment the stats for total number of times the {@code setExternalControl} method was
+     * triggered in the vibrator HAL.
+     */
+    void reportSetExternalControl() {
+        mVibratorSetExternalControlCount++;
+    }
+
+    /**
+     * Immutable metrics about this vibration, to be kept in memory until it can be pushed through
+     * {@link com.android.internal.util.FrameworkStatsLog} as a
+     * {@link com.android.internal.util.FrameworkStatsLog#VIBRATION_REPORTED}.
+     */
+    static final class StatsInfo {
+        public final int uid;
+        public final int vibrationType;
+        public final int usage;
+        public final int status;
+        public final boolean endedBySameUid;
+        public final int endedByUsage;
+        public final int interruptedUsage;
+        public final int repeatCount;
+        public final int totalDurationMillis;
+        public final int vibratorOnMillis;
+        public final int startLatencyMillis;
+        public final int endLatencyMillis;
+        public final int halComposeCount;
+        public final int halComposePwleCount;
+        public final int halOnCount;
+        public final int halOffCount;
+        public final int halPerformCount;
+        public final int halSetAmplitudeCount;
+        public final int halSetExternalControlCount;
+        public final int halCompositionSize;
+        public final int halPwleSize;
+        public final int[] halSupportedCompositionPrimitivesUsed;
+        public final int[] halSupportedEffectsUsed;
+        public final int[] halUnsupportedCompositionPrimitivesUsed;
+        public final int[] halUnsupportedEffectsUsed;
+        private boolean mIsWritten;
+
+        StatsInfo(int uid, int vibrationType, int usage, Vibration.Status status,
+                VibrationStats stats, long completionUptimeMillis) {
+            this.uid = uid;
+            this.vibrationType = vibrationType;
+            this.usage = usage;
+            this.status = status.getProtoEnumValue();
+            endedBySameUid = (uid == stats.mEndedByUid);
+            endedByUsage = stats.mEndedByUsage;
+            interruptedUsage = stats.mInterruptedUsage;
+            repeatCount = stats.mRepeatCount;
+
+            // This duration goes from the time this object was created until the time it was
+            // completed. We can use latencies to detect the times between first and last
+            // interaction with vibrator.
+            totalDurationMillis =
+                    (int) Math.max(0,  completionUptimeMillis - stats.mCreateUptimeMillis);
+            vibratorOnMillis = stats.mVibratorOnTotalDurationMillis;
+
+            if (stats.hasStarted()) {
+                // We only measure latencies for vibrations that actually triggered the vibrator.
+                startLatencyMillis =
+                        (int) Math.max(0, stats.mStartUptimeMillis - stats.mCreateUptimeMillis);
+                endLatencyMillis =
+                        (int) Math.max(0, completionUptimeMillis - stats.mEndUptimeMillis);
+            } else {
+                startLatencyMillis = endLatencyMillis = 0;
+            }
+
+            halComposeCount = stats.mVibratorComposeCount;
+            halComposePwleCount = stats.mVibratorComposePwleCount;
+            halOnCount = stats.mVibratorOnCount;
+            halOffCount = stats.mVibratorOffCount;
+            halPerformCount = stats.mVibratorPerformCount;
+            halSetAmplitudeCount = stats.mVibratorSetAmplitudeCount;
+            halSetExternalControlCount = stats.mVibratorSetExternalControlCount;
+            halCompositionSize = stats.mVibrationCompositionTotalSize;
+            halPwleSize = stats.mVibrationPwleTotalSize;
+            halSupportedCompositionPrimitivesUsed =
+                    filteredKeys(stats.mVibratorPrimitivesUsed, /* supported= */ true);
+            halSupportedEffectsUsed =
+                    filteredKeys(stats.mVibratorEffectsUsed, /* supported= */ true);
+            halUnsupportedCompositionPrimitivesUsed =
+                    filteredKeys(stats.mVibratorPrimitivesUsed, /* supported= */ false);
+            halUnsupportedEffectsUsed =
+                    filteredKeys(stats.mVibratorEffectsUsed, /* supported= */ false);
+        }
+
+        @VisibleForTesting
+        boolean isWritten() {
+            return mIsWritten;
+        }
+
+        void writeVibrationReported() {
+            if (mIsWritten) {
+                Slog.wtf(TAG, "Writing same vibration stats multiple times for uid=" + uid);
+            }
+            mIsWritten = true;
+            // Mapping from this MetricInfo representation and the atom proto VibrationReported.
+            FrameworkStatsLog.write_non_chained(
+                    FrameworkStatsLog.VIBRATION_REPORTED,
+                    uid, null, vibrationType, usage, status, endedBySameUid, endedByUsage,
+                    interruptedUsage, repeatCount, totalDurationMillis, vibratorOnMillis,
+                    startLatencyMillis, endLatencyMillis, halComposeCount, halComposePwleCount,
+                    halOnCount, halOffCount, halPerformCount, halSetAmplitudeCount,
+                    halSetExternalControlCount, halSupportedCompositionPrimitivesUsed,
+                    halSupportedEffectsUsed, halUnsupportedCompositionPrimitivesUsed,
+                    halUnsupportedEffectsUsed, halCompositionSize, halPwleSize);
+        }
+
+        private static int[] filteredKeys(SparseBooleanArray supportArray, boolean supported) {
+            int count = 0;
+            for (int i = 0; i < supportArray.size(); i++) {
+                if (supportArray.valueAt(i) == supported) count++;
+            }
+            if (count == 0) {
+                return null;
+            }
+            int pos = 0;
+            int[] res = new int[count];
+            for (int i = 0; i < supportArray.size(); i++) {
+                if (supportArray.valueAt(i) == supported) {
+                    res[pos++] = supportArray.keyAt(i);
+                }
+            }
+            return res;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index e3d8067..0af1718 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -81,12 +81,12 @@
     private final IntArray mSignalVibratorsComplete;
     @Nullable
     @GuardedBy("mLock")
-    private Vibration.Status mSignalCancelStatus = null;
+    private Vibration.EndInfo mSignalCancel = null;
     @GuardedBy("mLock")
     private boolean mSignalCancelImmediate = false;
 
     @Nullable
-    private Vibration.Status mCancelStatus = null;
+    private Vibration.EndInfo mCancelledVibrationEndInfo = null;
     private boolean mCancelledImmediately = false;  // hard stop
     private int mPendingVibrateSteps;
     private int mRemainingStartSequentialEffectSteps;
@@ -112,8 +112,7 @@
 
     @Nullable
     AbstractVibratorStep nextVibrateStep(long startTime, VibratorController controller,
-            VibrationEffect.Composed effect, int segmentIndex,
-            long previousStepVibratorOffTimeout) {
+            VibrationEffect.Composed effect, int segmentIndex, long pendingVibratorOffDeadline) {
         if (Build.IS_DEBUGGABLE) {
             expectIsVibrationThread(true);
         }
@@ -123,24 +122,24 @@
         if (segmentIndex < 0) {
             // No more segments to play, last step is to complete the vibration on this vibrator.
             return new CompleteEffectVibratorStep(this, startTime, /* cancelled= */ false,
-                    controller, previousStepVibratorOffTimeout);
+                    controller, pendingVibratorOffDeadline);
         }
 
         VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
         if (segment instanceof PrebakedSegment) {
             return new PerformPrebakedVibratorStep(this, startTime, controller, effect,
-                    segmentIndex, previousStepVibratorOffTimeout);
+                    segmentIndex, pendingVibratorOffDeadline);
         }
         if (segment instanceof PrimitiveSegment) {
             return new ComposePrimitivesVibratorStep(this, startTime, controller, effect,
-                    segmentIndex, previousStepVibratorOffTimeout);
+                    segmentIndex, pendingVibratorOffDeadline);
         }
         if (segment instanceof RampSegment) {
             return new ComposePwleVibratorStep(this, startTime, controller, effect, segmentIndex,
-                    previousStepVibratorOffTimeout);
+                    pendingVibratorOffDeadline);
         }
         return new SetAmplitudeVibratorStep(this, startTime, controller, effect, segmentIndex,
-                previousStepVibratorOffTimeout);
+                pendingVibratorOffDeadline);
     }
 
     /** Called when this conductor is going to be started running by the VibrationThread. */
@@ -153,6 +152,9 @@
         // This count is decremented at the completion of the step, so we don't subtract one.
         mRemainingStartSequentialEffectSteps = sequentialEffect.getEffects().size();
         mNextSteps.offer(new StartSequentialEffectStep(this, sequentialEffect));
+        // Vibration will start playing in the Vibrator, following the effect timings and delays.
+        // Report current time as the vibration start time, for debugging.
+        mVibration.stats().reportStarted();
     }
 
     public Vibration getVibration() {
@@ -182,24 +184,25 @@
      * Calculate the {@link Vibration.Status} based on the current queue state and the expected
      * number of {@link StartSequentialEffectStep} to be played.
      */
-    public Vibration.Status calculateVibrationStatus() {
+    @Nullable
+    public Vibration.EndInfo calculateVibrationEndInfo() {
         if (Build.IS_DEBUGGABLE) {
             expectIsVibrationThread(true);
         }
 
-        if (mCancelStatus != null) {
-            return mCancelStatus;
+        if (mCancelledVibrationEndInfo != null) {
+            return mCancelledVibrationEndInfo;
         }
-        if (mPendingVibrateSteps > 0
-                || mRemainingStartSequentialEffectSteps > 0) {
-            return Vibration.Status.RUNNING;
+        if (mPendingVibrateSteps > 0 || mRemainingStartSequentialEffectSteps > 0) {
+            // Vibration still running.
+            return null;
         }
         // No pending steps, and something happened.
         if (mSuccessfulVibratorOnSteps > 0) {
-            return Vibration.Status.FINISHED;
+            return new Vibration.EndInfo(Vibration.Status.FINISHED);
         }
         // If no step was able to turn the vibrator ON successfully.
-        return Vibration.Status.IGNORED_UNSUPPORTED;
+        return new Vibration.EndInfo(Vibration.Status.IGNORED_UNSUPPORTED);
     }
 
     /**
@@ -305,45 +308,50 @@
         if (DEBUG) {
             Slog.d(TAG, "Binder died, cancelling vibration...");
         }
-        notifyCancelled(Vibration.Status.CANCELLED_BINDER_DIED, /* immediate= */ false);
+        notifyCancelled(new Vibration.EndInfo(Vibration.Status.CANCELLED_BINDER_DIED),
+                /* immediate= */ false);
     }
 
     /**
      * Notify the execution that cancellation is requested. This will be acted upon
      * asynchronously in the VibrationThread.
      *
+     * <p>Only the first cancel signal will be used to end a cancelled vibration, but subsequent
+     * calls with {@code immediate} flag set to true can still force the first cancel signal to
+     * take effect urgently.
+     *
      * @param immediate indicates whether cancellation should abort urgently and skip cleanup steps.
      */
-    public void notifyCancelled(@NonNull Vibration.Status cancelStatus, boolean immediate) {
+    public void notifyCancelled(@NonNull Vibration.EndInfo cancelInfo, boolean immediate) {
         if (Build.IS_DEBUGGABLE) {
             expectIsVibrationThread(false);
         }
         if (DEBUG) {
-            Slog.d(TAG, "Vibration cancel requested with status=" + cancelStatus
+            Slog.d(TAG, "Vibration cancel requested with signal=" + cancelInfo
                     + ", immediate=" + immediate);
         }
-        if ((cancelStatus == null) || !cancelStatus.name().startsWith("CANCEL")) {
-            Slog.w(TAG, "Vibration cancel requested with bad status=" + cancelStatus
+        if ((cancelInfo == null) || !cancelInfo.status.name().startsWith("CANCEL")) {
+            Slog.w(TAG, "Vibration cancel requested with bad signal=" + cancelInfo
                     + ", using CANCELLED_UNKNOWN_REASON to ensure cancellation.");
-            cancelStatus = Vibration.Status.CANCELLED_BY_UNKNOWN_REASON;
+            cancelInfo = new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_UNKNOWN_REASON);
         }
         synchronized (mLock) {
-            if (immediate && mSignalCancelImmediate || (mSignalCancelStatus != null)) {
+            if ((immediate && mSignalCancelImmediate) || (mSignalCancel != null)) {
                 if (DEBUG) {
                     Slog.d(TAG, "Vibration cancel request ignored as the vibration "
-                            + mVibration.id + "is already being cancelled with status="
-                            + mSignalCancelStatus + ", immediate=" + mSignalCancelImmediate);
+                            + mVibration.id + "is already being cancelled with signal="
+                            + mSignalCancel + ", immediate=" + mSignalCancelImmediate);
                 }
                 return;
             }
             mSignalCancelImmediate |= immediate;
-            if (mSignalCancelStatus == null) {
-                mSignalCancelStatus = cancelStatus;
+            if (mSignalCancel == null) {
+                mSignalCancel = cancelInfo;
             } else {
                 if (DEBUG) {
-                    Slog.d(TAG, "Vibration cancel request new status=" + cancelStatus
-                            + " ignored as the vibration was already cancelled with status="
-                            + mSignalCancelStatus + ", but immediate flag was updated to "
+                    Slog.d(TAG, "Vibration cancel request new signal=" + cancelInfo
+                            + " ignored as the vibration was already cancelled with signal="
+                            + mSignalCancel + ", but immediate flag was updated to "
                             + mSignalCancelImmediate);
                 }
             }
@@ -401,9 +409,9 @@
         if (Build.IS_DEBUGGABLE) {
             expectIsVibrationThread(true);  // Reads VibrationThread variables as well as signals.
         }
-        return (mSignalCancelStatus != mCancelStatus)
-            || (mSignalCancelImmediate && !mCancelledImmediately)
-            || (mSignalVibratorsComplete.size() > 0);
+        return (mSignalCancel != null && mCancelledVibrationEndInfo == null)
+                || (mSignalCancelImmediate && !mCancelledImmediately)
+                || (mSignalVibratorsComplete.size() > 0);
     }
 
     /**
@@ -416,7 +424,7 @@
         }
 
         int[] vibratorsToProcess = null;
-        Vibration.Status doCancelStatus = null;
+        Vibration.EndInfo doCancelInfo = null;
         boolean doCancelImmediate = false;
         // Collect signals to process, but don't keep the lock while processing them.
         synchronized (mLock) {
@@ -426,10 +434,10 @@
                 }
                 // This should only happen once.
                 doCancelImmediate = true;
-                doCancelStatus = mSignalCancelStatus;
+                doCancelInfo = mSignalCancel;
             }
-            if (mSignalCancelStatus != mCancelStatus) {
-                doCancelStatus = mSignalCancelStatus;
+            if ((mSignalCancel != null) && (mCancelledVibrationEndInfo == null)) {
+                doCancelInfo = mSignalCancel;
             }
             if (!doCancelImmediate && mSignalVibratorsComplete.size() > 0) {
                 // Swap out the queue of completions to process.
@@ -443,11 +451,11 @@
         // completion signals that were collected in this call, but we won't process them
         // anyway as all steps are cancelled.
         if (doCancelImmediate) {
-            processCancelImmediately(doCancelStatus);
+            processCancelImmediately(doCancelInfo);
             return;
         }
-        if (doCancelStatus != null) {
-            processCancel(doCancelStatus);
+        if (doCancelInfo != null) {
+            processCancel(doCancelInfo);
         }
         if (vibratorsToProcess != null) {
             processVibratorsComplete(vibratorsToProcess);
@@ -460,12 +468,12 @@
      * <p>This will remove all steps and replace them with respective results of
      * {@link Step#cancel()}.
      */
-    public void processCancel(Vibration.Status cancelStatus) {
+    public void processCancel(Vibration.EndInfo cancelInfo) {
         if (Build.IS_DEBUGGABLE) {
             expectIsVibrationThread(true);
         }
 
-        mCancelStatus = cancelStatus;
+        mCancelledVibrationEndInfo = cancelInfo;
         // Vibrator callbacks should wait until all steps from the queue are properly cancelled
         // and clean up steps are added back to the queue, so they can handle the callback.
         List<Step> cleanUpSteps = new ArrayList<>();
@@ -483,13 +491,13 @@
      *
      * <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order.
      */
-    public void processCancelImmediately(Vibration.Status cancelStatus) {
+    public void processCancelImmediately(Vibration.EndInfo cancelInfo) {
         if (Build.IS_DEBUGGABLE) {
             expectIsVibrationThread(true);
         }
 
         mCancelledImmediately = true;
-        mCancelStatus = cancelStatus;
+        mCancelledVibrationEndInfo = cancelInfo;
         Step step;
         while ((step = pollNext()) != null) {
             step.cancelImmediately();
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index cecc5c0..e824db10 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -76,7 +76,7 @@
          * cleanup tasks, and should not be given new work until {@link #onVibrationThreadReleased}
          * is called.
          */
-        void onVibrationCompleted(long vibrationId, Vibration.Status status);
+        void onVibrationCompleted(long vibrationId, @NonNull Vibration.EndInfo vibrationEndInfo);
 
         /**
          * Tells the manager that the VibrationThread is finished with the previous vibration and
@@ -237,7 +237,8 @@
             try {
                 runCurrentVibrationWithWakeLockAndDeathLink();
             } finally {
-                clientVibrationCompleteIfNotAlready(Vibration.Status.FINISHED_UNEXPECTED);
+                clientVibrationCompleteIfNotAlready(
+                        new Vibration.EndInfo(Vibration.Status.FINISHED_UNEXPECTED));
             }
         } finally {
             mWakeLock.release();
@@ -255,7 +256,8 @@
             vibrationBinderToken.linkToDeath(mExecutingConductor, 0);
         } catch (RemoteException e) {
             Slog.e(TAG, "Error linking vibration to token death", e);
-            clientVibrationCompleteIfNotAlready(Vibration.Status.IGNORED_ERROR_TOKEN);
+            clientVibrationCompleteIfNotAlready(
+                    new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_TOKEN));
             return;
         }
         // Ensure that the unlink always occurs now.
@@ -274,11 +276,11 @@
     // Indicate that the vibration is complete. This can be called multiple times only for
     // convenience of handling error conditions - an error after the client is complete won't
     // affect the status.
-    private void clientVibrationCompleteIfNotAlready(Vibration.Status completedStatus) {
+    private void clientVibrationCompleteIfNotAlready(@NonNull Vibration.EndInfo vibrationEndInfo) {
         if (!mCalledVibrationCompleteCallback) {
             mCalledVibrationCompleteCallback = true;
             mVibratorManagerHooks.onVibrationCompleted(
-                    mExecutingConductor.getVibration().id, completedStatus);
+                    mExecutingConductor.getVibration().id, vibrationEndInfo);
         }
     }
 
@@ -298,12 +300,15 @@
                     mExecutingConductor.runNextStep();
                 }
 
-                Vibration.Status status = mExecutingConductor.calculateVibrationStatus();
-                // This block can only run once due to mCalledVibrationCompleteCallback.
-                if (status != Vibration.Status.RUNNING && !mCalledVibrationCompleteCallback) {
-                    // First time vibration stopped running, start clean-up tasks and notify
-                    // callback immediately.
-                    clientVibrationCompleteIfNotAlready(status);
+                if (!mCalledVibrationCompleteCallback) {
+                    // This block can only run once due to mCalledVibrationCompleteCallback.
+                    Vibration.EndInfo vibrationEndInfo =
+                            mExecutingConductor.calculateVibrationEndInfo();
+                    if (vibrationEndInfo != null) {
+                        // First time vibration stopped running, start clean-up tasks and notify
+                        // callback immediately.
+                        clientVibrationCompleteIfNotAlready(vibrationEndInfo);
+                    }
                 }
             }
         } finally {
diff --git a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
new file mode 100644
index 0000000..f600a29
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator;
+
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+/** Helper class for async write of atoms to {@link FrameworkStatsLog} using a given Handler. */
+public class VibratorFrameworkStatsLogger {
+    private static final String TAG = "VibratorFrameworkStatsLogger";
+
+    // VibrationReported pushed atom needs to be throttled to at most one every 10ms.
+    private static final int VIBRATION_REPORTED_MIN_INTERVAL_MILLIS = 10;
+    // We accumulate events that should take 3s to write and drop excessive metrics.
+    private static final int VIBRATION_REPORTED_MAX_QUEUE_SIZE = 300;
+    // Warning about dropping entries after this amount of atoms were dropped by the throttle.
+    private static final int VIBRATION_REPORTED_WARNING_QUEUE_SIZE = 200;
+
+    private final Object mLock = new Object();
+    private final Handler mHandler;
+    private final long mVibrationReportedLogIntervalMillis;
+    private final long mVibrationReportedQueueMaxSize;
+    private final Runnable mConsumeVibrationStatsQueueRunnable =
+            () -> writeVibrationReportedFromQueue();
+
+    @GuardedBy("mLock")
+    private long mLastVibrationReportedLogUptime;
+    @GuardedBy("mLock")
+    private Queue<VibrationStats.StatsInfo> mVibrationStatsQueue = new ArrayDeque<>();
+
+    VibratorFrameworkStatsLogger(Handler handler) {
+        this(handler, VIBRATION_REPORTED_MIN_INTERVAL_MILLIS, VIBRATION_REPORTED_MAX_QUEUE_SIZE);
+    }
+
+    @VisibleForTesting
+    VibratorFrameworkStatsLogger(Handler handler, int vibrationReportedLogIntervalMillis,
+            int vibrationReportedQueueMaxSize) {
+        mHandler = handler;
+        mVibrationReportedLogIntervalMillis = vibrationReportedLogIntervalMillis;
+        mVibrationReportedQueueMaxSize = vibrationReportedQueueMaxSize;
+    }
+
+    /** Writes {@link FrameworkStatsLog#VIBRATOR_STATE_CHANGED} for state ON. */
+    public void writeVibratorStateOnAsync(int uid, long duration) {
+        mHandler.post(
+                () -> FrameworkStatsLog.write_non_chained(
+                        FrameworkStatsLog.VIBRATOR_STATE_CHANGED, uid, null,
+                        FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON, duration));
+    }
+
+    /** Writes {@link FrameworkStatsLog#VIBRATOR_STATE_CHANGED} for state OFF. */
+    public void writeVibratorStateOffAsync(int uid) {
+        mHandler.post(
+                () -> FrameworkStatsLog.write_non_chained(
+                        FrameworkStatsLog.VIBRATOR_STATE_CHANGED, uid, null,
+                        FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
+                        /* duration= */ 0));
+    }
+
+    /**
+     *  Writes {@link FrameworkStatsLog#VIBRATION_REPORTED} for given vibration.
+     *
+     *  <p>This atom is throttled to be pushed once every 10ms, so this logger can keep a queue of
+     *  {@link VibrationStats.StatsInfo} entries to slowly write to statsd.
+     */
+    public void writeVibrationReportedAsync(VibrationStats.StatsInfo metrics) {
+        boolean needsScheduling;
+        long scheduleDelayMs;
+        int queueSize;
+
+        synchronized (mLock) {
+            queueSize = mVibrationStatsQueue.size();
+            needsScheduling = (queueSize == 0);
+
+            if (queueSize < mVibrationReportedQueueMaxSize) {
+                mVibrationStatsQueue.offer(metrics);
+            }
+
+            long nextLogUptime =
+                    mLastVibrationReportedLogUptime + mVibrationReportedLogIntervalMillis;
+            scheduleDelayMs = Math.max(0, nextLogUptime - SystemClock.uptimeMillis());
+        }
+
+        if ((queueSize + 1) == VIBRATION_REPORTED_WARNING_QUEUE_SIZE) {
+            Slog.w(TAG, " Approaching vibration metrics queue limit, events might be dropped.");
+        }
+
+        if (needsScheduling) {
+            mHandler.postDelayed(mConsumeVibrationStatsQueueRunnable, scheduleDelayMs);
+        }
+    }
+
+    /** Writes next {@link FrameworkStatsLog#VIBRATION_REPORTED} from the queue. */
+    private void writeVibrationReportedFromQueue() {
+        boolean needsScheduling;
+        VibrationStats.StatsInfo stats;
+
+        synchronized (mLock) {
+            stats = mVibrationStatsQueue.poll();
+            needsScheduling = !mVibrationStatsQueue.isEmpty();
+
+            if (stats != null) {
+                mLastVibrationReportedLogUptime = SystemClock.uptimeMillis();
+            }
+        }
+
+        if (stats == null) {
+            Slog.w(TAG, "Unexpected vibration metric flush with empty queue. Ignoring.");
+        } else {
+            stats.writeVibrationReported();
+        }
+
+        if (needsScheduling) {
+            mHandler.postDelayed(mConsumeVibrationStatsQueueRunnable,
+                    mVibrationReportedLogIntervalMillis);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 5ac2f4f..d1cde60 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -129,6 +129,7 @@
     private final Context mContext;
     private final PowerManager.WakeLock mWakeLock;
     private final IBatteryStats mBatteryStatsService;
+    private final VibratorFrameworkStatsLogger mFrameworkStatsLogger;
     private final Handler mHandler;
     private final VibrationThread mVibrationThread;
     private final AppOpsManager mAppOps;
@@ -163,10 +164,12 @@
                     // When the system is entering a non-interactive state, we want to cancel
                     // vibrations in case a misbehaving app has abandoned them.
                     if (shouldCancelOnScreenOffLocked(mNextVibration)) {
-                        clearNextVibrationLocked(Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+                        clearNextVibrationLocked(
+                                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF));
                     }
                     if (shouldCancelOnScreenOffLocked(mCurrentVibration)) {
-                        mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_SCREEN_OFF,
+                        mCurrentVibration.notifyCancelled(
+                                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
                                 /* immediate= */ false);
                     }
                 }
@@ -207,6 +210,7 @@
         mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit);
 
         mBatteryStatsService = injector.getBatteryStatsService();
+        mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);
 
         mAppOps = mContext.getSystemService(AppOpsManager.class);
 
@@ -383,8 +387,9 @@
      * An internal-only version of vibrate that allows the caller access to the {@link Vibration}.
      * The Vibration is only returned if it is ongoing after this method returns.
      */
+    @VisibleForTesting
     @Nullable
-    private Vibration vibrateInternal(int uid, String opPkg, @NonNull CombinedVibration effect,
+    Vibration vibrateInternal(int uid, String opPkg, @NonNull CombinedVibration effect,
             @Nullable VibrationAttributes attrs, String reason, IBinder token) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
         try {
@@ -399,6 +404,7 @@
                 return null;
             }
             attrs = fixupVibrationAttributes(attrs, effect);
+            // Create Vibration.Stats as close to the received request as possible, for tracking.
             Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs,
                     uid, opPkg, reason);
             fillVibrationFallbacks(vib, effect);
@@ -413,32 +419,56 @@
                 if (DEBUG) {
                     Slog.d(TAG, "Starting vibrate for vibration  " + vib.id);
                 }
-                Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
-                        vib.uid, vib.opPkg, vib.attrs);
+                int ignoredByUid = -1;
+                int ignoredByUsage = -1;
+                Vibration.Status status = null;
 
-                if (ignoreStatus == null) {
-                    ignoreStatus = shouldIgnoreVibrationForOngoingLocked(vib);
+                // Check if user settings or DnD is set to ignore this vibration.
+                status = shouldIgnoreVibrationLocked(vib.uid, vib.opPkg, vib.attrs);
+
+                // Check if something has external control, assume it's more important.
+                if ((status == null) && (mCurrentExternalVibration != null)) {
+                    status = Vibration.Status.IGNORED_FOR_EXTERNAL;
+                    ignoredByUid = mCurrentExternalVibration.externalVibration.getUid();
+                    ignoredByUsage = mCurrentExternalVibration.externalVibration
+                            .getVibrationAttributes().getUsage();
                 }
 
-                if (ignoreStatus != null) {
-                    endVibrationLocked(vib, ignoreStatus);
-                    return vib;
-                }
-
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    if (mCurrentVibration != null) {
-                        mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED,
-                                /* immediate= */ false);
+                // Check if ongoing vibration is more important than this vibration.
+                if (status == null) {
+                    status = shouldIgnoreVibrationForOngoingLocked(vib);
+                    if (status != null) {
+                        ignoredByUid = mCurrentVibration.getVibration().uid;
+                        ignoredByUsage = mCurrentVibration.getVibration().attrs.getUsage();
                     }
-                    Vibration.Status status = startVibrationLocked(vib);
-                    if (status != Vibration.Status.RUNNING) {
-                        endVibrationLocked(vib, status);
-                    }
-                    return vib;
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
                 }
+
+                // If not ignored so far then try to start this vibration.
+                if (status == null) {
+                    final long ident = Binder.clearCallingIdentity();
+                    try {
+                        if (mCurrentVibration != null) {
+                            vib.stats().reportInterruptedAnotherVibration(
+                                    mCurrentVibration.getVibration().attrs.getUsage());
+                            mCurrentVibration.notifyCancelled(
+                                    new Vibration.EndInfo(
+                                            Vibration.Status.CANCELLED_SUPERSEDED, vib.uid,
+                                            vib.attrs.getUsage()),
+                                    /* immediate= */ false);
+                        }
+                        status = startVibrationLocked(vib);
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
+                    }
+                }
+
+                // Ignored or failed to start the vibration, end it and report metrics right away.
+                if (status != Vibration.Status.RUNNING) {
+                    endVibrationLocked(vib,
+                            new Vibration.EndInfo(status, ignoredByUid, ignoredByUsage),
+                            /* shouldWriteStats= */ true);
+                }
+                return vib;
             }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -457,26 +487,28 @@
                 if (DEBUG) {
                     Slog.d(TAG, "Canceling vibration");
                 }
+                Vibration.EndInfo cancelledByUserInfo =
+                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER);
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     if (mNextVibration != null
                             && shouldCancelVibration(mNextVibration.getVibration(),
                             usageFilter, token)) {
-                        clearNextVibrationLocked(Vibration.Status.CANCELLED_BY_USER);
+                        clearNextVibrationLocked(cancelledByUserInfo);
                     }
                     if (mCurrentVibration != null
                             && shouldCancelVibration(mCurrentVibration.getVibration(),
                             usageFilter, token)) {
-                        mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_USER,
-                                /* immediate= */false);
+                        mCurrentVibration.notifyCancelled(
+                                cancelledByUserInfo, /* immediate= */false);
                     }
                     if (mCurrentExternalVibration != null
                             && shouldCancelVibration(
                             mCurrentExternalVibration.externalVibration.getVibrationAttributes(),
                             usageFilter)) {
-                        mCurrentExternalVibration.externalVibration.mute();
-                        endExternalVibrateLocked(Vibration.Status.CANCELLED_BY_USER,
-                                /* continueExternalControl= */ false);
+                        mCurrentExternalVibration.mute();
+                        endExternalVibrateLocked(
+                                cancelledByUserInfo, /* continueExternalControl= */ false);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -604,15 +636,17 @@
                     Slog.d(TAG, "Canceling vibration because settings changed: "
                             + (inputDevicesChanged ? "input devices changed" : ignoreStatus));
                 }
-                mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE,
+                mCurrentVibration.notifyCancelled(
+                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
                         /* immediate= */ false);
             }
         }
     }
 
-    private void setExternalControl(boolean externalControl) {
+    private void setExternalControl(boolean externalControl, VibrationStats vibrationStats) {
         for (int i = 0; i < mVibrators.size(); i++) {
             mVibrators.valueAt(i).setExternalControl(externalControl);
+            vibrationStats.reportSetExternalControl();
         }
     }
 
@@ -654,7 +688,9 @@
             }
             // If there's already a vibration queued (waiting for the previous one to finish
             // cancelling), end it cleanly and replace it with the new one.
-            clearNextVibrationLocked(Vibration.Status.IGNORED_SUPERSEDED);
+            clearNextVibrationLocked(
+                    new Vibration.EndInfo(Vibration.Status.IGNORED_SUPERSEDED,
+                            vib.uid, vib.attrs.getUsage()));
             mNextVibration = conductor;
             return Vibration.Status.RUNNING;
         } finally {
@@ -671,6 +707,7 @@
             switch (mode) {
                 case AppOpsManager.MODE_ALLOWED:
                     Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+                    // Make sure mCurrentVibration is set while triggering the VibrationThread.
                     mCurrentVibration = conductor;
                     if (!mVibrationThread.runVibrationOnVibrationThread(mCurrentVibration)) {
                         // Shouldn't happen. The method call already logs a wtf.
@@ -690,18 +727,26 @@
     }
 
     @GuardedBy("mLock")
-    private void endVibrationLocked(Vibration vib, Vibration.Status status) {
-        vib.end(status);
-        logVibrationStatus(vib.uid, vib.attrs, status);
+    private void endVibrationLocked(Vibration vib, Vibration.EndInfo vibrationEndInfo,
+            boolean shouldWriteStats) {
+        vib.end(vibrationEndInfo);
+        logVibrationStatus(vib.uid, vib.attrs, vibrationEndInfo.status);
         mVibratorManagerRecords.record(vib);
+        if (shouldWriteStats) {
+            mFrameworkStatsLogger.writeVibrationReportedAsync(
+                    vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
+        }
     }
 
     @GuardedBy("mLock")
-    private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) {
-        vib.end(status);
+    private void endVibrationAndWriteStatsLocked(ExternalVibrationHolder vib,
+            Vibration.EndInfo vibrationEndInfo) {
+        vib.end(vibrationEndInfo);
         logVibrationStatus(vib.externalVibration.getUid(),
-                vib.externalVibration.getVibrationAttributes(), status);
+                vib.externalVibration.getVibrationAttributes(), vibrationEndInfo.status);
         mVibratorManagerRecords.record(vib);
+        mFrameworkStatsLogger.writeVibrationReportedAsync(
+                vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
     }
 
     private void logVibrationStatus(int uid, VibrationAttributes attrs, Vibration.Status status) {
@@ -744,15 +789,17 @@
     }
 
     @GuardedBy("mLock")
-    private void reportFinishedVibrationLocked(Vibration.Status status) {
+    private void reportFinishedVibrationLocked(Vibration.EndInfo vibrationEndInfo) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
         Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
         try {
             Vibration vib = mCurrentVibration.getVibration();
             if (DEBUG) {
-                Slog.d(TAG, "Reporting vibration " + vib.id + " finished with status " + status);
+                Slog.d(TAG, "Reporting vibration " + vib.id + " finished with " + vibrationEndInfo);
             }
-            endVibrationLocked(vib, status);
+            // DO NOT write metrics at this point, wait for the VibrationThread to report the
+            // vibration was released, after all cleanup. The metrics will be reported then.
+            endVibrationLocked(vib, vibrationEndInfo, /* shouldWriteStats= */ false);
             finishAppOpModeLocked(vib.uid, vib.opPkg);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -791,11 +838,6 @@
     @GuardedBy("mLock")
     @Nullable
     private Vibration.Status shouldIgnoreVibrationForOngoingLocked(Vibration vib) {
-        if (mCurrentExternalVibration != null) {
-            // If something has external control of the vibrator, assume that it's more important.
-            return Vibration.Status.IGNORED_FOR_EXTERNAL;
-        }
-
         if (mCurrentVibration == null || vib.isRepeating()) {
             // Incoming repeating vibrations always take precedence over ongoing vibrations.
             return null;
@@ -1122,7 +1164,7 @@
         }
         Vibration vib = conductor.getVibration();
         return mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                vib.uid, vib.opPkg, vib.attrs.getUsage(), vib.startUptimeMillis);
+                vib.uid, vib.opPkg, vib.attrs.getUsage(), vib.stats().getCreateUptimeMillis());
     }
 
     @GuardedBy("mLock")
@@ -1158,6 +1200,10 @@
                     BatteryStats.SERVICE_NAME));
         }
 
+        VibratorFrameworkStatsLogger getFrameworkStatsLogger(Handler handler) {
+            return new VibratorFrameworkStatsLogger(handler);
+        }
+
         VibratorController createVibratorController(int vibratorId,
                 VibratorController.OnVibrationCompleteListener listener) {
             return new VibratorController(vibratorId, listener);
@@ -1197,6 +1243,10 @@
         public void noteVibratorOn(int uid, long duration) {
             try {
                 if (duration <= 0) {
+                    // Tried to turn vibrator ON and got:
+                    // duration == 0: Unsupported effect/method or zero-amplitude segment.
+                    // duration < 0: Unexpected error triggering the vibrator.
+                    // Skip battery stats and atom metric for VibratorStageChanged to ON.
                     return;
                 }
                 if (duration == Long.MAX_VALUE) {
@@ -1205,10 +1255,9 @@
                     duration = BATTERY_STATS_REPEATING_VIBRATION_DURATION;
                 }
                 mBatteryStatsService.noteVibratorOn(uid, duration);
-                FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
-                        uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON,
-                        duration);
+                mFrameworkStatsLogger.writeVibratorStateOnAsync(uid, duration);
             } catch (RemoteException e) {
+                Slog.e(TAG, "Error logging VibratorStateChanged to ON", e);
             }
         }
 
@@ -1216,22 +1265,21 @@
         public void noteVibratorOff(int uid) {
             try {
                 mBatteryStatsService.noteVibratorOff(uid);
-                FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
-                        uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
-                        /* duration= */ 0);
+                mFrameworkStatsLogger.writeVibratorStateOffAsync(uid);
             } catch (RemoteException e) {
+                Slog.e(TAG, "Error logging VibratorStateChanged to OFF", e);
             }
         }
 
         @Override
-        public void onVibrationCompleted(long vibrationId, Vibration.Status status) {
+        public void onVibrationCompleted(long vibrationId, Vibration.EndInfo vibrationEndInfo) {
             if (DEBUG) {
-                Slog.d(TAG, "Vibration " + vibrationId + " finished with status " + status);
+                Slog.d(TAG, "Vibration " + vibrationId + " finished with " + vibrationEndInfo);
             }
             synchronized (mLock) {
                 if (mCurrentVibration != null
                         && mCurrentVibration.getVibration().id == vibrationId) {
-                    reportFinishedVibrationLocked(status);
+                    reportFinishedVibrationLocked(vibrationEndInfo);
                 }
             }
         }
@@ -1251,13 +1299,21 @@
                             "VibrationId mismatch on release. expected=%d, released=%d",
                             mCurrentVibration.getVibration().id, vibrationId));
                 }
-                mCurrentVibration = null;
+                if (mCurrentVibration != null) {
+                    // This is when we consider the current vibration complete, so report metrics.
+                    mFrameworkStatsLogger.writeVibrationReportedAsync(
+                            mCurrentVibration.getVibration().getStatsInfo(
+                                    /* completionUptimeMillis= */ SystemClock.uptimeMillis()));
+                    mCurrentVibration = null;
+                }
                 if (mNextVibration != null) {
                     VibrationStepConductor nextConductor = mNextVibration;
                     mNextVibration = null;
                     Vibration.Status status = startVibrationOnThreadLocked(nextConductor);
                     if (status != Vibration.Status.RUNNING) {
-                        endVibrationLocked(nextConductor.getVibration(), status);
+                        // Failed to start the vibration, end it and report metrics right away.
+                        endVibrationLocked(nextConductor.getVibration(),
+                                new Vibration.EndInfo(status), /* shouldWriteStats= */ true);
                     }
                 }
             }
@@ -1325,31 +1381,48 @@
     private final class ExternalVibrationHolder implements IBinder.DeathRecipient {
 
         public final ExternalVibration externalVibration;
+        public final VibrationStats stats = new VibrationStats();
         public int scale;
 
-        private final long mStartUptimeMillis;
-        private final long mStartTimeDebug;
-
-        private long mEndUptimeMillis;
-        private long mEndTimeDebug;
         private Vibration.Status mStatus;
 
         private ExternalVibrationHolder(ExternalVibration externalVibration) {
             this.externalVibration = externalVibration;
             this.scale = IExternalVibratorService.SCALE_NONE;
-            mStartUptimeMillis = SystemClock.uptimeMillis();
-            mStartTimeDebug = System.currentTimeMillis();
             mStatus = Vibration.Status.RUNNING;
         }
 
-        public void end(Vibration.Status status) {
+        public void mute() {
+            externalVibration.mute();
+        }
+
+        public void linkToDeath() {
+            externalVibration.linkToDeath(this);
+        }
+
+        public void unlinkToDeath() {
+            externalVibration.unlinkToDeath(this);
+        }
+
+        public boolean isHoldingSameVibration(ExternalVibration externalVibration) {
+            return this.externalVibration.equals(externalVibration);
+        }
+
+        public void end(Vibration.EndInfo info) {
             if (mStatus != Vibration.Status.RUNNING) {
-                // Vibration already ended, keep first ending status set and ignore this one.
+                // Already ended, ignore this call
                 return;
             }
-            mStatus = status;
-            mEndUptimeMillis = SystemClock.uptimeMillis();
-            mEndTimeDebug = System.currentTimeMillis();
+            mStatus = info.status;
+            stats.reportEnded(info.endedByUid, info.endedByUsage);
+
+            if (stats.hasStarted()) {
+                // External vibration doesn't have feedback from total time the vibrator was playing
+                // with non-zero amplitude, so we use the duration between start and end times of
+                // the vibration as the time the vibrator was ON, since the haptic channels are
+                // open for this duration and can receive vibration waveform data.
+                stats.reportVibratorOn(stats.getEndUptimeMillis() - stats.getStartUptimeMillis());
+            }
         }
 
         public void binderDied() {
@@ -1358,19 +1431,26 @@
                     if (DEBUG) {
                         Slog.d(TAG, "External vibration finished because binder died");
                     }
-                    endExternalVibrateLocked(Vibration.Status.CANCELLED_BINDER_DIED,
+                    endExternalVibrateLocked(
+                            new Vibration.EndInfo(Vibration.Status.CANCELLED_BINDER_DIED),
                             /* continueExternalControl= */ false);
                 }
             }
         }
 
         public Vibration.DebugInfo getDebugInfo() {
-            long durationMs = mEndUptimeMillis == 0 ? -1 : mEndUptimeMillis - mStartUptimeMillis;
             return new Vibration.DebugInfo(
-                    mStartTimeDebug, mEndTimeDebug, durationMs,
-                    /* effect= */ null, /* originalEffect= */ null, scale,
+                    mStatus, stats, /* effect= */ null, /* originalEffect= */ null, scale,
                     externalVibration.getVibrationAttributes(), externalVibration.getUid(),
-                    externalVibration.getPackage(), /* reason= */ null, mStatus);
+                    externalVibration.getPackage(), /* reason= */ null);
+        }
+
+        public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
+            return new VibrationStats.StatsInfo(
+                    externalVibration.getUid(),
+                    FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
+                    externalVibration.getVibrationAttributes().getUsage(), mStatus, stats,
+                    completionUptimeMillis);
         }
     }
 
@@ -1500,9 +1580,11 @@
 
     /** Clears mNextVibration if set, ending it cleanly */
     @GuardedBy("mLock")
-    private void clearNextVibrationLocked(Vibration.Status endStatus) {
+    private void clearNextVibrationLocked(Vibration.EndInfo vibrationEndInfo) {
         if (mNextVibration != null) {
-            endVibrationLocked(mNextVibration.getVibration(), endStatus);
+            // Clearing next vibration before playing it, end it and report metrics right away.
+            endVibrationLocked(mNextVibration.getVibration(), vibrationEndInfo,
+                    /* shouldWriteStats= */ true);
             mNextVibration = null;
         }
     }
@@ -1510,25 +1592,25 @@
     /**
      * Ends the external vibration, and clears related service state.
      *
-     * @param status the status to end the associated Vibration with
+     * @param vibrationEndInfo the status and related info to end the associated Vibration with
      * @param continueExternalControl indicates whether external control will continue. If not, the
      *                                HAL will have external control turned off.
      */
     @GuardedBy("mLock")
-    private void endExternalVibrateLocked(Vibration.Status status,
+    private void endExternalVibrateLocked(Vibration.EndInfo vibrationEndInfo,
             boolean continueExternalControl) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "endExternalVibrateLocked");
         try {
             if (mCurrentExternalVibration == null) {
                 return;
             }
-            endVibrationLocked(mCurrentExternalVibration, status);
-            mCurrentExternalVibration.externalVibration.unlinkToDeath(
-                    mCurrentExternalVibration);
-            mCurrentExternalVibration = null;
+            mCurrentExternalVibration.unlinkToDeath();
             if (!continueExternalControl) {
-                setExternalControl(false);
+                setExternalControl(false, mCurrentExternalVibration.stats);
             }
+            // The external control was turned off, end it and report metrics right away.
+            endVibrationAndWriteStatsLocked(mCurrentExternalVibration, vibrationEndInfo);
+            mCurrentExternalVibration = null;
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
@@ -1552,6 +1634,8 @@
                 return IExternalVibratorService.SCALE_MUTE;
             }
 
+            // Create Vibration.Stats as close to the received request as possible, for tracking.
+            ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
             VibrationAttributes attrs = fixupVibrationAttributes(vib.getVibrationAttributes(),
                     /* effect= */ null);
             if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
@@ -1562,18 +1646,17 @@
 
             boolean alreadyUnderExternalControl = false;
             boolean waitForCompletion = false;
-            int scale;
             synchronized (mLock) {
                 Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
                         vib.getUid(), vib.getPackage(), attrs);
                 if (ignoreStatus != null) {
-                    ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
                     vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
-                    endVibrationLocked(vibHolder, ignoreStatus);
+                    // Failed to start the vibration, end it and report metrics right away.
+                    endVibrationAndWriteStatsLocked(vibHolder, new Vibration.EndInfo(ignoreStatus));
                     return vibHolder.scale;
                 }
                 if (mCurrentExternalVibration != null
-                        && mCurrentExternalVibration.externalVibration.equals(vib)) {
+                        && mCurrentExternalVibration.isHoldingSameVibration(vib)) {
                     // We are already playing this external vibration, so we can return the same
                     // scale calculated in the previous call to this method.
                     return mCurrentExternalVibration.scale;
@@ -1582,8 +1665,14 @@
                     // If we're not under external control right now, then cancel any normal
                     // vibration that may be playing and ready the vibrator for external control.
                     if (mCurrentVibration != null) {
-                        clearNextVibrationLocked(Vibration.Status.IGNORED_FOR_EXTERNAL);
-                        mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED,
+                        vibHolder.stats.reportInterruptedAnotherVibration(
+                                mCurrentVibration.getVibration().attrs.getUsage());
+                        clearNextVibrationLocked(
+                                new Vibration.EndInfo(Vibration.Status.IGNORED_FOR_EXTERNAL,
+                                        vib.getUid(), attrs.getUsage()));
+                        mCurrentVibration.notifyCancelled(
+                                new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
+                                        vib.getUid(), attrs.getUsage()),
                                 /* immediate= */ true);
                         waitForCompletion = true;
                     }
@@ -1597,22 +1686,27 @@
                     // Note that this doesn't support multiple concurrent external controls, as we
                     // would need to mute the old one still if it came from a different controller.
                     alreadyUnderExternalControl = true;
-                    mCurrentExternalVibration.externalVibration.mute();
-                    endExternalVibrateLocked(Vibration.Status.CANCELLED_SUPERSEDED,
+                    mCurrentExternalVibration.mute();
+                    vibHolder.stats.reportInterruptedAnotherVibration(
+                            mCurrentExternalVibration.externalVibration
+                                    .getVibrationAttributes().getUsage());
+                    endExternalVibrateLocked(
+                            new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
+                                    vib.getUid(), attrs.getUsage()),
                             /* continueExternalControl= */ true);
                 }
-                mCurrentExternalVibration = new ExternalVibrationHolder(vib);
-                vib.linkToDeath(mCurrentExternalVibration);
-                mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale(
-                        attrs.getUsage());
-                scale = mCurrentExternalVibration.scale;
+                mCurrentExternalVibration = vibHolder;
+                vibHolder.linkToDeath();
+                vibHolder.scale = mVibrationScaler.getExternalVibrationScale(attrs.getUsage());
             }
 
             if (waitForCompletion) {
                 if (!mVibrationThread.waitForThreadIdle(VIBRATION_CANCEL_WAIT_MILLIS)) {
                     Slog.e(TAG, "Timed out waiting for vibration to cancel");
                     synchronized (mLock) {
-                        endExternalVibrateLocked(Vibration.Status.IGNORED_ERROR_CANCELLING,
+                        // Trigger endExternalVibrateLocked to unlink to death recipient.
+                        endExternalVibrateLocked(
+                                new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_CANCELLING),
                                 /* continueExternalControl= */ false);
                     }
                     return IExternalVibratorService.SCALE_MUTE;
@@ -1622,23 +1716,27 @@
                 if (DEBUG) {
                     Slog.d(TAG, "Vibrator going under external control.");
                 }
-                setExternalControl(true);
+                setExternalControl(true, vibHolder.stats);
             }
             if (DEBUG) {
                 Slog.e(TAG, "Playing external vibration: " + vib);
             }
-            return scale;
+            // Vibrator will start receiving data from external channels after this point.
+            // Report current time as the vibration start time, for debugging.
+            vibHolder.stats.reportStarted();
+            return vibHolder.scale;
         }
 
         @Override
         public void onExternalVibrationStop(ExternalVibration vib) {
             synchronized (mLock) {
                 if (mCurrentExternalVibration != null
-                        && mCurrentExternalVibration.externalVibration.equals(vib)) {
+                        && mCurrentExternalVibration.isHoldingSameVibration(vib)) {
                     if (DEBUG) {
                         Slog.e(TAG, "Stopping external vibration" + vib);
                     }
-                    endExternalVibrateLocked(Vibration.Status.FINISHED,
+                    endExternalVibrateLocked(
+                            new Vibration.EndInfo(Vibration.Status.FINISHED),
                             /* continueExternalControl= */ false);
                 }
             }
@@ -1746,6 +1844,8 @@
                     attrs, commonOptions.description, deathBinder);
             if (vib != null && !commonOptions.background) {
                 try {
+                    // Waits for the client vibration to finish, but the VibrationThread may still
+                    // do cleanup after this.
                     vib.waitForEnd();
                 } catch (InterruptedException e) {
                 }
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index a21919c..f8cbd8b3d 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -1,5 +1,6 @@
 package com.android.server.wm;
 
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.app.ActivityManager.processStateAmToProto;
@@ -69,6 +70,7 @@
 import static com.android.internal.util.FrameworkStatsLog.CAMERA_COMPAT_CONTROL_EVENT_REPORTED__EVENT__CLICKED_REVERT_TREATMENT;
 import static com.android.server.am.MemoryStatUtil.MemoryStat;
 import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
+import static com.android.server.am.ProcessList.INVALID_ADJ;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_METRICS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -274,6 +276,10 @@
         final boolean mProcessRunning;
         /** whether the process of the launching activity didn't have any active activity. */
         final boolean mProcessSwitch;
+        /** The process state of the launching activity prior to the launch */
+        final int mProcessState;
+        /** The oom adj score of the launching activity prior to the launch */
+        final int mProcessOomAdj;
         /** Whether the last launched activity has reported drawn. */
         boolean mIsDrawn;
         /** The latest activity to have been launched. */
@@ -309,8 +315,8 @@
         @Nullable
         static TransitionInfo create(@NonNull ActivityRecord r,
                 @NonNull LaunchingState launchingState, @Nullable ActivityOptions options,
-                boolean processRunning, boolean processSwitch, boolean newActivityCreated,
-                int startResult) {
+                boolean processRunning, boolean processSwitch, int processState, int processOomAdj,
+                boolean newActivityCreated, int startResult) {
             if (startResult != START_SUCCESS && startResult != START_TASK_TO_FRONT) {
                 return null;
             }
@@ -325,18 +331,20 @@
                 transitionType = TYPE_TRANSITION_COLD_LAUNCH;
             }
             return new TransitionInfo(r, launchingState, options, transitionType, processRunning,
-                    processSwitch);
+                    processSwitch, processState, processOomAdj);
         }
 
         /** Use {@link TransitionInfo#create} instead to ensure the transition type is valid. */
         private TransitionInfo(ActivityRecord r, LaunchingState launchingState,
                 ActivityOptions options, int transitionType, boolean processRunning,
-                boolean processSwitch) {
+                boolean processSwitch, int processState, int processOomAdj) {
             mLaunchingState = launchingState;
             mTransitionStartTimeNs = launchingState.mCurrentTransitionStartTimeNs;
             mTransitionType = transitionType;
             mProcessRunning = processRunning;
             mProcessSwitch = processSwitch;
+            mProcessState = processState;
+            mProcessOomAdj = processOomAdj;
             mTransitionDeviceUptimeMs = launchingState.mCurrentUpTimeMs;
             setLatestLaunchedActivity(r);
             // The launching state can be reused by consecutive launch. Its original association
@@ -640,12 +648,23 @@
         // interesting.
         final boolean processSwitch = !processRunning
                 || !processRecord.hasStartedActivity(launchedActivity);
+        final int processState;
+        final int processOomAdj;
+        if (processRunning) {
+            processState = processRecord.getCurrentProcState();
+            processOomAdj = processRecord.getCurrentAdj();
+        } else {
+            processState = PROCESS_STATE_NONEXISTENT;
+            processOomAdj = INVALID_ADJ;
+        }
 
         final TransitionInfo info = launchingState.mAssociatedTransitionInfo;
         if (DEBUG_METRICS) {
             Slog.i(TAG, "notifyActivityLaunched" + " resultCode=" + resultCode
                     + " launchedActivity=" + launchedActivity + " processRunning=" + processRunning
                     + " processSwitch=" + processSwitch
+                    + " processState=" + processState
+                    + " processOomAdj=" + processOomAdj
                     + " newActivityCreated=" + newActivityCreated + " info=" + info);
         }
 
@@ -681,7 +700,8 @@
         }
 
         final TransitionInfo newInfo = TransitionInfo.create(launchedActivity, launchingState,
-                options, processRunning, processSwitch, newActivityCreated, resultCode);
+                options, processRunning, processSwitch, processState, processOomAdj,
+                newActivityCreated, resultCode);
         if (newInfo == null) {
             abort(launchingState, "unrecognized launch");
             return;
@@ -996,8 +1016,11 @@
             final long timestamp = info.mTransitionStartTimeNs;
             final long uptime = info.mTransitionDeviceUptimeMs;
             final int transitionDelay = info.mCurrentTransitionDelayMs;
+            final int processState = info.mProcessState;
+            final int processOomAdj = info.mProcessOomAdj;
             mLoggerHandler.post(() -> logAppTransition(
-                    timestamp, uptime, transitionDelay, infoSnapshot, isHibernating));
+                    timestamp, uptime, transitionDelay, infoSnapshot, isHibernating,
+                    processState, processOomAdj));
         }
         mLoggerHandler.post(() -> logAppDisplayed(infoSnapshot));
         if (info.mPendingFullyDrawn != null) {
@@ -1009,7 +1032,8 @@
 
     // This gets called on another thread without holding the activity manager lock.
     private void logAppTransition(long transitionStartTimeNs, long transitionDeviceUptimeMs,
-            int currentTransitionDelayMs, TransitionInfoSnapshot info, boolean isHibernating) {
+            int currentTransitionDelayMs, TransitionInfoSnapshot info, boolean isHibernating,
+            int processState, int processOomAdj) {
         final LogMaker builder = new LogMaker(APP_TRANSITION);
         builder.setPackageName(info.packageName);
         builder.setType(info.type);
@@ -1075,7 +1099,9 @@
                 isIncremental,
                 isLoading,
                 info.launchedActivityName.hashCode(),
-                TimeUnit.NANOSECONDS.toMillis(transitionStartTimeNs));
+                TimeUnit.NANOSECONDS.toMillis(transitionStartTimeNs),
+                processState,
+                processOomAdj);
 
         if (DEBUG_METRICS) {
             Slog.i(TAG, String.format("APP_START_OCCURRED(%s, %s, %s, %s, %s)",
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2bb1793..f8f94f6 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -78,6 +78,11 @@
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN;
 import static android.content.pm.ActivityInfo.PERSIST_ACROSS_REBOOTS;
 import static android.content.pm.ActivityInfo.PERSIST_ROOT_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
@@ -307,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;
@@ -352,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;
 
@@ -654,6 +659,9 @@
 
     boolean mUseTransferredAnimation;
 
+    /** Whether we need to setup the animation to animate only within the letterbox. */
+    private boolean mNeedsLetterboxedAnimation;
+
     /**
      * @see #currentLaunchCanTurnScreenOn()
      */
@@ -1561,7 +1569,7 @@
         if (task == mLastParentBeforePip && task != null) {
             // Notify the TaskFragmentOrganizer that the activity is reparented back from pip.
             mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController
-                    .onActivityReparentToTask(this);
+                    .onActivityReparentedToTask(this);
             // Activity's reparented back from pip, clear the links once established
             clearLastParentBeforePip();
         }
@@ -1757,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) {
@@ -3233,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;
     }
@@ -5320,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.
      *
@@ -5635,7 +5659,7 @@
         ProtoLog.v(WM_DEBUG_ADD_REMOVE, "notifyAppResumed: wasStopped=%b %s",
                 wasStopped, this);
         mAppStopped = false;
-        // Allow the window to turn the screen on once the app is resumed again.
+        // Allow the window to turn the screen on once the app is started and resumed.
         if (mAtmService.getActivityStartController().isInExecution()) {
             setCurrentLaunchCanTurnScreenOn(true);
         }
@@ -5863,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);
             }
@@ -7247,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;
@@ -7284,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.
@@ -7306,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);
         }
@@ -7419,6 +7464,12 @@
             mAnimationBoundsLayer = null;
         }
 
+        mNeedsAnimationBoundsLayer = false;
+        if (mNeedsLetterboxedAnimation) {
+            mNeedsLetterboxedAnimation = false;
+            updateLetterboxSurface(findMainWindow(), t);
+        }
+
         if (mAnimatingActivityRegistry != null) {
             mAnimatingActivityRegistry.notifyFinished(this);
         }
@@ -7431,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");
@@ -8751,15 +8801,35 @@
     /**
      * Returns the min aspect ratio of this activity.
      */
-    private float getMinAspectRatio() {
-        return info.getMinAspectRatio(getRequestedOrientation());
+    float getMinAspectRatio() {
+        if (info.applicationInfo == null || !info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO) || (
+                info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
+                        && !ActivityInfo.isFixedOrientationPortrait(getRequestedOrientation()))) {
+            return info.getMinAspectRatio();
+        }
+
+        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN)) {
+            return Math.max(mLetterboxUiController.getSplitScreenAspectRatio(),
+                    info.getMinAspectRatio());
+        }
+
+        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE)) {
+            return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+                    info.getMinAspectRatio());
+        }
+
+        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM)) {
+            return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
+                    info.getMinAspectRatio());
+        }
+        return info.getMinAspectRatio();
     }
 
     /**
      * Returns true if the activity has maximum or minimum aspect ratio.
      */
     private boolean hasFixedAspectRatio() {
-        return info.hasFixedAspectRatio(getRequestedOrientation());
+        return info.getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
     }
 
     /**
@@ -9614,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/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 77d6097..8f18064 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -94,6 +94,7 @@
 
     boolean mCheckedForSetup = false;
 
+    /** Whether an {@link ActivityStarter} is currently executing (starting an Activity). */
     private boolean mInExecution = false;
 
     /**
@@ -129,7 +130,7 @@
         return mFactory.obtain().setIntent(intent).setReason(reason);
     }
 
-    void onExecutionStarted(ActivityStarter starter) {
+    void onExecutionStarted() {
         mInExecution = true;
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index abedd96..619d693 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1267,7 +1267,7 @@
     }
 
     private void onExecutionStarted() {
-        mController.onExecutionStarted(this);
+        mController.onExecutionStarted();
     }
 
     private boolean isHomeApp(int uid, @Nullable String packageName) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 87a4fe9..4003eeb 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -311,7 +311,7 @@
 
     /**
      * The duration to keep a process in animating state (top scheduling group) when the
-     * wakefulness is changing from awake to doze or sleep.
+     * wakefulness is dozing (unlocking) or changing from awake to doze or sleep (locking).
      */
     private static final long DOZE_ANIMATING_STATE_RETAIN_TIME_MS = 2000;
 
@@ -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);
@@ -2927,12 +2928,14 @@
             mDemoteTopAppReasons &= ~DEMOTE_TOP_REASON_DURING_UNLOCKING;
             final WindowState notificationShade = mRootWindowContainer.getDefaultDisplay()
                     .getDisplayPolicy().getNotificationShade();
-            proc = notificationShade != null
-                    ? mProcessMap.getProcess(notificationShade.mSession.mPid) : null;
+            proc = notificationShade != null ? notificationShade.getProcess() : null;
         }
-        if (proc == null) {
-            return;
-        }
+        setProcessAnimatingWhileDozing(proc);
+    }
+
+    // The caller MUST NOT hold the global lock because it calls AM method directly.
+    void setProcessAnimatingWhileDozing(WindowProcessController proc) {
+        if (proc == null) return;
         // Set to activity manager directly to make sure the state can be seen by the subsequent
         // update of scheduling group.
         proc.setRunningAnimationUnsafe();
@@ -3560,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");
                 }
             }
         };
@@ -4621,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 be995a8..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);
@@ -2654,12 +2658,12 @@
 
         @Override
         public void accept(ActivityRecord r) {
-            if (r.finishing) {
-                return;
-            }
             if (r.mLaunchCookie != null) {
                 mInfo.addLaunchCookie(r.mLaunchCookie);
             }
+            if (r.finishing) {
+                return;
+            }
             mInfo.numActivities++;
             mInfo.baseActivity = r.mActivityComponent;
             if (mTopRunning == null) {
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 55d6b2f..c940a65 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -375,9 +375,6 @@
         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(),
@@ -416,7 +413,7 @@
     }
 
     void freeze() {
-        final boolean keyguardGoingAway = mNextAppTransitionRequests.contains(
+        final boolean keyguardGoingAwayCancelled = mNextAppTransitionRequests.contains(
                 TRANSIT_KEYGUARD_GOING_AWAY);
 
         // The RemoteAnimationControl didn't register AppTransitionListener and
@@ -429,7 +426,7 @@
         mNextAppTransitionRequests.clear();
         clear();
         setReady();
-        notifyAppTransitionCancelledLocked(keyguardGoingAway);
+        notifyAppTransitionCancelledLocked(keyguardGoingAwayCancelled);
     }
 
     private void setAppTransitionState(int state) {
@@ -479,9 +476,9 @@
         }
     }
 
-    private void notifyAppTransitionCancelledLocked(boolean keyguardGoingAway) {
+    private void notifyAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled) {
         for (int i = 0; i < mListeners.size(); i++) {
-            mListeners.get(i).onAppTransitionCancelledLocked(keyguardGoingAway);
+            mListeners.get(i).onAppTransitionCancelledLocked(keyguardGoingAwayCancelled);
         }
     }
 
@@ -491,14 +488,12 @@
         }
     }
 
-    private int notifyAppTransitionStartingLocked(boolean keyguardGoingAway,
-            boolean keyguardOcclude, long duration, long statusBarAnimationStartTime,
+    private int notifyAppTransitionStartingLocked(long statusBarAnimationStartTime,
             long statusBarAnimationDuration) {
         int redoLayout = 0;
         for (int i = 0; i < mListeners.size(); i++) {
-            redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway,
-                    keyguardOcclude, duration, statusBarAnimationStartTime,
-                    statusBarAnimationDuration);
+            redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(
+                    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 963345f..4b0005d 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -20,10 +20,6 @@
 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;
@@ -81,9 +77,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;
@@ -93,7 +91,6 @@
 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;
@@ -295,7 +292,6 @@
 
             final int flags = appTransition.getTransitFlags();
             layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
-            handleNonAppWindowsInTransition(transit, flags);
             appTransition.postAnimationCallback();
             appTransition.clear();
         } finally {
@@ -1037,6 +1033,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(
@@ -1143,30 +1165,6 @@
         }
     }
 
-    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/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 0422906..b033dca 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -504,6 +504,7 @@
 
     @Override
     public void onConfigurationChanged(Configuration newParentConfig) {
+        mTransitionController.collectForDisplayAreaChange(this);
         mTmpConfiguration.setTo(getConfiguration());
         super.onConfigurationChanged(newParentConfig);
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index c8692ca..1c90bba 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -53,7 +53,6 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 import static android.view.View.GONE;
-import static android.view.ViewRootImpl.LOCAL_LAYOUT;
 import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.systemBars;
@@ -442,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();
 
     /**
@@ -950,7 +949,7 @@
 
                 final int preferredModeId = getDisplayPolicy().getRefreshRatePolicy()
                         .getPreferredModeId(w);
-                if (mTmpApplySurfaceChangesTransactionState.preferredModeId == 0
+                if (w.isFocused() && mTmpApplySurfaceChangesTransactionState.preferredModeId == 0
                         && preferredModeId != 0) {
                     mTmpApplySurfaceChangesTransactionState.preferredModeId = preferredModeId;
                 }
@@ -2018,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.
@@ -2068,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);
@@ -2080,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);
@@ -2105,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,
@@ -2195,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;
@@ -2207,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);
     }
 
@@ -2247,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);
@@ -2256,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
@@ -2346,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;
@@ -2357,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) {
@@ -2385,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
@@ -2403,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;
@@ -2444,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;
         }
     }
 
@@ -2710,25 +2689,22 @@
         mCurrentPrivacyIndicatorBounds =
                 mCurrentPrivacyIndicatorBounds.updateStaticBounds(staticBounds);
         if (!Objects.equals(oldBounds, mCurrentPrivacyIndicatorBounds)) {
-            updateDisplayFrames(false /* insetsSourceMayChange */, true /* notifyInsetsChange */);
+            updateDisplayFrames(true /* notifyInsetsChange */);
         }
     }
 
     void onDisplayInfoChanged() {
-        updateDisplayFrames(LOCAL_LAYOUT, LOCAL_LAYOUT);
+        updateDisplayFrames(false /* notifyInsetsChange */);
         mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
         mInputMonitor.layoutInputConsumers(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
         mDisplayPolicy.onDisplayInfoChanged(mDisplayInfo);
     }
 
-    private void updateDisplayFrames(boolean insetsSourceMayChange, boolean notifyInsetsChange) {
+    private void updateDisplayFrames(boolean notifyInsetsChange) {
         if (mDisplayFrames.update(mDisplayInfo,
                 calculateDisplayCutoutForRotation(mDisplayInfo.rotation),
                 calculateRoundedCornersForRotation(mDisplayInfo.rotation),
                 calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation))) {
-            if (insetsSourceMayChange) {
-                mDisplayPolicy.updateInsetsSourceFramesExceptIme(mDisplayFrames);
-            }
             mInsetsStateController.onDisplayFramesUpdated(notifyInsetsChange);
         }
     }
@@ -3320,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));
@@ -4416,13 +4400,20 @@
      */
     @VisibleForTesting
     InsetsControlTarget computeImeControlTarget() {
+        if (mImeInputTarget == null) {
+            // A special case that if there is no IME input target while the IME is being killed,
+            // in case seeing unexpected IME surface visibility change when delivering the IME leash
+            // to the remote insets target during the IME restarting, but the focus window is not in
+            // multi-windowing mode, return null target until the next input target updated.
+            return null;
+        }
+
+        final WindowState imeInputTarget = mImeInputTarget.getWindowState();
         if (!isImeControlledByApp() && mRemoteInsetsControlTarget != null
-                || (mImeInputTarget != null
-                        && getImeHostOrFallback(mImeInputTarget.getWindowState())
-                               == mRemoteInsetsControlTarget)) {
+                || getImeHostOrFallback(imeInputTarget) == mRemoteInsetsControlTarget) {
             return mRemoteInsetsControlTarget;
         } else {
-            return mImeInputTarget != null ? mImeInputTarget.getWindowState() : null;
+            return imeInputTarget;
         }
     }
 
@@ -5943,6 +5934,9 @@
         if (changes != 0) {
             Slog.i(TAG, "Override config changes=" + Integer.toHexString(changes) + " "
                     + mTempConfig + " for displayId=" + mDisplayId);
+            if (isReady() && mTransitionController.isShellTransitionsEnabled()) {
+                requestChangeTransitionIfNeeded(changes, null /* displayChange */);
+            }
             onRequestedOverrideConfigurationChanged(mTempConfig);
 
             final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
@@ -5959,9 +5953,6 @@
             }
             mWmService.mDisplayNotificationController.dispatchDisplayChanged(
                     this, getConfiguration());
-            if (isReady() && mTransitionController.isShellTransitionsEnabled()) {
-                requestChangeTransitionIfNeeded(changes, null /* displayChange */);
-            }
         }
         return changes;
     }
@@ -6595,7 +6586,7 @@
         }
 
         @Override
-        public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
+        public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled) {
             // 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 0769406..508e6dc 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;
@@ -258,7 +265,7 @@
     private volatile boolean mWindowManagerDrawComplete;
 
     private WindowState mStatusBar = null;
-    private WindowState mNotificationShade = null;
+    private volatile WindowState mNotificationShade;
     private final int[] mStatusBarHeightForRotation = new int[4];
     private WindowState mNavigationBar = null;
     @NavigationBarPosition
@@ -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,9 +619,8 @@
             }
 
             @Override
-            public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
-                    boolean keyguardOccluding, long duration,
-                    long statusBarAnimationStartTime, long statusBarAnimationDuration) {
+            public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
+                    long statusBarAnimationDuration) {
                 mHandler.post(() -> {
                     StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
                     if (statusBar != null) {
@@ -616,7 +632,7 @@
             }
 
             @Override
-            public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
+            public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled) {
                 mHandler.post(mAppTransitionCancelled);
             }
 
@@ -794,11 +810,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;
             }
@@ -1576,19 +1592,6 @@
         }
     }
 
-    void updateInsetsSourceFramesExceptIme(DisplayFrames displayFrames) {
-        sTmpClientFrames.attachedFrame = null;
-        for (int i = mInsetsSourceWindowsExceptIme.size() - 1; i >= 0; i--) {
-            final WindowState win = mInsetsSourceWindowsExceptIme.valueAt(i);
-            mWindowLayout.computeFrames(win.mAttrs.forRotation(displayFrames.mRotation),
-                    displayFrames.mInsetsState, displayFrames.mDisplayCutoutSafe,
-                    displayFrames.mUnrestricted, win.getWindowingMode(), UNSPECIFIED_LENGTH,
-                    UNSPECIFIED_LENGTH, win.getRequestedVisibilities(), win.mGlobalScale,
-                    sTmpClientFrames);
-            win.updateSourceFrame(sTmpClientFrames.frame);
-        }
-    }
-
     void onDisplayInfoChanged(DisplayInfo info) {
         mSystemGestures.onDisplayInfoChanged(info);
     }
@@ -2020,35 +2023,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;
@@ -2070,45 +2044,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;
     }
 
     /**
@@ -2130,53 +2083,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());
     }
 
     /**
@@ -2208,48 +2131,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
@@ -2269,17 +2219,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) {
@@ -2384,18 +2323,19 @@
             return;
         }
 
-        // The immersive mode confirmation should never affect the system bar visibility, otherwise
+        // Immersive mode confirmation should never affect the system bar visibility, otherwise
         // it will unhide the navigation bar and hide itself.
         if (winCandidate.getAttrs().token == mImmersiveModeConfirmation.getWindowToken()) {
-
-            // The immersive mode confirmation took the focus from mLastFocusedWindow which was
-            // controlling the system ui visibility. So if mLastFocusedWindow can still receive
-            // keys, we let it keep controlling the visibility.
-            final boolean lastFocusCanReceiveKeys =
-                    (mLastFocusedWindow != null && mLastFocusedWindow.canReceiveKeys());
-            winCandidate = isKeyguardShowing() && !isKeyguardOccluded() ? mNotificationShade
-                    : lastFocusCanReceiveKeys ? mLastFocusedWindow
-                            : mTopFullscreenOpaqueWindowState;
+            if (mNotificationShade != null && mNotificationShade.canReceiveKeys()) {
+                // Let notification shade control the system bar visibility.
+                winCandidate = mNotificationShade;
+            } else if (mLastFocusedWindow != null && mLastFocusedWindow.canReceiveKeys()) {
+                // Immersive mode confirmation took the focus from mLastFocusedWindow which was
+                // controlling the system bar visibility. Let it keep controlling the visibility.
+                winCandidate = mLastFocusedWindow;
+            } else {
+                winCandidate = mTopFullscreenOpaqueWindowState;
+            }
             if (winCandidate == null) {
                 return;
             }
@@ -2758,6 +2698,19 @@
         mImmersiveModeConfirmation.onLockTaskModeChangedLw(lockTaskState);
     }
 
+    /** Called when a {@link android.os.PowerManager#USER_ACTIVITY_EVENT_TOUCH} is sent. */
+    public void onUserActivityEventTouch() {
+        // If there is keyguard, it may use INPUT_FEATURE_DISABLE_USER_ACTIVITY (InputDispatcher
+        // won't trigger user activity for touch). So while the device is not interactive, the user
+        // event is only sent explicitly from SystemUI.
+        if (mAwake) return;
+        // If the event is triggered while the display is not awake, the screen may be showing
+        // dozing UI such as AOD or overlay UI of under display fingerprint. Then set the animating
+        // state temporarily to make the process more responsive.
+        final WindowState w = mNotificationShade;
+        mService.mAtmService.setProcessAnimatingWhileDozing(w != null ? w.getProcess() : null);
+    }
+
     boolean onSystemUiSettingsChanged() {
         return mImmersiveModeConfirmation.onSettingChanged(mService.mCurrentUserId);
     }
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 3d91921..97609a7 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -399,9 +399,8 @@
                 return false;
             }
 
-            final ScreenRotationAnimation screenRotationAnimation =
-                    mDisplayContent.getRotationAnimation();
-            if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
+            if (mDisplayContent.inTransition()
+                    && !mDisplayContent.mTransitionController.useShellTransitionsRotation()) {
                 // Rotation updates cannot be performed while the previous rotation change animation
                 // is still in progress. Skip this update. We will try updating again after the
                 // animation is finished and the display is unfrozen.
@@ -514,19 +513,6 @@
         return true;
     }
 
-    /**
-     * Utility to get a rotating displaycontent from a Transition.
-     * @return null if the transition doesn't contain a rotating display.
-     */
-    static DisplayContent getDisplayFromTransition(Transition transition) {
-        for (int i = transition.mParticipants.size() - 1; i >= 0; --i) {
-            final WindowContainer wc = transition.mParticipants.valueAt(i);
-            if (!(wc instanceof DisplayContent)) continue;
-            return (DisplayContent) wc;
-        }
-        return null;
-    }
-
     private void startRemoteRotation(int fromRotation, int toRotation) {
         mDisplayContent.mRemoteDisplayChangeController.performRemoteDisplayChange(
                 fromRotation, toRotation, null /* newDisplayAreaInfo */,
@@ -546,11 +532,6 @@
                 throw new IllegalStateException("Trying to rotate outside a transition");
             }
             mDisplayContent.mTransitionController.collect(mDisplayContent);
-            // Go through all tasks and collect them before the rotation
-            // TODO(shell-transitions): move collect() to onConfigurationChange once wallpaper
-            //       handling is synchronized.
-            mDisplayContent.mTransitionController.collectForDisplayAreaChange(mDisplayContent,
-                    null /* use collecting transition */);
         }
         mService.mAtmService.deferWindowLayout();
         try {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 7a9e6a9..f3bd1a1 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -761,7 +761,7 @@
 
         InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) {
             super(show, false /* hasCallbacks */, types, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
-                    false /* disable */, 0 /* floatingImeBottomInsets */);
+                    false /* disable */, 0 /* floatingImeBottomInsets */, null);
             mFinishCallback = finishCallback;
             mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
         }
diff --git a/services/core/java/com/android/server/wm/LaunchParamsUtil.java b/services/core/java/com/android/server/wm/LaunchParamsUtil.java
index a618f7c..a0e22e7 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsUtil.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsUtil.java
@@ -63,7 +63,7 @@
      * Calculate the default size for a freeform environment. |defaultSize| is used as the default
      * DP size, but if this is null, the portrait phone size is used.
      */
-    static Size getDefaultFreeformSize(@NonNull ActivityInfo info,
+    static Size getDefaultFreeformSize(@NonNull ActivityRecord activityRecord,
             @NonNull TaskDisplayArea displayArea,
             @NonNull ActivityInfo.WindowLayout layout, int orientation,
             @NonNull Rect stableBounds) {
@@ -98,8 +98,8 @@
         final float aspectRatio = (float) Math.max(width, height) / (float) Math.min(width, height);
 
         // Aspect ratio requirements.
-        final float minAspectRatio = info.getMinAspectRatio(orientation);
-        final float maxAspectRatio = info.getMaxAspectRatio();
+        final float minAspectRatio = activityRecord.getMinAspectRatio();
+        final float maxAspectRatio = activityRecord.info.getMaxAspectRatio();
 
         // Adjust the width and height to the aspect ratio requirements.
         int adjWidth = width;
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 ec9ee29..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
@@ -257,6 +281,10 @@
                             : mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
         }
 
+        return getSplitScreenAspectRatio();
+    }
+
+    float getSplitScreenAspectRatio() {
         int dividerWindowWidth =
                 getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness);
         int dividerInsets =
@@ -432,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);
                 }
@@ -455,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);
@@ -472,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);
@@ -588,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/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
index 64749cf..a89894d 100644
--- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
+++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
@@ -101,7 +101,6 @@
 
         if (t != null) {
             mDisplayContent.mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
-            mTransitionController.collectForDisplayAreaChange(mDisplayContent, t);
             mTransition = t;
         }
     }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 5b702ea..7bb57d8 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -161,15 +161,14 @@
      */
     final AppTransitionListener mAppTransitionListener = new AppTransitionListener() {
         @Override
-        public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
-                boolean keyguardOccluding, long duration, long statusBarAnimationStartTime,
+        public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
                 long statusBarAnimationDuration) {
             continueDeferredCancel();
             return 0;
         }
 
         @Override
-        public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
+        public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled) {
             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/Session.java b/services/core/java/com/android/server/wm/Session.java
index 0128c18..fb68fe6 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -242,16 +242,17 @@
 
     @Override
     public int relayout(IWindow window, WindowManager.LayoutParams attrs,
-            int requestedWidth, int requestedHeight, int viewFlags, int flags,
-            ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
-            SurfaceControl outSurfaceControl, InsetsState outInsetsState,
-            InsetsSourceControl[] outActiveControls, Bundle outSyncSeqIdBundle) {
+            int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+            int lastSyncSeqId, ClientWindowFrames outFrames,
+            MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl,
+            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+            Bundle outSyncSeqIdBundle) {
         if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from "
                 + Binder.getCallingPid());
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag);
         int res = mService.relayoutWindow(this, window, attrs,
-                requestedWidth, requestedHeight, viewFlags, flags,
-                outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
+                requestedWidth, requestedHeight, viewFlags, flags, seq,
+                lastSyncSeqId, outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
                 outActiveControls, outSyncSeqIdBundle);
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to "
@@ -260,6 +261,16 @@
     }
 
     @Override
+    public void relayoutAsync(IWindow window, WindowManager.LayoutParams attrs,
+            int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+            int lastSyncSeqId) {
+        relayout(window, attrs, requestedWidth, requestedHeight, viewFlags, flags, seq,
+                lastSyncSeqId, null /* outFrames */, null /* mergedConfiguration */,
+                null /* outSurfaceControl */, null /* outInsetsState */,
+                null /* outActiveControls */, null /* outSyncIdBundle */);
+    }
+
+    @Override
     public boolean outOfMemory(IWindow window) {
         return mService.outOfMemoryWindow(this, window);
     }
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 c1f06e4..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);
     }
 
@@ -2340,7 +2341,10 @@
             return false;
         }
 
-        return !startBounds.equals(getBounds());
+        // Only take snapshot if the bounds are resized.
+        final Rect endBounds = getConfiguration().windowConfiguration.getBounds();
+        return endBounds.width() != startBounds.width()
+                || endBounds.height() != startBounds.height();
     }
 
     boolean canHaveEmbeddingActivityTransition(@NonNull ActivityRecord child) {
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 6ca5648..d615583 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -18,7 +18,7 @@
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.window.TaskFragmentOrganizer.putErrorInfoInBundle;
-import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENT_TO_TASK;
+import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED;
@@ -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*/);
         }
 
@@ -277,7 +295,7 @@
         }
 
         @Nullable
-        TaskFragmentTransaction.Change prepareActivityReparentToTask(
+        TaskFragmentTransaction.Change prepareActivityReparentedToTask(
                 @NonNull ActivityRecord activity) {
             if (activity.finishing) {
                 Slog.d(TAG, "Reparent activity=" + activity.token + " is finishing");
@@ -315,11 +333,45 @@
             }
             ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Activity=%s reparent to taskId=%d",
                     activity.token, task.mTaskId);
-            return new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENT_TO_TASK)
+            return new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENTED_TO_TASK)
                     .setTaskId(task.mTaskId)
                     .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.
@@ -521,7 +584,7 @@
         mAtmService.mWindowManager.mWindowPlacerLocked.requestTraversal();
     }
 
-    void onActivityReparentToTask(@NonNull ActivityRecord activity) {
+    void onActivityReparentedToTask(@NonNull ActivityRecord activity) {
         final ITaskFragmentOrganizer organizer;
         if (activity.mLastTaskFragmentOrganizerBeforePip != null) {
             // If the activity is previously embedded in an organized TaskFragment.
@@ -547,7 +610,7 @@
             return;
         }
         addPendingEvent(new PendingTaskFragmentEvent.Builder(
-                PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENT_TO_TASK, organizer)
+                PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENTED_TO_TASK, organizer)
                 .setActivity(activity)
                 .build());
     }
@@ -601,7 +664,7 @@
         static final int EVENT_INFO_CHANGED = 2;
         static final int EVENT_PARENT_INFO_CHANGED = 3;
         static final int EVENT_ERROR = 4;
-        static final int EVENT_ACTIVITY_REPARENT_TO_TASK = 5;
+        static final int EVENT_ACTIVITY_REPARENTED_TO_TASK = 5;
 
         @IntDef(prefix = "EVENT_", value = {
                 EVENT_APPEARED,
@@ -609,7 +672,7 @@
                 EVENT_INFO_CHANGED,
                 EVENT_PARENT_INFO_CHANGED,
                 EVENT_ERROR,
-                EVENT_ACTIVITY_REPARENT_TO_TASK
+                EVENT_ACTIVITY_REPARENTED_TO_TASK
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface EventType {}
@@ -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) {
@@ -900,8 +952,8 @@
             case PendingTaskFragmentEvent.EVENT_ERROR:
                 return state.prepareTaskFragmentError(event.mErrorCallbackToken, taskFragment,
                         event.mOpType, event.mException);
-            case PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENT_TO_TASK:
-                return state.prepareActivityReparentToTask(event.mActivity);
+            case PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENTED_TO_TASK:
+                return state.prepareActivityReparentedToTask(event.mActivity);
             default:
                 throw new IllegalArgumentException("Unknown TaskFragmentEvent=" + event.mEventType);
         }
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 7b0337d..1362094 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -746,7 +746,7 @@
 
         // First we get the default size we want.
         displayArea.getStableRect(mTmpStableBounds);
-        final Size defaultSize = LaunchParamsUtil.getDefaultFreeformSize(root.info, displayArea,
+        final Size defaultSize = LaunchParamsUtil.getDefaultFreeformSize(root, displayArea,
                 layout, orientation, mTmpStableBounds);
         mTmpBounds.set(0, 0, defaultSize.getWidth(), defaultSize.getHeight());
         if (hasInitialBounds || sizeMatches(inOutBounds, mTmpBounds)) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index bbc95a1..c586b15 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,13 +32,7 @@
 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;
@@ -68,11 +63,11 @@
 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;
@@ -80,7 +75,6 @@
 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;
 
@@ -205,6 +199,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;
@@ -265,10 +262,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());
+        }
     }
 
     /**
@@ -285,6 +303,11 @@
         }
     }
 
+    /** Only for testing. */
+    void setContainerFreezer(IContainerFreezer freezer) {
+        mContainerFreezer = freezer;
+    }
+
     @TransitionState
     int getState() {
         return mState;
@@ -314,13 +337,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);
     }
@@ -415,6 +443,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}.
@@ -531,6 +590,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;
@@ -622,9 +685,11 @@
             throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
         }
 
+        boolean hasParticipatedDisplay = false;
         // Commit all going-invisible containers
         for (int i = 0; i < mParticipants.size(); ++i) {
-            final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
+            final WindowContainer<?> participant = mParticipants.valueAt(i);
+            final ActivityRecord ar = participant.asActivityRecord();
             if (ar != null) {
                 boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar);
                 // We need both the expected visibility AND current requested-visibility to be
@@ -656,8 +721,13 @@
                     // Legacy dispatch relies on this (for now).
                     ar.mEnteringAnimation = visibleAtTransitionEnd;
                 }
+                continue;
             }
-            final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
+            if (participant.asDisplayContent() != null) {
+                hasParticipatedDisplay = true;
+                continue;
+            }
+            final WallpaperWindowToken wt = participant.asWallpaperToken();
             if (wt != null) {
                 final boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(wt);
                 if (!visibleAtTransitionEnd && !wt.isVisibleRequested()) {
@@ -737,6 +807,12 @@
 
         mState = STATE_FINISHED;
         mController.mTransitionTracer.logState(this);
+        // Rotation change may be deferred while there is a display change transition, so check
+        // again in case there is a new pending change.
+        if (hasParticipatedDisplay && !mController.useShellTransitionsRotation()) {
+            mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */,
+                    false /* forceRelayout */);
+        }
     }
 
     void abort() {
@@ -804,6 +880,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.
@@ -1053,36 +1142,9 @@
 
     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(
-                    true /* keyguardOccludingStarted */);
+                    false /* notify */);
         }
     }
 
@@ -1192,7 +1254,14 @@
             return false;
         }
 
-        final @TransitionInfo.TransitionMode int mode = changes.get(target).getTransitMode(target);
+        final ChangeInfo change = changes.get(target);
+        if (change.mStartParent != null && target.getParent() != change.mStartParent) {
+            // When a window is reparented, the state change won't fit into any of the parents.
+            // Don't promote such change so that we can animate the reparent if needed.
+            return false;
+        }
+
+        final @TransitionInfo.TransitionMode int mode = change.getTransitMode(target);
         for (int i = parent.getChildCount() - 1; i >= 0; --i) {
             final WindowContainer<?> sibling = parent.getChildAt(i);
             if (target == sibling) continue;
@@ -1332,14 +1401,14 @@
                     // Intermediate parents must be those that has window to be managed by Shell.
                     continue;
                 }
-                if (parentChange.mParent != null && !skipIntermediateReports) {
-                    changes.get(wc).mParent = p;
+                if (parentChange.mEndParent != null && !skipIntermediateReports) {
+                    changes.get(wc).mEndParent = p;
                     // The chain above the parent was processed.
                     break;
                 }
                 if (targetList.contains(p)) {
                     if (skipIntermediateReports) {
-                        changes.get(wc).mParent = p;
+                        changes.get(wc).mEndParent = p;
                     } else {
                         intermediates.add(p);
                     }
@@ -1351,10 +1420,10 @@
             }
             if (!foundParentInTargets || intermediates.isEmpty()) continue;
             // Add any always-report parents along the way.
-            changes.get(wc).mParent = intermediates.get(0);
+            changes.get(wc).mEndParent = intermediates.get(0);
             for (int j = 0; j < intermediates.size() - 1; j++) {
                 final WindowContainer<?> intermediate = intermediates.get(j);
-                changes.get(intermediate).mParent = intermediates.get(j + 1);
+                changes.get(intermediate).mEndParent = intermediates.get(j + 1);
                 targets.add(intermediate);
             }
         }
@@ -1467,8 +1536,8 @@
                     target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken()
                             : null, getLeashSurface(target, startT));
             // TODO(shell-transitions): Use leash for non-organized windows.
-            if (info.mParent != null) {
-                change.setParent(info.mParent.mRemoteToken.toWindowContainerToken());
+            if (info.mEndParent != null) {
+                change.setParent(info.mEndParent.mRemoteToken.toWindowContainerToken());
             }
             change.setMode(info.getTransitMode(target));
             change.setStartAbsBounds(info.mAbsoluteBounds);
@@ -1621,6 +1690,19 @@
         return mainWin.getAttrs().rotationAnimation;
     }
 
+    /** Applies the new configuration and returns {@code true} if there is a display change. */
+    boolean applyDisplayChangeIfNeeded() {
+        boolean changed = false;
+        for (int i = mParticipants.size() - 1; i >= 0; --i) {
+            final WindowContainer<?> wc = mParticipants.valueAt(i);
+            final DisplayContent dc = wc.asDisplayContent();
+            if (dc == null || !mChanges.get(dc).hasChanged(dc)) continue;
+            dc.sendNewConfiguration();
+            changed = true;
+        }
+        return changed;
+    }
+
     boolean getLegacyIsReady() {
         return isCollecting() && mSyncId >= 0;
     }
@@ -1651,7 +1733,9 @@
         @interface Flag {}
 
         // Usually "post" change state.
-        WindowContainer mParent;
+        WindowContainer mEndParent;
+        // Parent before change state.
+        WindowContainer mStartParent;
 
         // State tracking
         boolean mExistenceChanged = false;
@@ -1672,6 +1756,7 @@
             mAbsoluteBounds.set(origState.getBounds());
             mShowWallpaper = origState.showWallpaper();
             mRotation = origState.getWindowConfiguration().getRotation();
+            mStartParent = origState.getParent();
         }
 
         @VisibleForTesting
@@ -1779,6 +1864,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();
     }
 
     /**
@@ -1944,4 +2031,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 4f324f2..e243809 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;
     }
@@ -463,13 +470,13 @@
     }
 
     /**
-     * Collects the window containers which need to be synced with the changing display (e.g.
-     * rotating) to the given transition or the current collecting transition.
+     * Collects the window containers which need to be synced with the changing display area into
+     * the current collecting transition.
      */
-    void collectForDisplayAreaChange(@NonNull DisplayArea<?> wc, @Nullable Transition incoming) {
-        if (incoming == null) incoming = mCollectingTransition;
-        if (incoming == null) return;
-        final Transition transition = incoming;
+    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()) {
@@ -489,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;
@@ -638,11 +655,9 @@
     }
 
     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(keyguardGoingAway,
-                    false /* keyguardOcclude */, 0 /* durationHint */,
+            mLegacyListeners.get(i).onAppTransitionStartingLocked(
                     SystemClock.uptimeMillis() + statusBarTransitionDelay,
                     AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
         }
@@ -657,7 +672,7 @@
     void dispatchLegacyAppTransitionCancelled() {
         for (int i = 0; i < mLegacyListeners.size(); ++i) {
             mLegacyListeners.get(i).onAppTransitionCancelledLocked(
-                    false /* keyguardGoingAway */);
+                    false /* keyguardGoingAwayCancelled */);
         }
     }
 
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 da5f5d0..11475ac 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -220,9 +220,10 @@
         /**
          * Called when a pending app transition gets cancelled.
          *
-         * @param keyguardGoingAway true if keyguard going away transition got cancelled.
+         * @param keyguardGoingAwayCancelled {@code true} if keyguard going away transition was
+         *        cancelled.
          */
-        public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {}
+        public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled) {}
 
         /**
          * Called when an app transition is timed out.
@@ -232,9 +233,6 @@
         /**
          * 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
@@ -245,8 +243,7 @@
          * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER},
          * or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}.
          */
-        public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
-                boolean keyguardOccluding, long duration, long statusBarAnimationStartTime,
+        public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
                 long statusBarAnimationDuration) {
             return 0;
         }
@@ -810,12 +807,17 @@
          */
         public final String imeLayerTargetName;
 
+        /** The surface parent of the IME container. */
+        public final String imeSurfaceParentName;
+
         public ImeTargetInfo(String focusedWindowName, String requestWindowName,
-                String imeControlTargetName, String imeLayerTargetName) {
+                String imeControlTargetName, String imeLayerTargetName,
+                String imeSurfaceParentName) {
             this.focusedWindowName = focusedWindowName;
             this.requestWindowName = requestWindowName;
             this.imeControlTargetName = imeControlTargetName;
             this.imeLayerTargetName = imeLayerTargetName;
+            this.imeSurfaceParentName = imeSurfaceParentName;
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fd54f78..8025cb2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -88,10 +88,10 @@
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_RELAUNCH;
+import static android.view.WindowManager.fixScale;
 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;
@@ -320,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;
 
@@ -424,32 +425,6 @@
             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.
      */
@@ -1117,7 +1092,7 @@
             = new WindowManagerInternal.AppTransitionListener() {
 
         @Override
-        public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
+        public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled) {
         }
 
         @Override
@@ -1326,15 +1301,10 @@
         }, UserHandle.ALL, suspendPackagesFilter, null, null);
 
         // Get persisted window scale setting
-        mWindowAnimationScaleSetting = Settings.Global.getFloat(resolver,
-                Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting);
-        mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
-                Settings.Global.TRANSITION_ANIMATION_SCALE,
-                context.getResources().getFloat(
-                        R.dimen.config_appTransitionAnimationDurationScaleDefault));
+        mWindowAnimationScaleSetting = getWindowAnimationScaleSetting();
+        mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
 
-        setAnimatorDurationScale(Settings.Global.getFloat(resolver,
-                Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScaleSetting));
+        setAnimatorDurationScale(getAnimatorDurationScaleSetting());
 
         mForceDesktopModeOnExternalDisplays = Settings.Global.getInt(resolver,
                 DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0;
@@ -1408,6 +1378,22 @@
                 lightRadius);
     }
 
+    private float getTransitionAnimationScaleSetting() {
+        return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+                Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
+                                R.dimen.config_appTransitionAnimationDurationScaleDefault)));
+    }
+
+    private float getAnimatorDurationScaleSetting() {
+        return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+                Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScaleSetting));
+    }
+
+    private float getWindowAnimationScaleSetting() {
+        return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+                Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting));
+    }
+
     /**
      * Called after all entities (such as the {@link ActivityManagerService}) have been set up and
      * associated with the {@link WindowManagerService}.
@@ -1861,7 +1847,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();
             }
 
@@ -1869,7 +1856,7 @@
             displayContent.getInsetsStateController().updateAboveInsetsState(
                     false /* notifyInsetsChanged */);
 
-            outInsetsState.set(win.getCompatInsetsState(), win.isClientLocal());
+            outInsetsState.set(win.getCompatInsetsState(), true /* copySources */);
             getInsetsSourceControls(win, outActiveControls);
 
             if (win.mLayoutAttached) {
@@ -2248,11 +2235,14 @@
     }
 
     public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,
-            int requestedWidth, int requestedHeight, int viewVisibility, int flags,
-            ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
-            SurfaceControl outSurfaceControl, InsetsState outInsetsState,
-            InsetsSourceControl[] outActiveControls, Bundle outSyncIdBundle) {
-        Arrays.fill(outActiveControls, null);
+            int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,
+            int lastSyncSeqId, ClientWindowFrames outFrames,
+            MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl,
+            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+            Bundle outSyncIdBundle) {
+        if (outActiveControls != null) {
+            Arrays.fill(outActiveControls, null);
+        }
         int result = 0;
         boolean configChanged;
         final int pid = Binder.getCallingPid();
@@ -2263,8 +2253,15 @@
             if (win == null) {
                 return 0;
             }
+            if (win.mRelayoutSeq < seq) {
+                win.mRelayoutSeq = seq;
+            } else if (win.mRelayoutSeq > seq) {
+                return 0;
+            }
 
-            if (win.cancelAndRedraw() && win.mPrepareSyncSeqId <= win.mLastSeqIdSentToRelayout) {
+            if (win.cancelAndRedraw() && win.mPrepareSyncSeqId <= lastSyncSeqId) {
+                // The client has reported the sync draw, but we haven't finished it yet.
+                // Don't let the client perform a non-sync draw at this time.
                 result |= RELAYOUT_RES_CANCEL_AND_REDRAW;
             }
 
@@ -2427,7 +2424,7 @@
 
             // Create surfaceControl before surface placement otherwise layout will be skipped
             // (because WS.isGoneForLayout() is true when there is no surface.
-            if (shouldRelayout) {
+            if (shouldRelayout && outSurfaceControl != null) {
                 try {
                     result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);
                 } catch (Exception e) {
@@ -2466,22 +2463,25 @@
                 winAnimator.mEnterAnimationPending = false;
                 winAnimator.mEnteringAnimation = false;
 
-                if (viewVisibility == View.VISIBLE && winAnimator.hasSurface()) {
-                    // We already told the client to go invisible, but the message may not be
-                    // handled yet, or it might want to draw a last frame. If we already have a
-                    // surface, let the client use that, but don't create new surface at this point.
-                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
-                    winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl);
-                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-                } else {
-                    if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
-
-                    try {
-                        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmReleaseOutSurface_"
-                                + win.mAttrs.getTitle());
-                        outSurfaceControl.release();
-                    } finally {
+                if (outSurfaceControl != null) {
+                    if (viewVisibility == View.VISIBLE && winAnimator.hasSurface()) {
+                        // We already told the client to go invisible, but the message may not be
+                        // handled yet, or it might want to draw a last frame. If we already have a
+                        // surface, let the client use that, but don't create new surface at this
+                        // point.
+                        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
+                        winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl);
                         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+                    } else {
+                        if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
+
+                        try {
+                            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmReleaseOutSurface_"
+                                    + win.mAttrs.getTitle());
+                            outSurfaceControl.release();
+                        } finally {
+                            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+                        }
                     }
                 }
 
@@ -2534,20 +2534,16 @@
                 win.mResizedWhileGone = false;
             }
 
-            win.fillClientWindowFramesAndConfiguration(outFrames, mergedConfiguration,
-                    false /* useLatestConfig */, shouldRelayout);
+            if (outFrames != null && outMergedConfiguration != null) {
+                win.fillClientWindowFramesAndConfiguration(outFrames, outMergedConfiguration,
+                        false /* useLatestConfig */, shouldRelayout);
 
-            // Set resize-handled here because the values are sent back to the client.
-            win.onResizeHandled();
+                // Set resize-handled here because the values are sent back to the client.
+                win.onResizeHandled();
+            }
 
-            outInsetsState.set(win.getCompatInsetsState(), win.isClientLocal());
-            if (DEBUG) {
-                Slog.v(TAG_WM, "Relayout given client " + client.asBinder()
-                        + ", requestedWidth=" + requestedWidth
-                        + ", requestedHeight=" + requestedHeight
-                        + ", viewVisibility=" + viewVisibility
-                        + "\nRelayout returning frame=" + outFrames.frame
-                        + ", surface=" + outSurfaceControl);
+            if (outInsetsState != null) {
+                outInsetsState.set(win.getCompatInsetsState(), true /* copySources */);
             }
 
             ProtoLog.v(WM_DEBUG_FOCUS, "Relayout of %s: focusMayChange=%b",
@@ -2558,14 +2554,16 @@
             }
             win.mInRelayout = false;
 
-            if (USE_BLAST_SYNC && win.useBLASTSync() && viewVisibility != View.GONE
-                    && (win.mSyncSeqId > win.mLastSeqIdSentToRelayout)) {
-                win.markRedrawForSyncReported();
-
-                win.mLastSeqIdSentToRelayout = win.mSyncSeqId;
-                outSyncIdBundle.putInt("seqid", win.mSyncSeqId);
-            } else {
-                outSyncIdBundle.putInt("seqid", -1);
+            if (outSyncIdBundle != null) {
+                final int maybeSyncSeqId;
+                if (USE_BLAST_SYNC && win.useBLASTSync() && viewVisibility != View.GONE
+                        && win.mSyncSeqId > lastSyncSeqId) {
+                    maybeSyncSeqId = win.shouldSyncWithBuffers() ? win.mSyncSeqId : -1;
+                    win.markRedrawForSyncReported();
+                } else {
+                    maybeSyncSeqId = -1;
+                }
+                outSyncIdBundle.putInt("seqid", maybeSyncSeqId);
             }
 
             if (configChanged) {
@@ -2574,7 +2572,9 @@
                 displayContent.sendNewConfiguration();
                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             }
-            getInsetsSourceControls(win, outActiveControls);
+            if (outActiveControls != null) {
+                getInsetsSourceControls(win, outActiveControls);
+            }
         }
 
         Binder.restoreCallingIdentity(origId);
@@ -2610,27 +2610,32 @@
             transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
         }
 
-        String reason = null;
-        if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) {
-            reason = "applyAnimation";
-            focusMayChange = true;
-            win.mAnimatingExit = true;
-        } else if (win.mDisplayContent.okToAnimate() && win.isExitAnimationRunningSelfOrParent()) {
-            // Currently in a hide animation... turn this into
-            // an exit.
-            win.mAnimatingExit = true;
-        } else if (win.mDisplayContent.okToAnimate()
-                && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)
-                && win.mAttrs.type != TYPE_NOTIFICATION_SHADE) {
-            reason = "isWallpaperTarget";
-            // If the wallpaper is currently behind this app window, we need to change both of them
-            // inside of a transaction to avoid artifacts.
-            // For NotificationShade, sysui is in charge of running window animation and it updates
-            // the client view visibility only after both NotificationShade and the wallpaper are
-            // hidden. So we don't need to care about exit animation, but can destroy its surface
-            // immediately.
-            win.mAnimatingExit = true;
-        } else {
+        if (win.isWinVisibleLw() && win.mDisplayContent.okToAnimate()) {
+            String reason = null;
+            if (winAnimator.applyAnimationLocked(transit, false)) {
+                reason = "applyAnimation";
+                focusMayChange = true;
+                win.mAnimatingExit = true;
+            } else if (win.isExitAnimationRunningSelfOrParent()) {
+                reason = "animating";
+                win.mAnimatingExit = true;
+            } else if (win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)
+                    && win.mAttrs.type != TYPE_NOTIFICATION_SHADE) {
+                reason = "isWallpaperTarget";
+                // If the wallpaper is currently behind this app window, they should be updated
+                // in a transaction to avoid artifacts.
+                // For NotificationShade, sysui is in charge of running window animation and it
+                // updates the client view visibility only after both NotificationShade and the
+                // wallpaper are hidden. So the exit animation is not needed and can destroy its
+                // surface immediately.
+                win.mAnimatingExit = true;
+            }
+            if (reason != null) {
+                ProtoLog.d(WM_DEBUG_ANIM,
+                        "Set animatingExit: reason=startExitingAnimation/%s win=%s", reason, win);
+            }
+        }
+        if (!win.mAnimatingExit) {
             boolean stopped = win.mActivityRecord == null || win.mActivityRecord.mAppStopped;
             // We set mDestroying=true so ActivityRecord#notifyAppStopped in-to destroy surfaces
             // will later actually destroy the surface if we do not do so here. Normally we leave
@@ -2638,10 +2643,6 @@
             win.mDestroying = true;
             win.destroySurface(false, stopped);
         }
-        if (reason != null) {
-            ProtoLog.d(WM_DEBUG_ANIM, "Set animatingExit: reason=startExitingAnimation/%s win=%s",
-                    reason, win);
-        }
         if (mAccessibilityController.hasCallbacks()) {
             mAccessibilityController.onWindowTransition(win, transit);
         }
@@ -3393,11 +3394,6 @@
         }
     }
 
-    static float fixScale(float scale) {
-        if (scale < 0) scale = 0;
-        else if (scale > 20) scale = 20;
-        return Math.abs(scale);
-    }
 
     @Override
     public void setAnimationScale(int which, float scale) {
@@ -4289,7 +4285,9 @@
                     // Even if alwaysSend, we are waiting for a transition or remote to provide
                     // updated configuration, so we can't update configuration yet.
                     if (!pendingRemoteDisplayChange) {
-                        if (!rotationChanged || forceRelayout) {
+                        // The layout-needed flag will be set if there is a rotation change, so
+                        // only set it if the caller requests to force relayout.
+                        if (forceRelayout) {
                             displayContent.setLayoutNeeded();
                             layoutNeeded = true;
                         }
@@ -5323,24 +5321,16 @@
                     final int mode = msg.arg1;
                     switch (mode) {
                         case WINDOW_ANIMATION_SCALE: {
-                            mWindowAnimationScaleSetting = Settings.Global.getFloat(
-                                    mContext.getContentResolver(),
-                                    Settings.Global.WINDOW_ANIMATION_SCALE,
-                                    mWindowAnimationScaleSetting);
+                            mWindowAnimationScaleSetting = getWindowAnimationScaleSetting();
                             break;
                         }
                         case TRANSITION_ANIMATION_SCALE: {
-                            mTransitionAnimationScaleSetting = Settings.Global.getFloat(
-                                    mContext.getContentResolver(),
-                                    Settings.Global.TRANSITION_ANIMATION_SCALE,
-                                    mTransitionAnimationScaleSetting);
+                            mTransitionAnimationScaleSetting =
+                                    getTransitionAnimationScaleSetting();
                             break;
                         }
                         case ANIMATION_DURATION_SCALE: {
-                            mAnimatorDurationScaleSetting = Settings.Global.getFloat(
-                                    mContext.getContentResolver(),
-                                    Settings.Global.ANIMATOR_DURATION_SCALE,
-                                    mAnimatorDurationScaleSetting);
+                            mAnimatorDurationScaleSetting = getAnimatorDurationScaleSetting();
                             dispatchNewAnimatorScaleLocked(null);
                             break;
                         }
@@ -6369,27 +6359,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) {
@@ -7227,7 +7196,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);
         }
     }
 
@@ -8202,6 +8173,7 @@
             final String requestWindowName;
             final String imeControlTargetName;
             final String imeLayerTargetName;
+            final String imeSurfaceParentName;
             synchronized (mGlobalLock) {
                 final WindowState focusedWin = mWindowMap.get(focusedToken);
                 focusedWindowName = focusedWin != null ? focusedWin.getName() : "null";
@@ -8218,15 +8190,17 @@
                     }
                     final InsetsControlTarget target = dc.getImeTarget(IME_TARGET_LAYERING);
                     imeLayerTargetName = target != null ? target.getWindow().getName() : "null";
+                    final SurfaceControl imeParent = dc.mInputMethodSurfaceParent;
+                    imeSurfaceParentName = imeParent != null ? imeParent.toString() : "null";
                     if (show) {
                         dc.onShowImeRequested();
                     }
                 } else {
-                    imeControlTargetName = imeLayerTargetName = "no-display";
+                    imeControlTargetName = imeLayerTargetName = imeSurfaceParentName = "no-display";
                 }
             }
             return new ImeTargetInfo(focusedWindowName, requestWindowName, imeControlTargetName,
-                    imeLayerTargetName);
+                    imeLayerTargetName, imeSurfaceParentName);
         }
 
         @Override
@@ -8902,7 +8876,6 @@
     @Override
     public boolean getWindowInsets(WindowManager.LayoutParams attrs, int displayId,
             InsetsState outInsetsState) {
-        final boolean fromLocal = Binder.getCallingPid() == MY_PID;
         final int uid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         try {
@@ -8916,10 +8889,8 @@
                 final float overrideScale = mAtmService.mCompatModePackages.getCompatScale(
                         attrs.packageName, uid);
                 final InsetsState state = dc.getInsetsPolicy().getInsetsForWindowMetrics(attrs);
-                final boolean hasCompatScale =
-                        WindowState.hasCompatScale(attrs, token, overrideScale);
-                outInsetsState.set(state, hasCompatScale || fromLocal);
-                if (hasCompatScale) {
+                outInsetsState.set(state, true /* copySources */);
+                if (WindowState.hasCompatScale(attrs, token, overrideScale)) {
                     final float compatScale = token != null && token.hasSizeCompatBounds()
                             ? token.getSizeCompatScale() * overrideScale
                             : overrideScale;
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3b9cd36..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) {
@@ -397,17 +397,8 @@
         mService.deferWindowLayout();
         mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */);
         try {
-            if (transition != null) {
-                // First check if we have a display rotation transition and if so, update it.
-                final DisplayContent dc = DisplayRotation.getDisplayFromTransition(transition);
-                if (dc != null && transition.mChanges.get(dc).hasChanged(dc)) {
-                    // Go through all tasks and collect them before the rotation
-                    // TODO(shell-transitions): move collect() to onConfigurationChange once
-                    //       wallpaper handling is synchronized.
-                    dc.mTransitionController.collectForDisplayAreaChange(dc, transition);
-                    dc.sendNewConfiguration();
-                    effects |= TRANSACT_EFFECTS_LIFECYCLE;
-                }
+            if (transition != null && transition.applyDisplayChangeIfNeeded()) {
+                effects |= TRANSACT_EFFECTS_LIFECYCLE;
             }
             final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
             final int hopSize = hops.size();
@@ -428,15 +419,6 @@
                     addToSyncSet(syncId, wc);
                 }
                 if (transition != null) transition.collect(wc);
-                final DisplayArea da = wc.asDisplayArea();
-                // Only check DisplayArea here as a similar thing is done for DisplayContent above.
-                if (da != null && wc.asDisplayContent() == null
-                        && entry.getValue().getWindowingMode() != da.getWindowingMode()) {
-                    // Go through all tasks and collect them before changing the windowing mode of a
-                    // display-level container.
-                    // TODO(shell-transitions): handle this more elegantly.
-                    da.mTransitionController.collectForDisplayAreaChange(da, transition);
-                }
 
                 if ((entry.getValue().getChangeMask()
                         & WindowContainerTransaction.Change.CHANGE_FORCE_NO_PIP) != 0) {
@@ -1423,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/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 95b0645..8f63e93 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -27,6 +27,7 @@
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.server.am.ProcessList.INVALID_ADJ;
 import static com.android.server.wm.ActivityRecord.State.DESTROYED;
 import static com.android.server.wm.ActivityRecord.State.DESTROYING;
 import static com.android.server.wm.ActivityRecord.State.PAUSED;
@@ -125,6 +126,8 @@
     private volatile int mCurProcState = PROCESS_STATE_NONEXISTENT;
     // Last reported process state;
     private volatile int mRepProcState = PROCESS_STATE_NONEXISTENT;
+    // Currently computed oom adj score
+    private volatile int mCurAdj = INVALID_ADJ;
     // are we in the process of crashing?
     private volatile boolean mCrashing;
     // does the app have a not responding dialog?
@@ -319,6 +322,14 @@
         return mCurProcState;
     }
 
+    public void setCurrentAdj(int curAdj) {
+        mCurAdj = curAdj;
+    }
+
+    int getCurrentAdj() {
+        return mCurAdj;
+    }
+
     /**
      * Sets the computed process state from the oom adjustment calculation. This is frequently
      * called in activity manager's lock, so don't use window manager lock here.
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 283010e..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;
@@ -389,9 +390,8 @@
      * examine the git commit message introducing this comment and variable.2
      */
     int mSyncSeqId = 0;
-    int mLastSeqIdSentToRelayout = 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;
 
     /**
@@ -425,6 +425,7 @@
     boolean mHaveFrame;
     boolean mObscured;
 
+    int mRelayoutSeq = -1;
     int mLayoutSeq = -1;
 
     /**
@@ -1349,29 +1350,15 @@
         final WindowFrames windowFrames = mWindowFrames;
         mTmpRect.set(windowFrames.mParentFrame);
 
-        if (LOCAL_LAYOUT) {
-            windowFrames.mCompatFrame.set(clientWindowFrames.frame);
+        windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
+        windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
+        windowFrames.mFrame.set(clientWindowFrames.frame);
 
-            windowFrames.mFrame.set(clientWindowFrames.frame);
-            windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
-            windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
-            if (mGlobalScale != 1f) {
-                // The frames sent from the client need to be adjusted to the real coordinate space.
-                windowFrames.mFrame.scale(mGlobalScale);
-                windowFrames.mDisplayFrame.scale(mGlobalScale);
-                windowFrames.mParentFrame.scale(mGlobalScale);
-            }
-        } else {
-            windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
-            windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
-            windowFrames.mFrame.set(clientWindowFrames.frame);
-
-            windowFrames.mCompatFrame.set(windowFrames.mFrame);
-            if (mInvGlobalScale != 1f) {
-                // Also, the scaled frame that we report to the app needs to be adjusted to be in
-                // its coordinate space.
-                windowFrames.mCompatFrame.scale(mInvGlobalScale);
-            }
+        windowFrames.mCompatFrame.set(windowFrames.mFrame);
+        if (mInvGlobalScale != 1f) {
+            // Also, the scaled frame that we report to the app needs to be adjusted to be in
+            // its coordinate space.
+            windowFrames.mCompatFrame.scale(mInvGlobalScale);
         }
         windowFrames.setParentFrameWasClippedByDisplayCutout(
                 clientWindowFrames.isParentFrameClippedByDisplayCutout);
@@ -1415,13 +1402,6 @@
 
         updateSourceFrame(windowFrames.mFrame);
 
-        if (LOCAL_LAYOUT) {
-            if (!mHaveFrame) {
-                // The first frame should not be considered as moved.
-                updateLastFrames();
-            }
-        }
-
         if (mActivityRecord != null && !mIsChildWindow) {
             mActivityRecord.layoutLetterbox(this);
         }
@@ -1917,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();
@@ -2629,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,
@@ -3823,6 +3821,10 @@
         return wpc != null && wpc.registeredForDisplayAreaConfigChanges();
     }
 
+    WindowProcessController getProcess() {
+        return mWpcForDisplayAreaConfigChanges;
+    }
+
     /**
      * Fills the given window frames and merged configuration for the client.
      *
@@ -3909,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();
@@ -3937,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();
@@ -5941,7 +5944,9 @@
         }
 
         mSyncSeqId++;
-        mPrepareSyncSeqId = mSyncSeqId;
+        if (getSyncMethod() == BLASTSyncEngine.METHOD_BLAST) {
+            mPrepareSyncSeqId = mSyncSeqId;
+        }
         requestRedrawForSync();
         return true;
     }
@@ -6014,6 +6019,7 @@
             postDrawTransaction = null;
             skipLayout = true;
         } else if (syncActive) {
+            // Currently in a Sync that is using BLAST.
             if (!syncStillPending) {
                 onSyncFinishedDrawing();
             }
@@ -6022,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 =
@@ -6058,7 +6067,7 @@
     }
 
     boolean hasWallpaperForLetterboxBackground() {
-        return mActivityRecord != null && mActivityRecord.hasWallpaperBackgroudForLetterbox();
+        return mActivityRecord != null && mActivityRecord.hasWallpaperBackgroundForLetterbox();
     }
 
     /**
@@ -6080,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/core/xsd/display-device-config/autobrightness.xsd b/services/core/xsd/display-device-config/autobrightness.xsd
new file mode 100644
index 0000000..477625a
--- /dev/null
+++ b/services/core/xsd/display-device-config/autobrightness.xsd
@@ -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.
+-->
+<xs:schema version="2.0"
+           elementFormDefault="qualified"
+           xmlns:xs="http://www.w3.org/2001/XMLSchema">
+    <xs:complexType name="autoBrightness">
+        <xs:sequence>
+            <!-- Sets the debounce for autoBrightness brightening in millis-->
+            <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
+                        minOccurs="0" maxOccurs="1">
+                <xs:annotation name="final"/>
+            </xs:element>
+            <!-- Sets the debounce for autoBrightness darkening in millis-->
+            <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
+                        minOccurs="0" maxOccurs="1">
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
+</xs:schema>
\ No newline at end of file
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 073b131c..bea5e2c 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -23,6 +23,7 @@
 <xs:schema version="2.0"
            elementFormDefault="qualified"
            xmlns:xs="http://www.w3.org/2001/XMLSchema">
+    <xs:include schemaLocation="autobrightness.xsd" />
     <xs:element name="displayConfiguration">
         <xs:complexType>
             <xs:sequence>
@@ -102,22 +103,6 @@
     </xs:element>
 
     <!-- Type definitions -->
-
-    <xs:complexType name="autoBrightness">
-        <xs:sequence>
-            <!-- Sets the debounce for autoBrightness brightening in millis-->
-            <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
-                        minOccurs="0" maxOccurs="1">
-                <xs:annotation name="final"/>
-            </xs:element>
-            <!-- Sets the debounce for autoBrightness darkening in millis-->
-            <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
-                        minOccurs="0" maxOccurs="1">
-                <xs:annotation name="final"/>
-            </xs:element>
-        </xs:sequence>
-    </xs:complexType>
-
     <xs:complexType name="displayQuirks">
         <xs:sequence>
             <xs:element name="quirk" type="xs:string" minOccurs="0" maxOccurs="unbounded" />
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/people/java/com/android/server/people/data/ConversationInfo.java b/services/people/java/com/android/server/people/data/ConversationInfo.java
index 16c4c29..6ead44a 100644
--- a/services/people/java/com/android/server/people/data/ConversationInfo.java
+++ b/services/people/java/com/android/server/people/data/ConversationInfo.java
@@ -26,6 +26,7 @@
 import android.content.pm.ShortcutInfo.ShortcutFlags;
 import android.net.Uri;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.Slog;
 import android.util.proto.ProtoInputStream;
 import android.util.proto.ProtoOutputStream;
@@ -37,6 +38,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
+import java.io.EOFException;
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -50,6 +52,11 @@
  * Represents a conversation that is provided by the app based on {@link ShortcutInfo}.
  */
 public class ConversationInfo {
+    private static final boolean DEBUG = false;
+
+    // Schema version for the backup payload. Must be incremented whenever fields are added in
+    // backup payload.
+    private static final int VERSION = 1;
 
     private static final String TAG = ConversationInfo.class.getSimpleName();
 
@@ -100,6 +107,8 @@
 
     private long mLastEventTimestamp;
 
+    private long mCreationTimestamp;
+
     @ShortcutFlags
     private int mShortcutFlags;
 
@@ -116,6 +125,7 @@
         mNotificationChannelId = builder.mNotificationChannelId;
         mParentNotificationChannelId = builder.mParentNotificationChannelId;
         mLastEventTimestamp = builder.mLastEventTimestamp;
+        mCreationTimestamp = builder.mCreationTimestamp;
         mShortcutFlags = builder.mShortcutFlags;
         mConversationFlags = builder.mConversationFlags;
         mCurrStatuses = builder.mCurrStatuses;
@@ -170,6 +180,13 @@
         return mLastEventTimestamp;
     }
 
+    /**
+     * Timestamp of the creation of the conversationInfo.
+     */
+    long getCreationTimestamp() {
+        return mCreationTimestamp;
+    }
+
     /** Whether the shortcut for this conversation is set long-lived by the app. */
     public boolean isShortcutLongLived() {
         return hasShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED);
@@ -241,6 +258,7 @@
                 && Objects.equals(mNotificationChannelId, other.mNotificationChannelId)
                 && Objects.equals(mParentNotificationChannelId, other.mParentNotificationChannelId)
                 && Objects.equals(mLastEventTimestamp, other.mLastEventTimestamp)
+                && mCreationTimestamp == other.mCreationTimestamp
                 && mShortcutFlags == other.mShortcutFlags
                 && mConversationFlags == other.mConversationFlags
                 && Objects.equals(mCurrStatuses, other.mCurrStatuses);
@@ -250,7 +268,7 @@
     public int hashCode() {
         return Objects.hash(mShortcutId, mLocusId, mContactUri, mContactPhoneNumber,
                 mNotificationChannelId, mParentNotificationChannelId, mLastEventTimestamp,
-                mShortcutFlags, mConversationFlags, mCurrStatuses);
+                mCreationTimestamp, mShortcutFlags, mConversationFlags, mCurrStatuses);
     }
 
     @Override
@@ -264,6 +282,7 @@
         sb.append(", notificationChannelId=").append(mNotificationChannelId);
         sb.append(", parentNotificationChannelId=").append(mParentNotificationChannelId);
         sb.append(", lastEventTimestamp=").append(mLastEventTimestamp);
+        sb.append(", creationTimestamp=").append(mCreationTimestamp);
         sb.append(", statuses=").append(mCurrStatuses);
         sb.append(", shortcutFlags=0x").append(Integer.toHexString(mShortcutFlags));
         sb.append(" [");
@@ -329,6 +348,7 @@
                     mParentNotificationChannelId);
         }
         protoOutputStream.write(ConversationInfoProto.LAST_EVENT_TIMESTAMP, mLastEventTimestamp);
+        protoOutputStream.write(ConversationInfoProto.CREATION_TIMESTAMP, mCreationTimestamp);
         protoOutputStream.write(ConversationInfoProto.SHORTCUT_FLAGS, mShortcutFlags);
         protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags);
         if (mContactPhoneNumber != null) {
@@ -352,6 +372,8 @@
             out.writeUTF(mContactPhoneNumber != null ? mContactPhoneNumber : "");
             out.writeUTF(mParentNotificationChannelId != null ? mParentNotificationChannelId : "");
             out.writeLong(mLastEventTimestamp);
+            out.writeInt(VERSION);
+            out.writeLong(mCreationTimestamp);
             // ConversationStatus is a transient object and not persisted
         } catch (IOException e) {
             Slog.e(TAG, "Failed to write fields to backup payload.", e);
@@ -399,6 +421,9 @@
                     builder.setLastEventTimestamp(protoInputStream.readLong(
                             ConversationInfoProto.LAST_EVENT_TIMESTAMP));
                     break;
+                case (int) ConversationInfoProto.CREATION_TIMESTAMP:
+                    builder.setCreationTimestamp(protoInputStream.readLong(
+                            ConversationInfoProto.CREATION_TIMESTAMP));
                 case (int) ConversationInfoProto.SHORTCUT_FLAGS:
                     builder.setShortcutFlags(protoInputStream.readInt(
                             ConversationInfoProto.SHORTCUT_FLAGS));
@@ -448,6 +473,10 @@
                 builder.setParentNotificationChannelId(parentNotificationChannelId);
             }
             builder.setLastEventTimestamp(in.readLong());
+            int payloadVersion = maybeReadVersion(in);
+            if (payloadVersion == 1) {
+                builder.setCreationTimestamp(in.readLong());
+            }
         } catch (IOException e) {
             Slog.e(TAG, "Failed to read conversation info fields from backup payload.", e);
             return null;
@@ -455,6 +484,16 @@
         return builder.build();
     }
 
+    private static int maybeReadVersion(DataInputStream in) throws IOException {
+        try {
+            return in.readInt();
+        } catch (EOFException eofException) {
+            // EOF is expected if using old backup payload protocol.
+            if (DEBUG) Log.d(TAG, "Eof reached for data stream, missing version number");
+            return 0;
+        }
+    }
+
     /**
      * Builder class for {@link ConversationInfo} objects.
      */
@@ -479,6 +518,8 @@
 
         private long mLastEventTimestamp;
 
+        private long mCreationTimestamp;
+
         @ShortcutFlags
         private int mShortcutFlags;
 
@@ -502,6 +543,7 @@
             mNotificationChannelId = conversationInfo.mNotificationChannelId;
             mParentNotificationChannelId = conversationInfo.mParentNotificationChannelId;
             mLastEventTimestamp = conversationInfo.mLastEventTimestamp;
+            mCreationTimestamp = conversationInfo.mCreationTimestamp;
             mShortcutFlags = conversationInfo.mShortcutFlags;
             mConversationFlags = conversationInfo.mConversationFlags;
             mCurrStatuses = conversationInfo.mCurrStatuses;
@@ -542,6 +584,11 @@
             return this;
         }
 
+        Builder setCreationTimestamp(long creationTimestamp) {
+            mCreationTimestamp = creationTimestamp;
+            return this;
+        }
+
         Builder setShortcutFlags(@ShortcutFlags int shortcutFlags) {
             mShortcutFlags = shortcutFlags;
             return this;
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index d305fc5..693f3a0 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -816,10 +816,18 @@
     }
 
     private boolean isCachedRecentConversation(ConversationInfo conversationInfo) {
+        return isEligibleForCleanUp(conversationInfo)
+                && conversationInfo.getLastEventTimestamp() > 0L;
+    }
+
+    /**
+     * Conversations that are cached and not customized are eligible for clean-up, even if they
+     * don't have an associated notification event with them.
+     */
+    private boolean isEligibleForCleanUp(ConversationInfo conversationInfo) {
         return conversationInfo.isShortcutCachedForNotification()
                 && Objects.equals(conversationInfo.getNotificationChannelId(),
-                conversationInfo.getParentNotificationChannelId())
-                && conversationInfo.getLastEventTimestamp() > 0L;
+                conversationInfo.getParentNotificationChannelId());
     }
 
     private boolean hasActiveNotifications(String packageName, @UserIdInt int userId,
@@ -842,14 +850,14 @@
         }
         // pair of <package name, conversation info>
         List<Pair<String, ConversationInfo>> cachedConvos = new ArrayList<>();
-        userData.forAllPackages(packageData ->
+        userData.forAllPackages(packageData -> {
                 packageData.forAllConversations(conversationInfo -> {
-                    if (isCachedRecentConversation(conversationInfo)) {
+                    if (isEligibleForCleanUp(conversationInfo)) {
                         cachedConvos.add(
                                 Pair.create(packageData.getPackageName(), conversationInfo));
                     }
-                })
-        );
+                });
+        });
         if (cachedConvos.size() <= targetCachedCount) {
             return;
         }
@@ -858,7 +866,9 @@
         PriorityQueue<Pair<String, ConversationInfo>> maxHeap = new PriorityQueue<>(
                 numToUncache + 1,
                 Comparator.comparingLong((Pair<String, ConversationInfo> pair) ->
-                        pair.second.getLastEventTimestamp()).reversed());
+                        Math.max(
+                            pair.second.getLastEventTimestamp(),
+                            pair.second.getCreationTimestamp())).reversed());
         for (Pair<String, ConversationInfo> cached : cachedConvos) {
             if (hasActiveNotifications(cached.first, userId, cached.second.getShortcutId())) {
                 continue;
@@ -893,7 +903,7 @@
         }
         ConversationInfo.Builder builder = oldConversationInfo != null
                 ? new ConversationInfo.Builder(oldConversationInfo)
-                : new ConversationInfo.Builder();
+                : new ConversationInfo.Builder().setCreationTimestamp(System.currentTimeMillis());
 
         builder.setShortcutId(shortcutInfo.getId());
         builder.setLocusId(shortcutInfo.getLocusId());
@@ -1326,7 +1336,8 @@
         }
     }
 
-    private void updateConversationStoreThenNotifyListeners(ConversationStore cs,
+    @VisibleForTesting
+    void updateConversationStoreThenNotifyListeners(ConversationStore cs,
             ConversationInfo modifiedConv,
             String packageName, int userId) {
         cs.addOrUpdate(modifiedConv);
diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 297538a..159285a 100644
--- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -61,8 +61,8 @@
 import com.android.server.backup.testing.TransportData;
 import com.android.server.backup.testing.TransportTestUtils.TransportMock;
 import com.android.server.backup.transport.TransportNotRegisteredException;
-import com.android.server.testing.shadows.ShadowBackupEligibilityRules;
 import com.android.server.testing.shadows.ShadowApplicationPackageManager;
+import com.android.server.testing.shadows.ShadowBackupEligibilityRules;
 import com.android.server.testing.shadows.ShadowBinder;
 import com.android.server.testing.shadows.ShadowKeyValueBackupJob;
 import com.android.server.testing.shadows.ShadowKeyValueBackupTask;
@@ -361,6 +361,26 @@
     }
 
     /**
+     * Test verifying that {@link UserBackupManagerService#selectBackupTransport(String)} does not
+     * switch the current transport to the inputted transport, when the inputted transport is not
+     * registered.
+     */
+    @Test
+    public void testSelectBackupTransport_nonRegisteredTransport() throws Exception {
+        setUpForSelectTransport();
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        when(mTransportManager.isTransportRegistered(eq(mNewTransport.transportName)))
+                .thenReturn(false);
+        UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks();
+
+        String oldTransport = backupManagerService.selectBackupTransport(
+                mNewTransport.transportName);
+
+        assertThat(getSettingsTransport()).isNotEqualTo(mNewTransport.transportName);
+        assertThat(oldTransport).isEqualTo(null);
+    }
+
+    /**
      * Test verifying that {@link UserBackupManagerService#selectBackupTransport(String)} throws a
      * {@link SecurityException} if the caller does not have backup permission.
      */
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index b53a2c6..9022db8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -17,8 +17,10 @@
 package com.android.server.app;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.server.app.GameManagerService.WRITE_SETTINGS;
 
 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.assertThrows;
@@ -35,11 +37,15 @@
 import static org.mockito.Mockito.when;
 
 import android.Manifest;
+import android.annotation.Nullable;
 import android.app.GameManager;
 import android.app.GameModeInfo;
 import android.app.GameState;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -97,6 +103,7 @@
     private PowerManagerInternal mMockPowerManager;
     @Mock
     private UserManager mMockUserManager;
+    private BroadcastReceiver mShutDownActionReceiver;
 
     // Stolen from ConnectivityServiceTest.MockContext
     class MockContext extends ContextWrapper {
@@ -165,6 +172,12 @@
             }
             throw new UnsupportedOperationException("Couldn't find system service: " + name);
         }
+
+        @Override
+        public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter) {
+            mShutDownActionReceiver =  receiver;
+            return null;
+        }
     }
 
     @Before
@@ -200,15 +213,16 @@
     @After
     public void tearDown() throws Exception {
         LocalServices.removeServiceForTest(PowerManagerInternal.class);
-        GameManagerService gameManagerService = new GameManagerService(mMockContext);
         if (mMockingSession != null) {
             mMockingSession.finishMocking();
         }
+        deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
     }
 
     private void startUser(GameManagerService gameManagerService, int userId) {
         UserInfo userInfo = new UserInfo(userId, "name", 0);
-        gameManagerService.onUserStarting(new SystemService.TargetUser(userInfo));
+        gameManagerService.onUserStarting(new SystemService.TargetUser(userInfo),
+                InstrumentationRegistry.getContext().getFilesDir());
         mTestLooper.dispatchAll();
     }
 
@@ -584,7 +598,7 @@
             gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
         }
         GameManagerService.GamePackageConfiguration config =
-                gameManagerService.getConfig(mPackageName);
+                gameManagerService.getConfig(mPackageName, USER_ID_1);
         assertEquals(scaling, config.getGameModeConfiguration(gameMode).getScaling(), 0.01f);
     }
 
@@ -594,7 +608,7 @@
 
         // Validate GamePackageConfiguration returns the correct value.
         GameManagerService.GamePackageConfiguration config =
-                gameManagerService.getConfig(mPackageName);
+                gameManagerService.getConfig(mPackageName, USER_ID_1);
         assertEquals(config.getGameModeConfiguration(gameMode).getUseAngle(), angleEnabled);
 
         // Validate GameManagerService.isAngleEnabled() returns the correct value.
@@ -607,7 +621,7 @@
 
         // Validate GamePackageConfiguration returns the correct value.
         GameManagerService.GamePackageConfiguration config =
-                gameManagerService.getConfig(mPackageName);
+                gameManagerService.getConfig(mPackageName, USER_ID_1);
         assertEquals(
                 loadingBoost, config.getGameModeConfiguration(gameMode).getLoadingBoostDuration());
 
@@ -623,7 +637,7 @@
             gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
         }
         GameManagerService.GamePackageConfiguration config =
-                gameManagerService.getConfig(mPackageName);
+                gameManagerService.getConfig(mPackageName, USER_ID_1);
         assertEquals(fps, config.getGameModeConfiguration(gameMode).getFps());
     }
 
@@ -1049,7 +1063,7 @@
                 mTestLooper.getLooper());
         startUser(gameManagerService, USER_ID_1);
         GameManagerService.GamePackageConfiguration config =
-                gameManagerService.getConfig(mPackageName);
+                gameManagerService.getConfig(mPackageName, USER_ID_1);
         assertEquals(90,
                 config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE).getFps());
         assertEquals(30, config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY).getFps());
@@ -1064,7 +1078,7 @@
                 mTestLooper.getLooper());
         startUser(gameManagerService, USER_ID_1);
         GameManagerService.GamePackageConfiguration config =
-                gameManagerService.getConfig(mPackageName);
+                gameManagerService.getConfig(mPackageName, USER_ID_1);
         assertEquals(0,
                 config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE).getFps());
         assertEquals(0, config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY).getFps());
@@ -1092,7 +1106,7 @@
         startUser(gameManagerService, USER_ID_1);
         gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
         GameManagerService.GamePackageConfiguration config =
-                gameManagerService.getConfig(mPackageName);
+                gameManagerService.getConfig(mPackageName, USER_ID_1);
         assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
     }
 
@@ -1339,10 +1353,15 @@
         mTestLooper.dispatchAll();
 
         /* Expected fileOutput (order may vary)
+         # user 1001:
          com.android.app2 <UID>   0   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.5,fps=60
          com.android.app1 <UID>   1   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
          com.android.app0 <UID>   0   2   angle=0,scaling=0.6,fps=120 3   angle=0,scaling=0.7,fps=30
 
+         # user 1002:
+         com.android.app2 <UID>   0   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
+         com.android.app1 <UID>   1   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
+         com.android.app0 <UID>   0   2   angle=0,scaling=0.5,fps=90  3   angle=0,scaling=0.7,fps=30
          The current game mode would only be set to non-zero if the current user have that game
          installed.
         */
@@ -1386,7 +1405,7 @@
         assertEquals(splitLine[3], "2");
         assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
         assertEquals(splitLine[5], "3");
-        assertEquals(splitLine[6], "angle=0,scaling=0.5,fps=60");
+        assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");
         splitLine = fileOutput.get(1).split("\\s+");
         assertEquals(splitLine[0], "com.android.app1");
         assertEquals(splitLine[2], "3");
@@ -1398,7 +1417,7 @@
         assertEquals(splitLine[0], "com.android.app0");
         assertEquals(splitLine[2], "0");
         assertEquals(splitLine[3], "2");
-        assertEquals(splitLine[4], "angle=0,scaling=0.6,fps=120");
+        assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
         assertEquals(splitLine[5], "3");
         assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");
 
@@ -1493,12 +1512,52 @@
 
     @Test
     public void testGetResolutionScalingFactor_noUserId() {
-        mockModifyGameModeDenied();
+        mockModifyGameModeGranted();
         mockDeviceConfigAll();
         GameManagerService gameManagerService =
                 new GameManagerService(mMockContext, mTestLooper.getLooper());
         startUser(gameManagerService, USER_ID_2);
-        assertEquals(-1f, gameManagerService.getResolutionScalingFactor(mPackageName,
-                GameManager.GAME_MODE_BATTERY, USER_ID_1), 0.001f);
+        assertThrows(IllegalArgumentException.class, () -> {
+            gameManagerService.getResolutionScalingFactor(mPackageName,
+                    GameManager.GAME_MODE_BATTERY, USER_ID_1);
+        });
+    }
+
+    @Test
+    public void testWritingSettingFile_onShutdown() throws InterruptedException {
+        mockModifyGameModeGranted();
+        mockDeviceConfigAll();
+        GameManagerService gameManagerService = new GameManagerService(mMockContext);
+        gameManagerService.onBootCompleted();
+        startUser(gameManagerService, USER_ID_1);
+        Thread.sleep(500);
+        gameManagerService.setGameModeConfigOverride("com.android.app1", USER_ID_1,
+                GameManager.GAME_MODE_BATTERY, "60", "0.5");
+        gameManagerService.setGameMode("com.android.app1", USER_ID_1,
+                GameManager.GAME_MODE_PERFORMANCE);
+        GameManagerSettings settings = new GameManagerSettings(
+                InstrumentationRegistry.getContext().getFilesDir());
+        Thread.sleep(500);
+        // no data written as delayed messages are queued
+        assertFalse(settings.readPersistentDataLocked());
+        assertTrue(gameManagerService.mHandler.hasEqualMessages(WRITE_SETTINGS, USER_ID_1));
+        Intent shutdown = new Intent();
+        shutdown.setAction(Intent.ACTION_SHUTDOWN);
+        mShutDownActionReceiver.onReceive(mMockContext, shutdown);
+        Thread.sleep(500);
+        // data is written on processing new message with no delay on shutdown,
+        // and all queued messages should be removed
+        assertTrue(settings.readPersistentDataLocked());
+        assertFalse(gameManagerService.mHandler.hasEqualMessages(WRITE_SETTINGS, USER_ID_1));
+    }
+
+    private static void deleteFolder(File folder) {
+        File[] files = folder.listFiles();
+        if (files != null) {
+            for (File file : files) {
+                deleteFolder(file);
+            }
+        }
+        folder.delete();
     }
 }
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 57a8013..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,8 +33,8 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.ServiceInfo;
 import android.os.BatteryManagerInternal;
 import android.os.RemoteException;
 import android.util.ArraySet;
@@ -76,6 +76,8 @@
     private JobSchedulerService mJobSchedulerService;
     @Mock
     private PackageManagerInternal mPackageManagerInternal;
+    @Mock
+    private PackageManager mPackageManager;
 
     @Before
     public void setUp() {
@@ -100,7 +102,12 @@
         // Capture the listeners.
         ArgumentCaptor<BroadcastReceiver> receiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
-        mFlexibilityController = new FlexibilityController(mJobSchedulerService);
+
+        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);
 
         verify(mContext).registerReceiver(receiverCaptor.capture(),
@@ -188,7 +195,7 @@
             JobInfo jobInfo) {
         JobStatus js = JobStatus.createFromJobInfo(
                 jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
-        js.serviceInfo = mock(ServiceInfo.class);
+        js.serviceProcessName = "testProcess";
         // Make sure tests aren't passing just because the default bucket is likely ACTIVE.
         js.setStandbyBucket(FREQUENT_INDEX);
         return js;
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 7a70e7a..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);
-
         // 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 6d9f48d..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
@@ -16,17 +16,27 @@
 
 package com.android.server.job.controllers;
 
+import static android.app.job.JobInfo.BIAS_FOREGROUND_SERVICE;
+import static android.app.job.JobInfo.BIAS_TOP_APP;
 import static android.app.job.JobInfo.NETWORK_TYPE_ANY;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.server.job.controllers.FlexibilityController.FcConstants.KEY_FLEXIBILITY_ENABLED;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_DEADLINE_PROXIMITY_LIMIT;
+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;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -40,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;
@@ -67,12 +78,12 @@
 public class FlexibilityControllerTest {
     private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
     private static final int SOURCE_USER_ID = 0;
-
+    private static final long FROZEN_TIME = 100L;
     private MockitoSession mMockingSession;
     private FlexibilityController mFlexibilityController;
     private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
     private JobStore mJobStore;
-    private FlexibilityController.FcConstants mFcConstants;
+    private FlexibilityController.FcConfig mFcConfig;
 
     @Mock
     private AlarmManager mAlarmManager;
@@ -80,6 +91,10 @@
     private Context mContext;
     @Mock
     private JobSchedulerService mJobSchedulerService;
+    @Mock
+    private PrefetchController mPrefetchController;
+    @Mock
+    private PackageManager mPackageManager;
 
     @Before
     public void setup() {
@@ -97,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(
@@ -117,13 +135,15 @@
                 .when(() -> LocalServices.getService(PackageManagerInternal.class));
         // Freeze the clocks at a moment in time
         JobSchedulerService.sSystemClock =
-                Clock.fixed(Instant.ofEpochMilli(100L), ZoneOffset.UTC);
+                Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
         JobSchedulerService.sElapsedRealtimeClock =
-                Clock.fixed(Instant.ofEpochMilli(100L), ZoneOffset.UTC);
+                Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
         // Initialize real objects.
-        mFlexibilityController = new FlexibilityController(mJobSchedulerService);
-        mFcConstants = mFlexibilityController.getFcConstants();
+        mFlexibilityController = new FlexibilityController(mJobSchedulerService,
+                mPrefetchController);
+        mFcConfig = mFlexibilityController.getFcConfig();
 
+        setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, 0L);
         setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
     }
 
@@ -138,7 +158,25 @@
         mDeviceConfigPropertiesBuilder.setBoolean(key, val);
         synchronized (mFlexibilityController.mLock) {
             mFlexibilityController.prepareForUpdatedConstantsLocked();
-            mFcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
+            mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
+            mFlexibilityController.onConstantsUpdatedLocked();
+        }
+    }
+
+    private void setDeviceConfigLong(String key, Long val) {
+        mDeviceConfigPropertiesBuilder.setLong(key, val);
+        synchronized (mFlexibilityController.mLock) {
+            mFlexibilityController.prepareForUpdatedConstantsLocked();
+            mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
+            mFlexibilityController.onConstantsUpdatedLocked();
+        }
+    }
+
+    private void setDeviceConfigString(String key, String val) {
+        mDeviceConfigPropertiesBuilder.setString(key, val);
+        synchronized (mFlexibilityController.mLock) {
+            mFlexibilityController.prepareForUpdatedConstantsLocked();
+            mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
             mFlexibilityController.onConstantsUpdatedLocked();
         }
     }
@@ -149,76 +187,309 @@
 
     private JobStatus createJobStatus(String testTag, JobInfo.Builder job) {
         JobInfo jobInfo = job.build();
-        return JobStatus.createFromJobInfo(
+        JobStatus js = JobStatus.createFromJobInfo(
                 jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+        js.enqueueTime = FROZEN_TIME;
+        return js;
+    }
+
+    /**
+     * Tests that the there are equally many percents to drop constraints as there are constraints
+     */
+    @Test
+    public void testDefaultVariableValues() {
+        assertEquals(NUM_FLEXIBLE_CONSTRAINTS,
+                mFlexibilityController.mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS.length
+        );
     }
 
     @Test
-    public void testGetNextConstraintDropTimeElapsed() {
+    public void testOnConstantsUpdated_DefaultFlexibility() {
+        JobStatus js = createJobStatus("testDefaultFlexibilityConfig", createJob(0));
+        assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
+        setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, false);
+        assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
+        setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
+        assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
+    }
+
+    @Test
+    public void testOnConstantsUpdated_DeadlineProximity() {
+        JobStatus js = createJobStatus("testDeadlineProximityConfig", createJob(0));
+        setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, Long.MAX_VALUE);
+        mFlexibilityController.mFlexibilityAlarmQueue
+                .scheduleDropNumConstraintsAlarm(js, FROZEN_TIME);
+        assertEquals(0, js.getNumRequiredFlexibleConstraints());
+    }
+
+    @Test
+    public void testOnConstantsUpdated_FallbackDeadline() {
+        JobStatus js = createJobStatus("testFallbackDeadlineConfig", createJob(0));
+        assertEquals(DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0L));
+        setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 100L);
+        assertEquals(100L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0L));
+    }
+
+    @Test
+    public void testOnConstantsUpdated_PercentsToDropConstraints() {
+        JobInfo.Builder jb = createJob(0).setOverrideDeadline(100L);
+        JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
+        assertEquals(150L,
+                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+        setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,20,30,40");
+        assertArrayEquals(
+                mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS,
+                new int[] {10, 20, 30, 40});
+        assertEquals(110L,
+                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+        js.adjustNumRequiredFlexibleConstraints(-1);
+        assertEquals(120L,
+                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+        js.adjustNumRequiredFlexibleConstraints(-1);
+        assertEquals(130L,
+                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+    }
+
+    @Test
+    public void testOnConstantsUpdated_PercentsToDropConstraintsInvalidValues() {
+        JobInfo.Builder jb = createJob(0).setOverrideDeadline(100L);
+        JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
+        js.enqueueTime = 100L;
+        assertEquals(150L,
+                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+        setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,20a,030,40");
+        assertEquals(150L,
+                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+        setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,40");
+        assertEquals(150L,
+                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+        setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "50,40,10,40");
+        assertEquals(150L,
+                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+    }
+
+    @Test
+    public void testGetNextConstraintDropTimeElapsedLocked() {
         long nextTimeToDropNumConstraints;
 
         // no delay, deadline
         JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000);
         JobStatus js = createJobStatus("time", jb);
-        js.enqueueTime = 100L;
 
-        assertEquals(0, js.getEarliestRunTime());
-        assertEquals(1100L, js.getLatestRunTimeElapsed());
-        assertEquals(100L, js.enqueueTime);
+        assertEquals(JobStatus.NO_EARLIEST_RUNTIME, js.getEarliestRunTime());
+        assertEquals(1000 + FROZEN_TIME, js.getLatestRunTimeElapsed());
+        assertEquals(FROZEN_TIME, js.enqueueTime);
 
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(600L, nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(700L, nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(800L, nextTimeToDropNumConstraints);
 
         // delay, no deadline
         jb = createJob(0).setMinimumLatency(800000L);
         js = createJobStatus("time", jb);
-        js.enqueueTime = 100L;
 
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(130400100, nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(156320100L, nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(182240100L, nextTimeToDropNumConstraints);
 
         // no delay, no deadline
         jb = createJob(0);
         js = createJobStatus("time", jb);
-        js.enqueueTime = 100L;
 
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(129600100, nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(155520100L, nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(181440100L, nextTimeToDropNumConstraints);
 
         // delay, deadline
         jb = createJob(0).setOverrideDeadline(1100).setMinimumLatency(100);
         js = createJobStatus("time", jb);
-        js.enqueueTime = 100L;
 
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(700L, nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(800L, nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
-        nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+        nextTimeToDropNumConstraints = mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js);
         assertEquals(900L, nextTimeToDropNumConstraints);
     }
 
     @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(nowElapsed), ZoneOffset.UTC);
+        assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
+
+        nowElapsed = 1400;
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+        assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
+
+        nowElapsed = 950 + FROZEN_TIME;
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+        assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
+
+        nowElapsed = FROZEN_TIME;
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+        long delay = 100;
+        deadline = 1100;
+        jb = createJob(0).setOverrideDeadline(deadline).setMinimumLatency(delay);
+        js = createJobStatus("time", jb);
+
+        assertEquals(FROZEN_TIME + delay,
+                mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+        assertEquals(deadline + FROZEN_TIME,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME + delay));
+
+        nowElapsed = 600 + FROZEN_TIME + delay;
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+
+        assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
+
+        nowElapsed = 1400;
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+        assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
+
+        nowElapsed = 950 + FROZEN_TIME + delay;
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+        assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
+    }
+
+    @Test
+    public void testGetLifeCycleBeginningElapsedLocked_Prefetch() {
+        // prefetch with lifecycle
+        when(mPrefetchController.getLaunchTimeThresholdMs()).thenReturn(700L);
+        JobInfo.Builder jb = createJob(0).setPrefetch(true);
+        JobStatus js = createJobStatus("time", jb);
+        when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(900L);
+        assertEquals(900L - 700L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+        // prefetch with enqueue
+        jb = createJob(0).setPrefetch(true);
+        js = createJobStatus("time", jb);
+        assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+        // prefetch with delay
+        jb = createJob(0).setPrefetch(true).setMinimumLatency(200);
+        js = createJobStatus("time", jb);
+        assertEquals(200 + FROZEN_TIME, js.getEarliestRunTime());
+        assertEquals(js.getEarliestRunTime(),
+                mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+        // prefetch without estimate
+        mFlexibilityController.mPrefetchLifeCycleStart
+                .add(js.getUserId(), js.getSourcePackageName(), 500L);
+        when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(Long.MAX_VALUE);
+        jb = createJob(0).setPrefetch(true);
+        js = createJobStatus("time", jb);
+        assertEquals(500L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+    }
+
+    @Test
+    public void testGetLifeCycleBeginningElapsedLocked_NonPrefetch() {
+        // delay
+        long delay = 100;
+        JobInfo.Builder jb = createJob(0).setMinimumLatency(delay);
+        JobStatus js = createJobStatus("time", jb);
+        assertEquals(delay + FROZEN_TIME,
+                mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+        // no delay
+        jb = createJob(0);
+        js = createJobStatus("time", jb);
+        assertEquals(FROZEN_TIME,
+                mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+    }
+
+    @Test
+    public void testGetLifeCycleEndElapsedLocked_Prefetch() {
+        // prefetch no estimate
+        JobInfo.Builder jb = createJob(0).setPrefetch(true);
+        JobStatus js = createJobStatus("time", jb);
+        when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(Long.MAX_VALUE);
+        assertEquals(Long.MAX_VALUE, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+        // prefetch with estimate
+        jb = createJob(0).setPrefetch(true);
+        js = createJobStatus("time", jb);
+        when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(1000L);
+        assertEquals(1000L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+    }
+
+    @Test
+    public void testGetLifeCycleEndElapsedLocked_NonPrefetch() {
+        // deadline
+        JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000L);
+        JobStatus js = createJobStatus("time", jb);
+        assertEquals(1000L + FROZEN_TIME,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+        // no deadline
+        jb = createJob(0);
+        js = createJobStatus("time", jb);
+        assertEquals(100L + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, 100L));
+    }
+
+    @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.
@@ -233,8 +504,9 @@
     public void testFlexibilityTracker() {
         FlexibilityController.FlexibilityTracker flexTracker =
                 mFlexibilityController.new
-                        FlexibilityTracker(FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS);
-
+                        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++) {
@@ -254,34 +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());
-            assertEquals(0, trackedJobs.get(1).size());
-            assertEquals(2, trackedJobs.get(2).size());
-            assertEquals(0, trackedJobs.get(3).size());
-
-            flexTracker.remove(jobs[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(1, trackedJobs.get(2).size());
-            assertEquals(0, trackedJobs.get(3).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(2, trackedJobs.get(0).size());
+            assertEquals(0, trackedJobs.get(1).size());
+            assertEquals(0, trackedJobs.get(2).size());
+            assertEquals(1, trackedJobs.get(3).size());
+            assertEquals(0, trackedJobs.get(4).size());
+
+            flexTracker.resetJobNumDroppedConstraints(jobs[0], 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.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(1, trackedJobs.get(3).size());
+            assertEquals(0, trackedJobs.get(4).size());
         }
     }
 
@@ -303,14 +606,6 @@
     }
 
     @Test
-    public void testExceptions_Prefetch() {
-        JobInfo.Builder jb = createJob(0);
-        jb.setPrefetch(true);
-        JobStatus js = createJobStatus("testExceptions_Prefetch", jb);
-        assertFalse(js.hasFlexibilityConstraint());
-    }
-
-    @Test
     public void testExceptions_NoFlexibleConstraints() {
         JobInfo.Builder jb = createJob(0);
         jb.setRequiresDeviceIdle(true);
@@ -321,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);
@@ -368,20 +672,20 @@
 
     @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));
     }
 
     @Test
     public void testSetConstraintSatisfied_Jobs() {
         JobInfo.Builder jb;
-        int[] constraintPermutations = {
+        int[] constraintCombinations = {
                 CONSTRAINT_IDLE & CONSTRAINT_CHARGING & CONSTRAINT_BATTERY_NOT_LOW,
                 CONSTRAINT_IDLE & CONSTRAINT_BATTERY_NOT_LOW,
                 CONSTRAINT_IDLE & CONSTRAINT_CHARGING,
@@ -393,9 +697,9 @@
         };
 
         int constraints;
-        for (int i = 0; i < constraintPermutations.length; i++) {
+        for (int i = 0; i < constraintCombinations.length; i++) {
             jb = createJob(i);
-            constraints = constraintPermutations[i];
+            constraints = constraintCombinations[i];
             jb.setRequiresDeviceIdle((constraints & CONSTRAINT_IDLE) != 0);
             jb.setRequiresBatteryNotLow((constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0);
             jb.setRequiresCharging((constraints & CONSTRAINT_CHARGING) != 0);
@@ -404,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 < constraintPermutations.length; i++) {
-            constraints = constraintPermutations[i];
+        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) {
@@ -427,6 +732,162 @@
         }
     }
 
+    @Test
+    public void testResetJobNumDroppedConstraints() {
+        JobInfo.Builder jb = createJob(22).setOverrideDeadline(100L);
+        JobStatus js = createJobStatus("testResetJobNumDroppedConstraints", jb);
+        js.adjustNumRequiredFlexibleConstraints(3);
+        long nowElapsed;
+
+        mFlexibilityController.mFlexibilityTracker.add(js);
+
+        assertEquals(3, js.getNumRequiredFlexibleConstraints());
+        assertEquals(0, js.getNumDroppedFlexibleConstraints());
+        assertEquals(1, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
+
+
+        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, nowElapsed);
+
+        assertEquals(2, js.getNumRequiredFlexibleConstraints());
+        assertEquals(1, js.getNumDroppedFlexibleConstraints());
+        assertEquals(1, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
+
+        nowElapsed = 140L;
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+
+        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(nowElapsed), ZoneOffset.UTC);
+
+        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
+
+        assertEquals(0, js.getNumRequiredFlexibleConstraints());
+        assertEquals(3, js.getNumDroppedFlexibleConstraints());
+
+        nowElapsed = 165L;
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+
+        mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
+
+        assertEquals(1, js.getNumRequiredFlexibleConstraints());
+        assertEquals(2, js.getNumDroppedFlexibleConstraints());
+        assertEquals(1, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(1).size());
+    }
+
+    @Test
+    public void testOnPrefetchCacheUpdated() {
+        ArraySet<JobStatus> jobs = new ArraySet<JobStatus>();
+        JobInfo.Builder jb = createJob(22).setPrefetch(true);
+        JobStatus js = createJobStatus("onPrefetchCacheUpdated", jb);
+        jobs.add(js);
+        when(mPrefetchController.getLaunchTimeThresholdMs()).thenReturn(7 * HOUR_IN_MILLIS);
+        when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(
+                1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS);
+
+        mFlexibilityController.maybeStartTrackingJobLocked(js, null);
+
+        final long nowElapsed = 150L;
+        JobSchedulerService.sElapsedRealtimeClock =
+                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,
+                nowElapsed);
+
+        assertEquals(150L,
+                (long) mFlexibilityController.mPrefetchLifeCycleStart
+                        .get(js.getSourceUserId(), js.getSourcePackageName()));
+        assertEquals(150L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+        assertEquals(1150L,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(js, 150L));
+        assertEquals(0, mFlexibilityController.getCurPercentOfLifecycleLocked(js, FROZEN_TIME));
+        assertEquals(650L, mFlexibilityController
+                .getNextConstraintDropTimeElapsedLocked(js));
+        assertEquals(3, js.getNumRequiredFlexibleConstraints());
+        assertEquals(1, mFlexibilityController
+                .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
+    }
+
+    /**
+     * The beginning of a lifecycle for prefetch jobs includes the cached maximum of the last time
+     * the estimated launch time was updated and the last time the app was opened.
+     * When the UID bias updates it means the app might have been opened.
+     * This tests that the cached value is updated properly.
+     */
+    @Test
+    public void testUidUpdatesLifeCycle() {
+        JobInfo.Builder jb = createJob(0).setPrefetch(true);
+        JobStatus js = createJobStatus("uidTest", jb);
+        mFlexibilityController.maybeStartTrackingJobLocked(js, null);
+        mJobStore.add(js);
+
+        final ArraySet<String> pkgs = new ArraySet<>();
+        pkgs.add(js.getSourcePackageName());
+        when(mJobSchedulerService.getPackagesForUidLocked(js.getUid())).thenReturn(pkgs);
+
+        setUidBias(js.getUid(), BIAS_TOP_APP);
+        setUidBias(js.getUid(), BIAS_FOREGROUND_SERVICE);
+        assertEquals(100L, (long) mFlexibilityController.mPrefetchLifeCycleStart
+                .getOrDefault(js.getSourceUserId(), js.getSourcePackageName(), 0L));
+
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(Instant.ofEpochMilli(50L), ZoneOffset.UTC);
+
+        setUidBias(js.getUid(), BIAS_TOP_APP);
+        setUidBias(js.getUid(), BIAS_FOREGROUND_SERVICE);
+        assertEquals(100L, (long) mFlexibilityController
+                .mPrefetchLifeCycleStart.get(js.getSourceUserId(), js.getSourcePackageName()));
+
+    }
+
+    @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/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index f15e60f..149ae0b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -33,6 +33,7 @@
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONTENT_TRIGGER;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_DEADLINE;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_DEVICE_NOT_DOZING;
+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.CONSTRAINT_STORAGE_NOT_LOW;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_TIMING_DELAY;
@@ -50,7 +51,6 @@
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.ServiceInfo;
 import android.net.Uri;
 import android.os.SystemClock;
 import android.provider.MediaStore;
@@ -790,6 +790,83 @@
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
     }
 
+    @Test
+    public void testWouldBeReadyWithConstraint_FlexibilityDoesNotAffectReadiness() {
+        final JobStatus job = createJobStatus(
+                new JobInfo.Builder(101, new ComponentName("foo", "bar")).build());
+
+        markImplicitConstraintsSatisfied(job, false);
+        job.setFlexibilityConstraintSatisfied(0, false);
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_FLEXIBLE));
+
+        markImplicitConstraintsSatisfied(job, true);
+        job.setFlexibilityConstraintSatisfied(0, false);
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_FLEXIBLE));
+
+        markImplicitConstraintsSatisfied(job, false);
+        job.setFlexibilityConstraintSatisfied(0, true);
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
+        assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_FLEXIBLE));
+
+        markImplicitConstraintsSatisfied(job, true);
+        job.setFlexibilityConstraintSatisfied(0, true);
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
+        assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_FLEXIBLE));
+    }
+
+    @Test
+    public void testReadinessStatusWithConstraint_FlexibilityConstraint() {
+        final JobStatus job = createJobStatus(
+                new JobInfo.Builder(101, new ComponentName("foo", "bar")).build());
+        job.setConstraintSatisfied(CONSTRAINT_FLEXIBLE, sElapsedRealtimeClock.millis(), false);
+        markImplicitConstraintsSatisfied(job, true);
+        assertTrue(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true));
+        assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, false));
+
+        markImplicitConstraintsSatisfied(job, false);
+        assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true));
+        assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, false));
+
+        job.setConstraintSatisfied(CONSTRAINT_FLEXIBLE, sElapsedRealtimeClock.millis(), true);
+        markImplicitConstraintsSatisfied(job, true);
+        assertTrue(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true));
+        assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, false));
+
+        markImplicitConstraintsSatisfied(job, false);
+        assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true));
+        assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, false));
+    }
+
     private void markImplicitConstraintsSatisfied(JobStatus job, boolean isSatisfied) {
         job.setQuotaConstraintSatisfied(sElapsedRealtimeClock.millis(), isSatisfied);
         job.setTareWealthConstraintSatisfied(sElapsedRealtimeClock.millis(), isSatisfied);
@@ -797,7 +874,6 @@
                 sElapsedRealtimeClock.millis(), isSatisfied, false);
         job.setBackgroundNotRestrictedConstraintSatisfied(
                 sElapsedRealtimeClock.millis(), isSatisfied, false);
-        job.setFlexibilityConstraintSatisfied(sElapsedRealtimeClock.millis(), isSatisfied);
     }
 
     private static JobStatus createJobStatus(long earliestRunTimeElapsedMillis,
@@ -810,7 +886,7 @@
 
     private static JobStatus createJobStatus(JobInfo job) {
         JobStatus jobStatus = JobStatus.createFromJobInfo(job, 0, null, -1, "JobStatusTest");
-        jobStatus.serviceInfo = mock(ServiceInfo.class);
+        jobStatus.serviceProcessName = "testProcess";
         return jobStatus;
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index bcdfc35..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
@@ -47,7 +47,6 @@
 import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.pm.ServiceInfo;
 import android.os.Looper;
 import android.os.Process;
 import android.os.SystemClock;
@@ -185,7 +184,7 @@
             JobInfo jobInfo) {
         JobStatus js = JobStatus.createFromJobInfo(
                 jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
-        js.serviceInfo = mock(ServiceInfo.class);
+        js.serviceProcessName = "testProcess";
         js.setStandbyBucket(FREQUENT_INDEX);
         // Make sure Doze and background-not-restricted don't affect tests.
         js.setDeviceNotDozingConstraintSatisfied(/* nowElapsed */ sElapsedRealtimeClock.millis(),
@@ -480,4 +479,32 @@
 
         sSystemClock = getShiftedClock(sSystemClock, HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
     }
+
+    @Test
+    public void testRegisterOnPrefetchChangedListener() {
+        when(mUsageStatsManagerInternal
+                .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
+                .thenReturn(sSystemClock.millis() + 10 * HOUR_IN_MILLIS);
+        // Needs to get wrapped in an array to get accessed by an inner class.
+        final boolean[] onPrefetchCacheChangedCalled = new boolean[1];
+        final PrefetchController.PrefetchChangedListener prefetchChangedListener =
+                new PrefetchController.PrefetchChangedListener() {
+                    @Override
+                    public void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs,
+                            int userId, String pkgName, long prevEstimatedLaunchTime,
+                            long newEstimatedLaunchTime, long nowElapsed) {
+                        onPrefetchCacheChangedCalled[0] = true;
+                    }
+                };
+        mPrefetchController.registerPrefetchChangedListener(prefetchChangedListener);
+
+        JobStatus jobStatus = createJobStatus("testRegisterOnPrefetchChangedListener", 1);
+        trackJobs(jobStatus);
+
+        mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID,
+                SOURCE_PACKAGE, sSystemClock.millis() + HOUR_IN_MILLIS);
+        verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
+
+        assertTrue(onPrefetchCacheChangedCalled[0]);
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 7048fb2..9407968 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -70,7 +70,6 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.ServiceInfo;
 import android.os.BatteryManagerInternal;
 import android.os.Handler;
 import android.os.Looper;
@@ -385,7 +384,7 @@
             JobInfo jobInfo) {
         JobStatus js = JobStatus.createFromJobInfo(
                 jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
-        js.serviceInfo = mock(ServiceInfo.class);
+        js.serviceProcessName = "testProcess";
         // Make sure tests aren't passing just because the default bucket is likely ACTIVE.
         js.setStandbyBucket(FREQUENT_INDEX);
         // Make sure Doze and background-not-restricted don't affect tests.
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
index d7fef60..5b92706 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
@@ -58,8 +58,10 @@
         void onRegistrationAdded(Consumer<TestListenerRegistration> consumer,
                 TestListenerRegistration registration);
 
-        void onRegistrationReplaced(Consumer<TestListenerRegistration> consumer,
-                TestListenerRegistration oldRegistration, TestListenerRegistration newRegistration);
+        void onRegistrationReplaced(Consumer<TestListenerRegistration> oldConsumer,
+                TestListenerRegistration oldRegistration,
+                Consumer<TestListenerRegistration> newConsumer,
+                TestListenerRegistration newRegistration);
 
         void onRegistrationRemoved(Consumer<TestListenerRegistration> consumer,
                 TestListenerRegistration registration);
@@ -93,10 +95,10 @@
         assertThat(mMultiplexer.mMergedRequest).isEqualTo(0);
 
         mMultiplexer.addListener(1, consumer);
-        mInOrder.verify(mCallbacks).onRegistrationRemoved(eq(consumer),
-                any(TestListenerRegistration.class));
         mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(consumer),
-                any(TestListenerRegistration.class), any(TestListenerRegistration.class));
+                any(TestListenerRegistration.class),
+                eq(consumer),
+                any(TestListenerRegistration.class));
         assertThat(mMultiplexer.mRegistered).isTrue();
         assertThat(mMultiplexer.mMergedRequest).isEqualTo(1);
 
@@ -115,10 +117,10 @@
                 any(TestListenerRegistration.class));
         mInOrder.verify(mCallbacks).onActive();
         mMultiplexer.replaceListener(1, oldConsumer, consumer);
-        mInOrder.verify(mCallbacks).onRegistrationRemoved(eq(oldConsumer),
+        mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(oldConsumer),
+                any(TestListenerRegistration.class),
+                eq(consumer),
                 any(TestListenerRegistration.class));
-        mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(consumer),
-                any(TestListenerRegistration.class), any(TestListenerRegistration.class));
         assertThat(mMultiplexer.mRegistered).isTrue();
         assertThat(mMultiplexer.mMergedRequest).isEqualTo(1);
 
@@ -352,13 +354,19 @@
     }
 
     private static class TestListenerRegistration extends
-            RequestListenerRegistration<Integer, Consumer<TestListenerRegistration>> {
+            ListenerRegistration<Consumer<TestListenerRegistration>> {
 
+        private final Integer mInteger;
         boolean mActive = true;
 
         protected TestListenerRegistration(Integer integer,
                 Consumer<TestListenerRegistration> consumer) {
-            super(DIRECT_EXECUTOR, integer, consumer);
+            super(DIRECT_EXECUTOR, consumer);
+            mInteger = integer;
+        }
+
+        public Integer getInteger() {
+            return mInteger;
         }
     }
 
@@ -375,11 +383,6 @@
             mCallbacks = callbacks;
         }
 
-        @Override
-        public String getTag() {
-            return "TestMultiplexer";
-        }
-
         public void addListener(Integer request, Consumer<TestListenerRegistration> consumer) {
             putRegistration(consumer, new TestListenerRegistration(request, consumer));
         }
@@ -399,9 +402,9 @@
             removeRegistration(consumer, registration);
         }
 
-        public void setActive(Integer request, boolean active) {
+        public void setActive(Integer integer, boolean active) {
             updateRegistrations(testRegistration -> {
-                if (testRegistration.getRequest().equals(request)) {
+                if (testRegistration.getInteger().equals(integer)) {
                     testRegistration.mActive = active;
                     return true;
                 }
@@ -458,10 +461,11 @@
         }
 
         @Override
-        protected void onRegistrationReplaced(Consumer<TestListenerRegistration> consumer,
+        protected void onRegistrationReplaced(Consumer<TestListenerRegistration> oldKey,
                 TestListenerRegistration oldRegistration,
+                Consumer<TestListenerRegistration> newKey,
                 TestListenerRegistration newRegistration) {
-            mCallbacks.onRegistrationReplaced(consumer, oldRegistration, newRegistration);
+            mCallbacks.onRegistrationReplaced(oldKey, oldRegistration, newKey, newRegistration);
         }
 
         @Override
@@ -475,8 +479,8 @@
                 Collection<TestListenerRegistration> testRegistrations) {
             int max = Integer.MIN_VALUE;
             for (TestListenerRegistration registration : testRegistrations) {
-                if (registration.getRequest() > max) {
-                    max = registration.getRequest();
+                if (registration.getInteger() > max) {
+                    max = registration.getInteger();
                 }
             }
             mMergeCount++;
@@ -493,7 +497,7 @@
         @Override
         protected void onRegistrationAdded(Consumer<TestListenerRegistration> consumer,
                 TestListenerRegistration registration) {
-            addListener(registration.getRequest(), consumer);
+            addListener(registration.getInteger(), consumer);
         }
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 71cc65b..0ac1443 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -32,7 +32,6 @@
 import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
 import static com.android.server.location.LocationUtils.createLocation;
 import static com.android.server.location.LocationUtils.createLocationResult;
-import static com.android.server.location.listeners.RemoteListenerRegistration.IN_PROCESS_EXECUTOR;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -534,7 +533,7 @@
                 listener);
 
         CountDownLatch blocker = new CountDownLatch(1);
-        IN_PROCESS_EXECUTOR.execute(() -> {
+        FgThread.getExecutor().execute(() -> {
             try {
                 blocker.await();
             } catch (InterruptedException e) {
@@ -661,7 +660,7 @@
                 listener);
 
         CountDownLatch blocker = new CountDownLatch(1);
-        IN_PROCESS_EXECUTOR.execute(() -> {
+        FgThread.getExecutor().execute(() -> {
             try {
                 blocker.await();
             } catch (InterruptedException e) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BroadcastHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/BroadcastHelperTest.kt
deleted file mode 100644
index d25649e..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BroadcastHelperTest.kt
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm
-
-import com.android.server.testutils.whenever
-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
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@RunWith(JUnit4::class)
-class BroadcastHelperTest {
-
-    companion object {
-        const val TEST_PACKAGE_1 = "com.android.test.package1"
-        const val TEST_PACKAGE_2 = "com.android.test.package2"
-        const val TEST_UID_1 = 10100
-        const val TEST_UID_2 = 10101
-        const val TEST_USER_ID = 0
-    }
-
-    lateinit var broadcastHelper: BroadcastHelper
-    lateinit var packagesToChange: Array<String>
-    lateinit var uidsToChange: IntArray
-
-    @Mock
-    lateinit var snapshot: Computer
-
-    @Rule
-    @JvmField
-    val rule = MockSystemRule()
-
-    @Before
-    open fun setup() {
-        MockitoAnnotations.initMocks(this)
-        rule.system().stageNominalSystemState()
-        broadcastHelper = BroadcastHelper(rule.mocks().injector)
-        packagesToChange = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
-        uidsToChange = intArrayOf(TEST_UID_1, TEST_UID_2)
-    }
-
-    @Test
-    fun getBroadcastParams_withSameVisibilityAllowList_shouldGroup() {
-        val allowList = intArrayOf(10001, 10002, 10003)
-        mockVisibilityAllowList(TEST_PACKAGE_1, allowList)
-        mockVisibilityAllowList(TEST_PACKAGE_2, allowList)
-
-        val broadcastParams: List<BroadcastParams> = broadcastHelper.getBroadcastParams(
-                snapshot, packagesToChange, uidsToChange, TEST_USER_ID)
-
-        assertThat(broadcastParams).hasSize(1)
-        assertThat(broadcastParams[0].packageNames).asList().containsExactlyElementsIn(
-                packagesToChange.toCollection(ArrayList()))
-        assertThat(broadcastParams[0].uids).asList().containsExactlyElementsIn(
-                uidsToChange.toCollection(ArrayList()))
-    }
-
-    @Test
-    fun getBroadcastParams_withDifferentVisibilityAllowList_shouldNotGroup() {
-        val allowList1 = intArrayOf(10001, 10002, 10003)
-        val allowList2 = intArrayOf(10001, 10002, 10007)
-        mockVisibilityAllowList(TEST_PACKAGE_1, allowList1)
-        mockVisibilityAllowList(TEST_PACKAGE_2, allowList2)
-
-        val broadcastParams: List<BroadcastParams> = broadcastHelper.getBroadcastParams(
-                snapshot, packagesToChange, uidsToChange, TEST_USER_ID)
-
-        assertThat(broadcastParams).hasSize(2)
-        broadcastParams.forEachIndexed { i, params ->
-            val changedPackages = params.packageNames
-            val changedUids = params.uids
-            assertThat(changedPackages[0]).isEqualTo(packagesToChange[i])
-            assertThat(changedUids[0]).isEqualTo(uidsToChange[i])
-        }
-    }
-
-    @Test
-    fun getBroadcastParams_withNullVisibilityAllowList_shouldNotGroup() {
-        val allowList = intArrayOf(10001, 10002, 10003)
-        mockVisibilityAllowList(TEST_PACKAGE_1, allowList)
-        mockVisibilityAllowList(TEST_PACKAGE_2, null)
-
-        val broadcastParams: List<BroadcastParams> = broadcastHelper.getBroadcastParams(
-                snapshot, packagesToChange, uidsToChange, TEST_USER_ID)
-
-        assertThat(broadcastParams).hasSize(2)
-        broadcastParams.forEachIndexed { i, params ->
-            val changedPackages = params.packageNames
-            val changedUids = params.uids
-            assertThat(changedPackages[0]).isEqualTo(packagesToChange[i])
-            assertThat(changedUids[0]).isEqualTo(uidsToChange[i])
-        }
-    }
-
-    private fun mockVisibilityAllowList(pkgName: String, list: IntArray?) {
-        whenever(snapshot.getVisibilityAllowList(pkgName, TEST_USER_ID))
-                .thenReturn(list ?: IntArray(0))
-    }
-}
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
index d8770e5..9f1cec3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
@@ -29,7 +29,6 @@
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.never
-import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 
 @RunWith(JUnit4::class)
@@ -53,7 +52,7 @@
         verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
         verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED),
                 nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
-                nullable(), nullable(), nullable())
+                nullable(), nullable(), nullable(), nullable())
 
         val modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
         val distractionFlags = bundleCaptor.value.getInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS)
@@ -78,7 +77,8 @@
         verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
         verify(broadcastHelper, never()).sendPackageBroadcast(
                 eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), bundleCaptor.capture(),
-                anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+                anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(),
+                nullable())
         assertThat(unactionedPackages).isEmpty()
     }
 
@@ -156,7 +156,7 @@
         verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
         verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED),
                 nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
-                nullable(), nullable(), nullable())
+                nullable(), nullable(), nullable(), nullable())
         val modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
         val distractionFlags = bundleCaptor.value.getInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS)
         assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
@@ -172,7 +172,7 @@
         verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
         verify(broadcastHelper, never()).sendPackageBroadcast(eq(
                 Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(),
-                nullable(), nullable(), any(), nullable(), nullable(), nullable())
+                nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable())
     }
 
     @Test
@@ -183,7 +183,7 @@
         verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
         verify(broadcastHelper, never()).sendPackageBroadcast(eq(
                 Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(),
-                nullable(), nullable(), any(), nullable(), nullable(), nullable())
+                nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable())
 
         distractingPackageHelper.removeDistractingPackageRestrictions(pms.snapshotComputer(),
                 arrayOfNulls(0), TEST_USER_ID)
@@ -191,18 +191,17 @@
         verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
         verify(broadcastHelper, never()).sendPackageBroadcast(eq(
                 Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(),
-                nullable(), nullable(), any(), nullable(), nullable(), nullable())
+                nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable())
     }
 
     @Test
-    fun sendDistractingPackagesChanged_withSameVisibilityAllowList() {
-        distractingPackageHelper.sendDistractingPackagesChanged(pms.snapshotComputer(),
-                packagesToChange, uidsToChange, TEST_USER_ID,
-                PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
+    fun sendDistractingPackagesChanged() {
+        distractingPackageHelper.sendDistractingPackagesChanged(packagesToChange, uidsToChange,
+                TEST_USER_ID, PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
         testHandler.flush()
         verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED),
                 nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
-                nullable(), nullable(), nullable())
+                nullable(), nullable(), nullable(), nullable())
 
         var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
         var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
@@ -210,49 +209,4 @@
         assertThat(changedUids).asList().containsExactly(
                 packageSetting1.appId, packageSetting2.appId)
     }
-
-    @Test
-    fun sendDistractingPackagesChanged_withDifferentVisibilityAllowList() {
-        mockDividedSeparatedBroadcastList(
-                intArrayOf(10001, 10002, 10003), intArrayOf(10001, 10002, 10007))
-
-        distractingPackageHelper.sendDistractingPackagesChanged(pms.snapshotComputer(),
-                packagesToChange, uidsToChange, TEST_USER_ID,
-                PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
-        testHandler.flush()
-        verify(broadcastHelper, times(2)).sendPackageBroadcast(
-                eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), bundleCaptor.capture(),
-                anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
-
-        bundleCaptor.allValues.forEachIndexed { i, it ->
-            var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
-            var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
-            assertThat(changedPackages?.size).isEqualTo(1)
-            assertThat(changedUids?.size).isEqualTo(1)
-            assertThat(changedPackages?.get(0)).isEqualTo(packagesToChange[i])
-            assertThat(changedUids?.get(0)).isEqualTo(uidsToChange[i])
-        }
-    }
-
-    @Test
-    fun sendDistractingPackagesChanged_withNullVisibilityAllowList() {
-        mockDividedSeparatedBroadcastList(intArrayOf(10001, 10002, 10003), null)
-
-        distractingPackageHelper.sendDistractingPackagesChanged(pms.snapshotComputer(),
-                packagesToChange, uidsToChange, TEST_USER_ID,
-                PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
-        testHandler.flush()
-        verify(broadcastHelper, times(2)).sendPackageBroadcast(
-                eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), bundleCaptor.capture(),
-                anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
-
-        bundleCaptor.allValues.forEachIndexed { i, it ->
-            var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
-            var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
-            assertThat(changedPackages?.size).isEqualTo(1)
-            assertThat(changedUids?.size).isEqualTo(1)
-            assertThat(changedPackages?.get(0)).isEqualTo(packagesToChange[i])
-            assertThat(changedUids?.get(0)).isEqualTo(uidsToChange[i])
-        }
-    }
-}
\ No newline at end of file
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
index 5f9ef58..4ac5e86 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
@@ -28,7 +28,6 @@
 import org.junit.Before
 import org.junit.Rule
 import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
@@ -102,7 +101,6 @@
         whenever(rule.mocks().userManagerService.hasUserRestriction(
                 eq(UserManager.DISALLOW_UNINSTALL_APPS), eq(TEST_USER_ID))).thenReturn(true)
         mockKnownPackages(pms)
-        mockUnifiedSeparatedBroadcastList()
     }
 
     private fun mockKnownPackages(pms: PackageManagerService) {
@@ -139,26 +137,4 @@
         rule.system().validateFinalState()
         return pms
     }
-
-    protected fun mockUnifiedSeparatedBroadcastList() {
-        whenever(broadcastHelper.getBroadcastParams(any(Computer::class.java),
-                any() as Array<String>, any(IntArray::class.java), anyInt()
-        )).thenReturn(ArrayList<BroadcastParams>().apply {
-            this.add(BroadcastParams(packagesToChange[0], uidsToChange[0], IntArray(0),
-                    TEST_USER_ID).apply {
-                this.addPackage(packagesToChange[1], uidsToChange[1])
-            })
-        })
-    }
-
-    protected fun mockDividedSeparatedBroadcastList(allowlist1: IntArray?, allowlist2: IntArray?) {
-        whenever(broadcastHelper.getBroadcastParams(any(Computer::class.java),
-                any() as Array<String>, any(IntArray::class.java), anyInt()
-        )).thenReturn(ArrayList<BroadcastParams>().apply {
-            this.add(BroadcastParams(packagesToChange[0], uidsToChange[0],
-                    allowlist1 ?: IntArray(0), TEST_USER_ID))
-            this.add(BroadcastParams(packagesToChange[1], uidsToChange[1],
-                    allowlist2 ?: IntArray(0), TEST_USER_ID))
-        })
-    }
-}
\ No newline at end of file
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index df53dfe..dc74469 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -45,11 +45,13 @@
         verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
         verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_SUSPENDED),
             nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
-            nullable(), nullable(), nullable())
+            nullable(), nullable(), nullable(), nullable())
         verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(),
-            nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), nullable(), nullable())
+            nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), nullable(),
+            nullable(), nullable())
         verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(),
-            nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), nullable(), nullable())
+            nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), nullable(),
+            nullable(), nullable())
 
         var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
         assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
@@ -135,13 +137,13 @@
         verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
         verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
             nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
-            nullable(), nullable(), nullable())
+            nullable(), nullable(), nullable(), nullable())
         verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
             nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(),
-            nullable(), nullable())
+            nullable(), nullable(), nullable())
         verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
             nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(),
-            nullable(), nullable())
+            nullable(), nullable(), nullable())
 
         var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
         assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
@@ -217,13 +219,13 @@
         verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
         verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
             nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
-            nullable(), nullable(), nullable())
+            nullable(), nullable(), nullable(), nullable())
         verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
             nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(),
-            nullable(), nullable())
+            nullable(), nullable(), nullable())
         verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
             nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(),
-            nullable(), nullable())
+            nullable(), nullable(), nullable())
 
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
             TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull()
@@ -297,12 +299,13 @@
 
     @Test
     @Throws(Exception::class)
-    fun sendPackagesSuspendedForUser_withSameVisibilityAllowList() {
-        suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
+    fun sendPackagesSuspendedForUser() {
+        suspendPackageHelper.sendPackagesSuspendedForUser(
             Intent.ACTION_PACKAGES_SUSPENDED, packagesToChange, uidsToChange, TEST_USER_ID)
         testHandler.flush()
         verify(broadcastHelper).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
-                anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+                anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(),
+                nullable())
 
         var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
         var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
@@ -313,59 +316,14 @@
 
     @Test
     @Throws(Exception::class)
-    fun sendPackagesSuspendedForUser_withDifferentVisibilityAllowList() {
-        mockDividedSeparatedBroadcastList(
-                intArrayOf(10001, 10002, 10003), intArrayOf(10001, 10002, 10007))
-
-        suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
-            Intent.ACTION_PACKAGES_SUSPENDED, packagesToChange, uidsToChange, TEST_USER_ID)
-        testHandler.flush()
-        verify(broadcastHelper, times(2)).sendPackageBroadcast(
-                any(), nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
-                nullable(), any(), nullable())
-
-        bundleCaptor.allValues.forEachIndexed { i, it ->
-            var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
-            var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
-            assertThat(changedPackages?.size).isEqualTo(1)
-            assertThat(changedUids?.size).isEqualTo(1)
-            assertThat(changedPackages?.get(0)).isEqualTo(packagesToChange[i])
-            assertThat(changedUids?.get(0)).isEqualTo(uidsToChange[i])
-        }
-    }
-
-    @Test
-    @Throws(Exception::class)
-    fun sendPackagesSuspendedForUser_withNullVisibilityAllowList() {
-        mockDividedSeparatedBroadcastList(intArrayOf(10001, 10002, 10003), null)
-
-        suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
-            Intent.ACTION_PACKAGES_SUSPENDED, packagesToChange, uidsToChange, TEST_USER_ID)
-        testHandler.flush()
-        verify(broadcastHelper, times(2)).sendPackageBroadcast(
-                any(), nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
-                nullable(), nullable(), nullable())
-
-        bundleCaptor.allValues.forEachIndexed { i, it ->
-            var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
-            var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
-            assertThat(changedPackages?.size).isEqualTo(1)
-            assertThat(changedUids?.size).isEqualTo(1)
-            assertThat(changedPackages?.get(0)).isEqualTo(packagesToChange[i])
-            assertThat(changedUids?.get(0)).isEqualTo(uidsToChange[i])
-        }
-    }
-
-    @Test
-    @Throws(Exception::class)
     fun sendPackagesSuspendModifiedForUser() {
-        suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
-            Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, packagesToChange, uidsToChange,
-            TEST_USER_ID)
+        suspendPackageHelper.sendPackagesSuspendedForUser(
+            Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, packagesToChange, uidsToChange, TEST_USER_ID)
         testHandler.flush()
         verify(broadcastHelper).sendPackageBroadcast(
                 eq(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED), nullable(), bundleCaptor.capture(),
-                anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+                anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(),
+                nullable())
 
         var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
         var modifiedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
index f9f6fe9..831a69a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
@@ -20,6 +20,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 
 import android.app.AlarmManager;
@@ -92,7 +94,7 @@
         Ledger ledger = new Ledger();
 
         doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
-        doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance();
+        doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
 
         Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
@@ -121,7 +123,7 @@
         Ledger ledger = new Ledger();
 
         doReturn(1000L).when(mIrs).getConsumptionLimitLocked();
-        doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance();
+        doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
 
         Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
@@ -168,7 +170,7 @@
         Ledger ledger = new Ledger();
 
         doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
-        doReturn(1000L).when(mEconomicPolicy).getMaxSatiatedBalance();
+        doReturn(1000L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
 
         Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
@@ -187,7 +189,7 @@
         assertEquals(1_000, ledger.getCurrentBalance());
 
         // Shouldn't change in normal operation, but adding test case in case it does.
-        doReturn(900L).when(mEconomicPolicy).getMaxSatiatedBalance();
+        doReturn(900L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
 
         transaction = new Ledger.Transaction(0, 0, 0, null, 500, 0);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
diff --git a/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
similarity index 97%
rename from services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
rename to services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
index 9e986be..2fac31e 100644
--- a/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,7 +17,10 @@
 package com.android.server.tare;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.util.ArraySet;
 import android.util.SparseLongArray;
@@ -60,7 +63,7 @@
         }
 
         @Override
-        long getMaxSatiatedBalance() {
+        long getMaxSatiatedBalance(int userId, String pkgName) {
             return 0;
         }
 
@@ -99,7 +102,9 @@
 
     @Before
     public void setUp() {
-        mEconomicPolicy = new MockEconomicPolicy(mock(InternalResourceService.class));
+        final InternalResourceService irs = mock(InternalResourceService.class);
+        when(irs.isVip(anyInt(), anyString())).thenReturn(false);
+        mEconomicPolicy = new MockEconomicPolicy(irs);
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java
index 2e200c3..fb3e8f2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java
@@ -134,8 +134,11 @@
                 mEconomicPolicy.getInitialSatiatedConsumptionLimit());
         assertEquals(EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES,
                 mEconomicPolicy.getHardSatiatedConsumptionLimit());
+        final String pkgRestricted = "com.pkg.restricted";
+        when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+        assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
         assertEquals(EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES,
-                mEconomicPolicy.getMaxSatiatedBalance());
+                mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
         final String pkgExempted = "com.pkg.exempted";
         when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
         assertEquals(EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
@@ -154,7 +157,10 @@
 
         assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
         assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit());
-        assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance());
+        final String pkgRestricted = "com.pkg.restricted";
+        when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+        assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+        assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
         final String pkgExempted = "com.pkg.exempted";
         when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
         assertEquals(arcToCake(9), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
@@ -172,7 +178,10 @@
 
         assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
         assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit());
-        assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedBalance());
+        final String pkgRestricted = "com.pkg.restricted";
+        when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+        assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+        assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
         final String pkgExempted = "com.pkg.exempted";
         when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
         assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
@@ -187,7 +196,8 @@
 
         assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
         assertEquals(arcToCake(5), mEconomicPolicy.getHardSatiatedConsumptionLimit());
-        assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance());
+        assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+        assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
         assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
         assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
index 45c97e4..47155a1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
@@ -142,9 +142,12 @@
         assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES
                 + EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES,
                 mEconomicPolicy.getHardSatiatedConsumptionLimit());
+        final String pkgRestricted = "com.pkg.restricted";
+        when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+        assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
         assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES
                 + EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES,
-                mEconomicPolicy.getMaxSatiatedBalance());
+                mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
         final String pkgExempted = "com.pkg.exempted";
         when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
         assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES
@@ -170,7 +173,10 @@
 
         assertEquals(arcToCake(10), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
         assertEquals(arcToCake(50), mEconomicPolicy.getHardSatiatedConsumptionLimit());
-        assertEquals(arcToCake(20), mEconomicPolicy.getMaxSatiatedBalance());
+        final String pkgRestricted = "com.pkg.restricted";
+        when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+        assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+        assertEquals(arcToCake(20), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
         final String pkgExempted = "com.pkg.exempted";
         when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
         assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
index 03ce91a..19b798d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
@@ -134,8 +134,11 @@
                 mEconomicPolicy.getInitialSatiatedConsumptionLimit());
         assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES,
                 mEconomicPolicy.getHardSatiatedConsumptionLimit());
+        final String pkgRestricted = "com.pkg.restricted";
+        when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+        assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
         assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
-                mEconomicPolicy.getMaxSatiatedBalance());
+                mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
         final String pkgExempted = "com.pkg.exempted";
         when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
         assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
@@ -154,7 +157,10 @@
 
         assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
         assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit());
-        assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance());
+        final String pkgRestricted = "com.pkg.restricted";
+        when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+        assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+        assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
         final String pkgExempted = "com.pkg.exempted";
         when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
         assertEquals(arcToCake(9), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
@@ -172,7 +178,10 @@
 
         assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
         assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit());
-        assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedBalance());
+        final String pkgRestricted = "com.pkg.restricted";
+        when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+        assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+        assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
         final String pkgExempted = "com.pkg.exempted";
         when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
         assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
@@ -187,7 +196,8 @@
 
         assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
         assertEquals(arcToCake(5), mEconomicPolicy.getHardSatiatedConsumptionLimit());
-        assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance());
+        assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+        assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
         assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
         assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
index 13510adb..5cf026e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
@@ -68,7 +68,8 @@
     private MockitoSession mMockingSession;
     private Scribe mScribeUnderTest;
     private File mTestFileDir;
-    private final List<PackageInfo> mInstalledPackages = new ArrayList<>();
+    private final SparseArrayMap<String, InstalledPackageInfo> mInstalledPackages =
+            new SparseArrayMap<>();
     private final List<Analyst.Report> mReports = new ArrayList<>();
 
     @Mock
@@ -455,6 +456,6 @@
         ApplicationInfo applicationInfo = new ApplicationInfo();
         applicationInfo.uid = UserHandle.getUid(userId, Math.abs(pkgName.hashCode()));
         pkgInfo.applicationInfo = applicationInfo;
-        mInstalledPackages.add(pkgInfo);
+        mInstalledPackages.add(userId, pkgName, new InstalledPackageInfo(pkgInfo));
     }
 }
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index daa7d7b..b27f49d 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -30,6 +30,10 @@
             <item res='@*android:color/profile_badge_2' />
         </badge-colors>
         <default-restrictions no_remove_user='true' no_bluetooth='true' />
+        <user-properties
+            showInLauncher='2020'
+            startWithParent='false'
+        />
     </profile-type>
     <profile-type name='custom.test.1' max-allowed-per-parent='14' />
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
index 4a16874..842b23c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
@@ -17,13 +17,13 @@
 package com.android.server.accessibility;
 
 
+import static android.view.accessibility.AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS;
 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST;
 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST;
 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID;
 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS;
 import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_UNINTERRUPTIBLE;
-import static android.view.accessibility.AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS;
 import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID;
 
 import static org.junit.Assert.assertEquals;
@@ -528,8 +528,7 @@
                     // different client that holds different fetch flags for TextView1.
                     sendNodeRequestToController(nodeId, mMockClientCallback2,
                             mMockClient2InteractionId,
-                            FLAG_PREFETCH_SIBLINGS
-                                    | FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS
+                            FLAG_PREFETCH_SIBLINGS | FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
                                     | FLAG_PREFETCH_ANCESTORS);
                 }
             }
diff --git a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
index bce99a0..2b6be37 100644
--- a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -430,7 +430,8 @@
                 userId,
                 false /* allowBackgroundActivityStarts */,
                 null /* activityStartsToken */,
-                false /* timeoutExempt */ );
+                false /* timeoutExempt */,
+                null /* filterExtrasForReceiver */);
     }
 
     private static int getAppId(int i) {
diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
index 5be05d8..e8dd541 100644
--- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
@@ -16,9 +16,13 @@
 
 package com.android.server.app;
 
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
+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 android.app.GameManager;
 import android.content.Context;
 import android.platform.test.annotations.Presubmit;
 import android.util.AtomicFile;
@@ -28,6 +32,9 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.app.GameManagerService.GamePackageConfiguration;
+import com.android.server.app.GameManagerService.GamePackageConfiguration.GameModeConfiguration;
+
 import org.junit.After;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -66,6 +73,9 @@
                         + "  <package name=\"com.android.app1\" gameMode=\"1\">\n"
                         + "  </package>\n"
                         + "  <package name=\"com.android.app2\" gameMode=\"2\">\n"
+                        + "     <gameModeConfig gameMode=\"2\" scaling=\"0.99\" "
+                        + "useAngle=\"true\" fps=\"90\" loadingBoost=\"123\"></gameModeConfig>\n"
+                        + "     <gameModeConfig gameMode=\"3\"></gameModeConfig>\n"
                         + "  </package>\n"
                         + "  <package name=\"com.android.app3\" gameMode=\"3\">\n"
                         + "  </package>\n"
@@ -92,40 +102,159 @@
         writeGameServiceXml();
     }
 
-    private void verifyGameServiceSettingsData(GameManagerSettings settings) {
-        assertThat(settings.getGameModeLocked(PACKAGE_NAME_1), is(1));
-        assertThat(settings.getGameModeLocked(PACKAGE_NAME_2), is(2));
-        assertThat(settings.getGameModeLocked(PACKAGE_NAME_3), is(3));
-    }
-
     @After
     public void tearDown() throws Exception {
         deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
     }
 
-    /** read in data and verify */
     @Test
     public void testReadGameServiceSettings() {
-        /* write out files and read */
         writeOldFiles();
         final Context context = InstrumentationRegistry.getContext();
         GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
-        assertThat(settings.readPersistentDataLocked(), is(true));
-        verifyGameServiceSettingsData(settings);
+        assertTrue(settings.readPersistentDataLocked());
+
+        // test game modes
+        assertEquals(1, settings.getGameModeLocked(PACKAGE_NAME_1));
+        assertEquals(2, settings.getGameModeLocked(PACKAGE_NAME_2));
+        assertEquals(3, settings.getGameModeLocked(PACKAGE_NAME_3));
+
+        // test game mode configs
+        assertNull(settings.getConfigOverride(PACKAGE_NAME_1));
+        assertNull(settings.getConfigOverride(PACKAGE_NAME_3));
+        final GamePackageConfiguration config = settings.getConfigOverride(PACKAGE_NAME_2);
+        assertNotNull(config);
+
+        assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_STANDARD));
+        final GameModeConfiguration performanceConfig = config.getGameModeConfiguration(
+                GameManager.GAME_MODE_PERFORMANCE);
+        assertNotNull(performanceConfig);
+        assertEquals(performanceConfig.getScaling(), 0.99, 0.01f);
+        assertEquals(performanceConfig.getLoadingBoostDuration(), 123);
+        assertEquals(performanceConfig.getFpsStr(), "90");
+        assertTrue(performanceConfig.getUseAngle());
+        final GameModeConfiguration batteryConfig = config.getGameModeConfiguration(
+                GameManager.GAME_MODE_BATTERY);
+        assertNotNull(batteryConfig);
+        assertEquals(batteryConfig.getScaling(), GameModeConfiguration.DEFAULT_SCALING, 0.01f);
+        assertEquals(batteryConfig.getLoadingBoostDuration(),
+                GameModeConfiguration.DEFAULT_LOADING_BOOST_DURATION);
+        assertEquals(batteryConfig.getFpsStr(), GameModeConfiguration.DEFAULT_FPS);
+        assertFalse(batteryConfig.getUseAngle());
     }
 
-    /** read in data, write it out, and read it back in.  Verify same. */
     @Test
-    public void testWriteGameServiceSettings() {
-        // write out files and read
-        writeOldFiles();
+    public void testReadGameServiceSettings_invalidConfigAttributes() {
+        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(),
+                        "system/game-manager-service.xml"),
+                ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                        + "<packages>\n"
+                        + "  <package name=\"com.android.app1\" gameMode=\"1\">\n"
+                        + "     <gameModeConfig gameMode=\"3\" scaling=\"invalid\" "
+                        + "useAngle=\"invalid\" fps=\"invalid\" "
+                        + "loadingBoost=\"invalid\"></gameModeConfig>\n"
+                        + "  </package>\n"
+                        + "</packages>\n").getBytes());
         final Context context = InstrumentationRegistry.getContext();
         GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
-        assertThat(settings.readPersistentDataLocked(), is(true));
+        assertTrue(settings.readPersistentDataLocked());
 
-        // write out, read back in and verify the same
+        final GamePackageConfiguration config = settings.getConfigOverride(PACKAGE_NAME_1);
+        assertNotNull(config);
+        final GameModeConfiguration batteryConfig = config.getGameModeConfiguration(
+                GameManager.GAME_MODE_BATTERY);
+        assertNotNull(batteryConfig);
+        assertEquals(batteryConfig.getScaling(), GameModeConfiguration.DEFAULT_SCALING, 0.01f);
+        assertEquals(batteryConfig.getLoadingBoostDuration(),
+                GameModeConfiguration.DEFAULT_LOADING_BOOST_DURATION);
+        assertEquals(batteryConfig.getFpsStr(), "invalid");
+        assertFalse(batteryConfig.getUseAngle());
+    }
+
+    @Test
+    public void testReadGameServiceSettings_invalidTags() {
+        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(),
+                        "system/game-manager-service.xml"),
+                ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                        + "<packages>\n"
+                        + "  <package gameMode=\"1\">\n"
+                        + "  </package>\n"
+                        + "  <package name=\"com.android.app2\" gameMode=\"2\">\n"
+                        + "     <unknown></unknown>"
+                        + "     <gameModeConfig gameMode=\"3\" fps=\"90\"></gameModeConfig>\n"
+                        + "     foo bar"
+                        + "  </package>\n"
+                        + "  <unknownTag></unknownTag>\n"
+                        + "    foo bar\n"
+                        + "  <package name=\"com.android.app3\" gameMode=\"3\">\n"
+                        + "  </package>\n"
+                        + "</packages>\n").getBytes());
+        final Context context = InstrumentationRegistry.getContext();
+        GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
+        assertTrue(settings.readPersistentDataLocked());
+        assertEquals(0, settings.getGameModeLocked(PACKAGE_NAME_1));
+        assertEquals(2, settings.getGameModeLocked(PACKAGE_NAME_2));
+        assertEquals(3, settings.getGameModeLocked(PACKAGE_NAME_3));
+
+        final GamePackageConfiguration config = settings.getConfigOverride(PACKAGE_NAME_2);
+        assertNotNull(config);
+        final GameModeConfiguration batteryConfig = config.getGameModeConfiguration(
+                GameManager.GAME_MODE_BATTERY);
+        assertNotNull(batteryConfig);
+        assertEquals(batteryConfig.getFpsStr(), "90");
+    }
+
+
+    @Test
+    public void testWriteGameServiceSettings() {
+        final Context context = InstrumentationRegistry.getContext();
+        GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
+
+        // set package settings and write out to file
+        settings.setGameModeLocked(PACKAGE_NAME_1, GameManager.GAME_MODE_BATTERY);
+        settings.setGameModeLocked(PACKAGE_NAME_2, GameManager.GAME_MODE_PERFORMANCE);
+        settings.setGameModeLocked(PACKAGE_NAME_3, GameManager.GAME_MODE_STANDARD);
+        GamePackageConfiguration config = new GamePackageConfiguration(PACKAGE_NAME_2);
+        GameModeConfiguration performanceConfig = config.getOrAddDefaultGameModeConfiguration(
+                GameManager.GAME_MODE_PERFORMANCE);
+        performanceConfig.setLoadingBoostDuration(321);
+        performanceConfig.setScaling(0.66f);
+        performanceConfig.setUseAngle(true);
+        performanceConfig.setFpsStr("60");
+        GameModeConfiguration batteryConfig = config.getOrAddDefaultGameModeConfiguration(
+                GameManager.GAME_MODE_BATTERY);
+        batteryConfig.setScaling(0.77f);
+        settings.setConfigOverride(PACKAGE_NAME_2, config);
         settings.writePersistentDataLocked();
-        assertThat(settings.readPersistentDataLocked(), is(true));
-        verifyGameServiceSettingsData(settings);
+
+        // clear the settings in memory
+        settings.removeGame(PACKAGE_NAME_1);
+        settings.removeGame(PACKAGE_NAME_2);
+        settings.removeGame(PACKAGE_NAME_3);
+
+        // read back in and verify
+        assertTrue(settings.readPersistentDataLocked());
+        assertEquals(3, settings.getGameModeLocked(PACKAGE_NAME_1));
+        assertEquals(2, settings.getGameModeLocked(PACKAGE_NAME_2));
+        assertEquals(1, settings.getGameModeLocked(PACKAGE_NAME_3));
+
+        config = settings.getConfigOverride(PACKAGE_NAME_1);
+        assertNull(config);
+        config = settings.getConfigOverride(PACKAGE_NAME_2);
+        assertNotNull(config);
+        batteryConfig = config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY);
+        assertNotNull(batteryConfig);
+        assertEquals(batteryConfig.getScaling(), 0.77f, 0.01f);
+        assertEquals(batteryConfig.getLoadingBoostDuration(),
+                GameModeConfiguration.DEFAULT_LOADING_BOOST_DURATION);
+        assertEquals(batteryConfig.getFpsStr(), GameModeConfiguration.DEFAULT_FPS);
+        assertFalse(batteryConfig.getUseAngle());
+
+        performanceConfig = config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE);
+        assertNotNull(performanceConfig);
+        assertEquals(performanceConfig.getScaling(), 0.66f, 0.01f);
+        assertEquals(performanceConfig.getLoadingBoostDuration(), 321);
+        assertEquals(performanceConfig.getFpsStr(), "60");
+        assertTrue(performanceConfig.getUseAngle());
     }
 }
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/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index e2c3a94..4c939f0 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -83,6 +83,7 @@
                 VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED,
                 /* activityListener= */ null,
                 /* activityBlockedCallback= */ null,
+                /* secureWindowCallback= */ null,
                 /* deviceProfile= */ DEVICE_PROFILE_APP_STREAMING);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
index e305957..f69c5c2 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
@@ -39,16 +39,23 @@
         mBrightnessEvent = new BrightnessEvent(1);
         mBrightnessEvent.setReason(
                 getReason(BrightnessReason.REASON_DOZE, BrightnessReason.MODIFIER_LOW_POWER));
+        mBrightnessEvent.setPhysicalDisplayId("test");
         mBrightnessEvent.setLux(100.0f);
+        mBrightnessEvent.setFastAmbientLux(90.0f);
+        mBrightnessEvent.setSlowAmbientLux(85.0f);
         mBrightnessEvent.setPreThresholdLux(150.0f);
         mBrightnessEvent.setTime(System.currentTimeMillis());
+        mBrightnessEvent.setInitialBrightness(25.0f);
         mBrightnessEvent.setBrightness(0.6f);
         mBrightnessEvent.setRecommendedBrightness(0.6f);
         mBrightnessEvent.setHbmMax(0.62f);
+        mBrightnessEvent.setRbcStrength(-1);
         mBrightnessEvent.setThermalMax(0.65f);
+        mBrightnessEvent.setPowerFactor(0.2f);
         mBrightnessEvent.setHbmMode(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
         mBrightnessEvent.setFlags(0);
         mBrightnessEvent.setAdjustmentFlags(0);
+        mBrightnessEvent.setAutomaticBrightnessEnabled(true);
     }
 
     @Test
@@ -56,19 +63,41 @@
         BrightnessEvent secondBrightnessEvent = new BrightnessEvent(1);
         secondBrightnessEvent.copyFrom(mBrightnessEvent);
         secondBrightnessEvent.setTime(0);
-        assertEquals(secondBrightnessEvent.equalsMainData(mBrightnessEvent), true);
+        assertEquals(true, secondBrightnessEvent.equalsMainData(mBrightnessEvent));
     }
 
     @Test
     public void testToStringWorksAsExpected() {
         String actualString = mBrightnessEvent.toString(false);
         String expectedString =
-                "BrightnessEvent: disp=1, brt=0.6, rcmdBrt=0.6, preBrt=NaN, lux=100.0, preLux=150"
-                        + ".0, hbmMax=0.62, hbmMode=off, thrmMax=0.65, flags=, reason=doze [ "
-                        + "low_pwr ]";
-        assertEquals(actualString, expectedString);
+                "BrightnessEvent: disp=1, physDisp=test, brt=0.6, initBrt=25.0, rcmdBrt=0.6,"
+                + " preBrt=NaN, lux=100.0, fastLux=90.0, slowLux=85.0, preLux=150.0, hbmMax=0.62,"
+                + " hbmMode=off, rbcStrength=-1, thrmMax=0.65, powerFactor=0.2, flags=, reason=doze"
+                + " [ low_pwr ], autoBrightness=true";
+        assertEquals(expectedString, actualString);
     }
 
+    @Test
+    public void testFlagsToString() {
+        mBrightnessEvent.reset();
+        mBrightnessEvent.setFlags(mBrightnessEvent.getFlags() | BrightnessEvent.FLAG_IDLE_CURVE);
+        String actualString = mBrightnessEvent.flagsToString();
+        String expectedString = "idle_curve ";
+        assertEquals(expectedString, actualString);
+    }
+
+    @Test
+    public void testFlagsToString_multipleFlags() {
+        mBrightnessEvent.reset();
+        mBrightnessEvent.setFlags(mBrightnessEvent.getFlags()
+                    | BrightnessEvent.FLAG_IDLE_CURVE
+                    | BrightnessEvent.FLAG_LOW_POWER_MODE);
+        String actualString = mBrightnessEvent.flagsToString();
+        String expectedString = "idle_curve low_power_mode ";
+        assertEquals(expectedString, actualString);
+    }
+
+
     private BrightnessReason getReason(int reason, int modifier) {
         BrightnessReason brightnessReason = new BrightnessReason();
         brightnessReason.setReason(reason);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 0f6addb..fe9e0b6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -47,7 +47,6 @@
 import org.junit.runners.JUnit4;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.concurrent.TimeUnit;
 
 @SmallTest
@@ -79,6 +78,7 @@
     private TestLooper mTestLooper = new TestLooper();
     private FakePowerManagerWrapper mPowerManager;
     private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+    private ArrayList<Integer> mLocalDeviceTypes = new ArrayList<>();
     private int mPlaybackPhysicalAddress;
     private int mPlaybackLogicalAddress;
     private boolean mWokenUp;
@@ -91,9 +91,11 @@
         Context context = InstrumentationRegistry.getTargetContext();
         mMyLooper = mTestLooper.getLooper();
 
+        mLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_PLAYBACK);
         mHdmiControlService =
                 new HdmiControlService(InstrumentationRegistry.getTargetContext(),
-                        Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
+                        mLocalDeviceTypes, new FakeAudioDeviceVolumeManagerWrapper()) {
+
                     @Override
                     void wakeUp() {
                         mWokenUp = true;
@@ -121,16 +123,6 @@
                     }
 
                     @Override
-                    boolean isPowerStandby() {
-                        return false;
-                    }
-
-                    @Override
-                    boolean isPowerStandbyOrTransient() {
-                        return false;
-                    }
-
-                    @Override
                     boolean canGoToStandby() {
                         return true;
                     }
@@ -165,6 +157,7 @@
         mNativeWrapper.clearResultMessages();
         mHdmiCecLocalDevicePlayback.mPlaybackDeviceActionOnRoutingControl =
                 HdmiProperties.playback_device_action_on_routing_control_values.NONE;
+        mHdmiControlService.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
     }
 
     @Test
@@ -1199,6 +1192,9 @@
         HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
                 mPlaybackPhysicalAddress);
         mHdmiCecLocalDevicePlayback.dispatchMessage(setStreamPath);
+        // The ActiveSourceAction created from the message above is deferred until the device wakes
+        // up.
+        mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
         mTestLooper.dispatchAll();
         HdmiCecMessage activeSource =
                 HdmiCecMessageBuilder.buildActiveSource(
@@ -2088,4 +2084,111 @@
         assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
                 .isEmpty();
     }
+
+    @Test
+    public void handleRoutingChange_addressNotAllocated_removeActiveSourceAction() {
+        long allocationDelay = TimeUnit.SECONDS.toMillis(60);
+        mHdmiCecLocalDevicePlayback.mPlaybackDeviceActionOnRoutingControl =
+                HdmiProperties
+                        .playback_device_action_on_routing_control_values
+                        .WAKE_UP_AND_SEND_ACTIVE_SOURCE;
+        mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+                "HdmiCecLocalDevicePlaybackTest");
+        HdmiCecMessage routingChangeToPlayback =
+                HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+                        mPlaybackPhysicalAddress);
+        HdmiCecMessage routingChangeToTv =
+                HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, mPlaybackPhysicalAddress,
+                        0x0000);
+        HdmiCecMessage unexpectedMessage =
+                HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+                        mPlaybackPhysicalAddress);
+        // 1. DUT goes to sleep.
+        mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+        // Delay allocate logical address in order to trigger message buffering.
+        mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
+        mNativeWrapper.onCecMessage(routingChangeToPlayback);
+        mTestLooper.dispatchAll();
+        // 2. DUT wakes up and defer ActiveSourceAction.
+        mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiCecLocalDevicePlayback.getActions(ActiveSourceAction.class)).isNotEmpty();
+        // 3. DUT buffers <Routing Change> message to TV.
+        mNativeWrapper.onCecMessage(routingChangeToTv);
+        mTestLooper.dispatchAll();
+        // 4. Allocation is finished and the ActiveSourceAction is removed from the queue.
+        // No <Active Source> message is sent by the DUT.
+        mTestLooper.moveTimeForward(allocationDelay);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiCecLocalDevicePlayback.getActions(ActiveSourceAction.class)).isEmpty();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpectedMessage);
+    }
+
+    @Test
+    public void handleSetStreamPath_addressNotAllocated_removeActiveSourceAction() {
+        long allocationDelay = TimeUnit.SECONDS.toMillis(60);
+        mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+                "HdmiCecLocalDevicePlaybackTest");
+        HdmiCecMessage setStreamPathToPlayback =
+                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, mPlaybackPhysicalAddress);
+        HdmiCecMessage setStreamPathToTv =
+                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x0000);
+        HdmiCecMessage unexpectedMessage =
+                HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+                        mPlaybackPhysicalAddress);
+        // 1. DUT goes to sleep.
+        mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+        // Delay allocate logical address in order to trigger message buffering.
+        mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
+        mNativeWrapper.onCecMessage(setStreamPathToPlayback);
+        mTestLooper.dispatchAll();
+        // 2. DUT wakes up and defer ActiveSourceAction.
+        mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiCecLocalDevicePlayback.getActions(ActiveSourceAction.class)).isNotEmpty();
+        // 3. DUT buffers <Set Stream Path> message to TV.
+        mNativeWrapper.onCecMessage(setStreamPathToTv);
+        mTestLooper.dispatchAll();
+        // 4. Allocation is finished and the ActiveSourceAction is removed from the queue.
+        // No <Active Source> message is sent by the DUT.
+        mTestLooper.moveTimeForward(allocationDelay);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiCecLocalDevicePlayback.getActions(ActiveSourceAction.class)).isEmpty();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpectedMessage);
+    }
+
+    @Test
+    public void handleActiveSource_addressNotAllocated_removeActiveSourceAction() {
+        long allocationDelay = TimeUnit.SECONDS.toMillis(60);
+
+        mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+                "HdmiCecLocalDevicePlaybackTest");
+
+        HdmiCecMessage setStreamPathToPlayback =
+                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, mPlaybackPhysicalAddress);
+        HdmiCecMessage activeSourceFromTv =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+        HdmiCecMessage unexpectedMessage =
+                HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+                        mPlaybackPhysicalAddress);
+        // 1. DUT goes to sleep.
+        mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+        // Delay allocate logical address in order to trigger message buffering.
+        mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
+        mNativeWrapper.onCecMessage(setStreamPathToPlayback);
+        mTestLooper.dispatchAll();
+        // 2. DUT wakes up and defer ActiveSourceAction.
+        mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiCecLocalDevicePlayback.getActions(ActiveSourceAction.class)).isNotEmpty();
+        // 3. DUT buffers <Active Source> message from TV.
+        mNativeWrapper.onCecMessage(activeSourceFromTv);
+        mTestLooper.dispatchAll();
+        // 4. Allocation is finished and the ActiveSourceAction is removed from the queue.
+        // No <Active Source> message is sent by the DUT.
+        mTestLooper.moveTimeForward(allocationDelay);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiCecLocalDevicePlayback.getActions(ActiveSourceAction.class)).isEmpty();
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpectedMessage);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/input/OWNERS b/services/tests/servicestests/src/com/android/server/input/OWNERS
index d701f23..6e9aa1d 100644
--- a/services/tests/servicestests/src/com/android/server/input/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/input/OWNERS
@@ -1 +1,2 @@
-include /core/java/android/hardware/input/OWNERS
+include /services/core/java/com/android/server/input/OWNERS
+
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index a4cccb3..3477288 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -369,6 +369,8 @@
                     throws Exception {
         final LockscreenCredential parentPassword = newPassword("parentPassword");
         final LockscreenCredential profilePassword = newPattern("12345");
+        mService.setSeparateProfileChallengeEnabled(
+                MANAGED_PROFILE_USER_ID, true, profilePassword);
         initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword);
         // Create and verify separate profile credentials.
         testCreateCredential(MANAGED_PROFILE_USER_ID, profilePassword);
@@ -550,11 +552,12 @@
             throws RemoteException {
         assertEquals(0, mGateKeeperService.getSecureUserId(userId));
         synchronized (mService.mSpManager) {
-            mService.initializeSyntheticPasswordLocked(credential, userId);
+            mService.initializeSyntheticPasswordLocked(userId);
         }
         if (credential.isNone()) {
             assertEquals(0, mGateKeeperService.getSecureUserId(userId));
         } else {
+            assertTrue(mService.setLockCredential(credential, nonePassword(), userId));
             assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
index 8139310..c90064e 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
@@ -58,6 +58,7 @@
                 .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
                 .setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
                 .setLastEventTimestamp(100L)
+                .setCreationTimestamp(200L)
                 .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED
                         | ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)
                 .setImportant(true)
@@ -79,6 +80,7 @@
         assertEquals(PARENT_NOTIFICATION_CHANNEL_ID,
                 conversationInfo.getParentNotificationChannelId());
         assertEquals(100L, conversationInfo.getLastEventTimestamp());
+        assertEquals(200L, conversationInfo.getCreationTimestamp());
         assertTrue(conversationInfo.isShortcutLongLived());
         assertTrue(conversationInfo.isShortcutCachedForNotification());
         assertTrue(conversationInfo.isImportant());
@@ -105,6 +107,7 @@
         assertNull(conversationInfo.getNotificationChannelId());
         assertNull(conversationInfo.getParentNotificationChannelId());
         assertEquals(0L, conversationInfo.getLastEventTimestamp());
+        assertEquals(0L, conversationInfo.getCreationTimestamp());
         assertFalse(conversationInfo.isShortcutLongLived());
         assertFalse(conversationInfo.isShortcutCachedForNotification());
         assertFalse(conversationInfo.isImportant());
@@ -131,6 +134,7 @@
                 .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
                 .setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
                 .setLastEventTimestamp(100L)
+                .setCreationTimestamp(200L)
                 .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED)
                 .setImportant(true)
                 .setNotificationSilenced(true)
@@ -154,6 +158,7 @@
         assertEquals(NOTIFICATION_CHANNEL_ID, destination.getNotificationChannelId());
         assertEquals(PARENT_NOTIFICATION_CHANNEL_ID, destination.getParentNotificationChannelId());
         assertEquals(100L, destination.getLastEventTimestamp());
+        assertEquals(200L, destination.getCreationTimestamp());
         assertTrue(destination.isShortcutLongLived());
         assertFalse(destination.isImportant());
         assertTrue(destination.isNotificationSilenced());
@@ -164,4 +169,105 @@
         assertThat(destination.getStatuses()).contains(cs);
         assertThat(destination.getStatuses()).contains(cs2);
     }
+
+    @Test
+    public void testBuildFromAnotherConversation_identicalConversation() {
+        ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build();
+        ConversationStatus cs2 = new ConversationStatus.Builder("id2", ACTIVITY_GAME).build();
+
+        ConversationInfo source = new ConversationInfo.Builder()
+                .setShortcutId(SHORTCUT_ID)
+                .setLocusId(LOCUS_ID)
+                .setContactUri(CONTACT_URI)
+                .setContactPhoneNumber(PHONE_NUMBER)
+                .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
+                .setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
+                .setLastEventTimestamp(100L)
+                .setCreationTimestamp(200L)
+                .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED)
+                .setImportant(true)
+                .setNotificationSilenced(true)
+                .setBubbled(true)
+                .setPersonImportant(true)
+                .setPersonBot(true)
+                .setContactStarred(true)
+                .addOrUpdateStatus(cs)
+                .addOrUpdateStatus(cs2)
+                .build();
+
+        ConversationInfo destination = new ConversationInfo.Builder(source).build();
+
+        assertEquals(SHORTCUT_ID, destination.getShortcutId());
+        assertEquals(LOCUS_ID, destination.getLocusId());
+        assertEquals(CONTACT_URI, destination.getContactUri());
+        assertEquals(PHONE_NUMBER, destination.getContactPhoneNumber());
+        assertEquals(NOTIFICATION_CHANNEL_ID, destination.getNotificationChannelId());
+        assertEquals(PARENT_NOTIFICATION_CHANNEL_ID, destination.getParentNotificationChannelId());
+        assertEquals(100L, destination.getLastEventTimestamp());
+        assertEquals(200L, destination.getCreationTimestamp());
+        assertTrue(destination.isShortcutLongLived());
+        assertTrue(destination.isImportant());
+        assertTrue(destination.isNotificationSilenced());
+        assertTrue(destination.isBubbled());
+        assertTrue(destination.isPersonImportant());
+        assertTrue(destination.isPersonBot());
+        assertTrue(destination.isContactStarred());
+        assertThat(destination.getStatuses()).contains(cs);
+        assertThat(destination.getStatuses()).contains(cs2);
+        // Also check equals() implementation
+        assertTrue(source.equals(destination));
+        assertTrue(destination.equals(source));
+    }
+
+    @Test
+    public void testBuildFromBackupPayload() {
+        ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build();
+        ConversationStatus cs2 = new ConversationStatus.Builder("id2", ACTIVITY_GAME).build();
+
+        ConversationInfo conversationInfo = new ConversationInfo.Builder()
+                .setShortcutId(SHORTCUT_ID)
+                .setLocusId(LOCUS_ID)
+                .setContactUri(CONTACT_URI)
+                .setContactPhoneNumber(PHONE_NUMBER)
+                .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
+                .setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
+                .setLastEventTimestamp(100L)
+                .setCreationTimestamp(200L)
+                .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED
+                        | ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)
+                .setImportant(true)
+                .setNotificationSilenced(true)
+                .setBubbled(true)
+                .setDemoted(true)
+                .setPersonImportant(true)
+                .setPersonBot(true)
+                .setContactStarred(true)
+                .addOrUpdateStatus(cs)
+                .addOrUpdateStatus(cs2)
+                .build();
+
+        ConversationInfo conversationInfoFromBackup =
+                ConversationInfo.readFromBackupPayload(conversationInfo.getBackupPayload());
+
+        assertEquals(SHORTCUT_ID, conversationInfoFromBackup.getShortcutId());
+        assertEquals(LOCUS_ID, conversationInfoFromBackup.getLocusId());
+        assertEquals(CONTACT_URI, conversationInfoFromBackup.getContactUri());
+        assertEquals(PHONE_NUMBER, conversationInfoFromBackup.getContactPhoneNumber());
+        assertEquals(
+                NOTIFICATION_CHANNEL_ID, conversationInfoFromBackup.getNotificationChannelId());
+        assertEquals(PARENT_NOTIFICATION_CHANNEL_ID,
+                conversationInfoFromBackup.getParentNotificationChannelId());
+        assertEquals(100L, conversationInfoFromBackup.getLastEventTimestamp());
+        assertEquals(200L, conversationInfoFromBackup.getCreationTimestamp());
+        assertTrue(conversationInfoFromBackup.isShortcutLongLived());
+        assertTrue(conversationInfoFromBackup.isShortcutCachedForNotification());
+        assertTrue(conversationInfoFromBackup.isImportant());
+        assertTrue(conversationInfoFromBackup.isNotificationSilenced());
+        assertTrue(conversationInfoFromBackup.isBubbled());
+        assertTrue(conversationInfoFromBackup.isDemoted());
+        assertTrue(conversationInfoFromBackup.isPersonImportant());
+        assertTrue(conversationInfoFromBackup.isPersonBot());
+        assertTrue(conversationInfoFromBackup.isContactStarred());
+        // ConversationStatus is a transient object and not persisted
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 2a4896a..66c3f07 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -291,7 +291,8 @@
                 mShortcutChangeCallbackCaptor.capture());
         mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue();
 
-        verify(mContext, times(2)).registerReceiver(any(), any());
+        verify(mContext, times(1)).registerReceiver(any(), any());
+        verify(mContext, times(1)).registerReceiver(any(), any(), anyInt());
     }
 
     @After
@@ -1163,6 +1164,76 @@
     }
 
     @Test
+    public void testUncacheOldestCachedShortcut_missingNotificationEvents() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+        for (int i = 0; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
+            String shortcutId = TEST_SHORTCUT_ID + i;
+            ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
+                    buildPerson());
+            shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+            mShortcutChangeCallback.onShortcutsAddedOrUpdated(
+                    TEST_PKG_NAME,
+                    Collections.singletonList(shortcut),
+                    UserHandle.of(USER_ID_PRIMARY));
+            mLooper.dispatchAll();
+        }
+
+        // Only the shortcut #0 is uncached, all the others are not.
+        verify(mShortcutServiceInternal).uncacheShortcuts(
+                anyInt(), any(), eq(TEST_PKG_NAME),
+                eq(Collections.singletonList(TEST_SHORTCUT_ID + 0)), eq(USER_ID_PRIMARY),
+                eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+        for (int i = 1; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
+            verify(mShortcutServiceInternal, never()).uncacheShortcuts(
+                    anyInt(), anyString(), anyString(),
+                    eq(Collections.singletonList(TEST_SHORTCUT_ID + i)), anyInt(),
+                    eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+        }
+    }
+
+    @Test
+    public void testUncacheOldestCachedShortcut_legacyConversation() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+        // Add an extra conversation with a legacy type (no creationTime)
+        ConversationStore conversationStore = mDataManager
+                .getUserDataForTesting(USER_ID_PRIMARY)
+                .getOrCreatePackageData(TEST_PKG_NAME)
+                .getConversationStore();
+        ConversationInfo.Builder builder = new ConversationInfo.Builder();
+        builder.setShortcutId(TEST_SHORTCUT_ID + 0);
+        builder.setShortcutFlags(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+        mDataManager.updateConversationStoreThenNotifyListeners(
+                conversationStore,
+                builder.build(),
+                TEST_PKG_NAME, USER_ID_PRIMARY);
+        for (int i = 1; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
+            String shortcutId = TEST_SHORTCUT_ID + i;
+            ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
+                    buildPerson());
+            shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+            mShortcutChangeCallback.onShortcutsAddedOrUpdated(
+                    TEST_PKG_NAME,
+                    Collections.singletonList(shortcut),
+                    UserHandle.of(USER_ID_PRIMARY));
+            mLooper.dispatchAll();
+        }
+
+        // Only the shortcut #0 is uncached, all the others are not.
+        verify(mShortcutServiceInternal).uncacheShortcuts(
+                anyInt(), any(), eq(TEST_PKG_NAME),
+                eq(Collections.singletonList(TEST_SHORTCUT_ID + 0)), eq(USER_ID_PRIMARY),
+                eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+        for (int i = 1; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
+            verify(mShortcutServiceInternal, never()).uncacheShortcuts(
+                    anyInt(), anyString(), anyString(),
+                    eq(Collections.singletonList(TEST_SHORTCUT_ID + i)), anyInt(),
+                    eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+        }
+    }
+
+    @Test
     public void testBackupAndRestoration()
             throws IntentFilter.MalformedMimeTypeException {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
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/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
new file mode 100644
index 0000000..13a7a3e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.pm.UserProperties;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.function.Supplier;
+
+/**
+ * Tests for UserManager's {@link UserProperties}.
+ *
+ * Additional test coverage (that actually exercises the functionality) can be found in
+ * {@link UserManagerTest} and
+ * {@link UserManagerServiceUserTypeTest} (for {@link UserProperties#updateFromXml}).
+ *
+ * <p>Run with: atest UserManagerServiceUserPropertiesTest
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class UserManagerServiceUserPropertiesTest {
+
+    /** Test that UserProperties can properly read the xml information that it writes. */
+    @Test
+    public void testWriteReadXml() throws Exception {
+        final UserProperties defaultProps = new UserProperties.Builder()
+                .setShowInLauncher(21)
+                .setStartWithParent(false)
+                .build();
+        final UserProperties actualProps = new UserProperties(defaultProps);
+        actualProps.setShowInLauncher(14);
+
+        // Write the properties to xml.
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        final TypedXmlSerializer out = Xml.newFastSerializer();
+        out.setOutput(baos, StandardCharsets.UTF_8.name());
+        out.startDocument(null, true);
+        out.startTag(null, "testTag");
+        actualProps.writeToXml(out);
+        out.endTag(null, "testTag");
+        out.endDocument();
+
+        // Now read those properties from xml.
+        final ByteArrayInputStream input = new ByteArrayInputStream(baos.toByteArray());
+        final TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(input, StandardCharsets.UTF_8.name());
+        parser.nextTag();
+        final UserProperties readProps = new UserProperties(parser, defaultProps);
+
+        assertUserPropertiesEquals(actualProps, readProps);
+    }
+
+    /** Tests parcelling an object in which all properties are present. */
+    @Test
+    public void testParcelUnparcel() throws Exception {
+        final UserProperties originalProps = new UserProperties.Builder()
+                .setShowInLauncher(2145)
+                .build();
+        final UserProperties readProps = parcelThenUnparcel(originalProps);
+        assertUserPropertiesEquals(originalProps, readProps);
+    }
+
+    /** Tests copying a UserProperties object varying permissions. */
+    @Test
+    public void testCopyLacksPermissions() throws Exception {
+        final UserProperties defaultProps = new UserProperties.Builder()
+                .setShowInLauncher(2145)
+                .setStartWithParent(true)
+                .build();
+        final UserProperties orig = new UserProperties(defaultProps);
+        orig.setShowInLauncher(2841);
+        orig.setStartWithParent(false);
+
+        // Test every permission level. (Currently, it's linear so it's easy.)
+        for (int permLevel = 0; permLevel < 4; permLevel++) {
+            final boolean exposeAll = permLevel >= 3;
+            final boolean hasManage = permLevel >= 2;
+            final boolean hasQuery = permLevel >= 1;
+
+            // Make a possibly-not-full-permission (i.e. partial) copy and check that it is correct.
+            final UserProperties copy = new UserProperties(orig, exposeAll, hasManage, hasQuery);
+            verifyTestCopyLacksPermissions(orig, copy, exposeAll, hasManage, hasQuery);
+            if (permLevel < 1) {
+                // PropertiesPresent should definitely be different since not all items were copied.
+                assertThat(orig.getPropertiesPresent()).isNotEqualTo(copy.getPropertiesPresent());
+            }
+
+            // Now, just like in the SystemServer, parcel/unparcel the copy and make sure that the
+            // unparcelled version behaves just like the partial copy did.
+            final UserProperties readProps = parcelThenUnparcel(copy);
+            verifyTestCopyLacksPermissions(orig, readProps, exposeAll, hasManage, hasQuery);
+        }
+    }
+
+    /**
+     * Verifies that the copy of orig has the expected properties
+     * for the test {@link #testCopyLacksPermissions}.
+     */
+    private void verifyTestCopyLacksPermissions(
+            UserProperties orig,
+            UserProperties copy,
+            boolean exposeAll,
+            boolean hasManagePermission,
+            boolean hasQueryPermission) {
+
+        // Items requiring exposeAll.
+        assertEqualGetterOrThrows(orig::getStartWithParent, copy::getStartWithParent, exposeAll);
+
+        // Items requiring hasManagePermission - put them here using hasManagePermission.
+        // Items requiring hasQueryPermission - put them here using hasQueryPermission.
+
+        // Items with no permission requirements.
+        assertEqualGetterOrThrows(orig::getShowInLauncher, copy::getShowInLauncher, true);
+    }
+
+    /**
+     * If hasPerm, then asserts that value of actualGetter equals value of expectedGetter.
+     * If !hasPerm, then asserts that actualGetter throws a SecurityException.
+     */
+    @SuppressWarnings("ReturnValueIgnored")
+    private void assertEqualGetterOrThrows(
+            Supplier expectedGetter,
+            Supplier actualGetter,
+            boolean hasPerm) {
+        if (hasPerm) {
+            assertThat(expectedGetter.get()).isEqualTo(actualGetter.get());
+        } else {
+            assertThrows(SecurityException.class, actualGetter::get);
+        }
+    }
+
+    private UserProperties parcelThenUnparcel(UserProperties originalProps) {
+        final Parcel out = Parcel.obtain();
+        originalProps.writeToParcel(out, 0);
+        final byte[] data = out.marshall();
+        out.recycle();
+
+        final Parcel in = Parcel.obtain();
+        in.unmarshall(data, 0, data.length);
+        in.setDataPosition(0);
+        final UserProperties readProps = UserProperties.CREATOR.createFromParcel(in);
+        in.recycle();
+
+        return readProps;
+    }
+
+    /** Checks that two UserProperties get the same values. */
+    private void assertUserPropertiesEquals(UserProperties expected, UserProperties actual) {
+        assertThat(expected.getPropertiesPresent()).isEqualTo(actual.getPropertiesPresent());
+        assertThat(expected.getShowInLauncher()).isEqualTo(actual.getShowInLauncher());
+        assertThat(expected.getStartWithParent()).isEqualTo(actual.getStartWithParent());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 971b036..5f48004 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -35,6 +35,7 @@
 import static org.testng.Assert.assertThrows;
 
 import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.os.Bundle;
@@ -81,6 +82,8 @@
                 DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
                 /* flags= */0,
                 /* letsPersonalDataIntoProfile= */false).build());
+        final UserProperties.Builder userProps = new UserProperties.Builder()
+                .setShowInLauncher(17);
         final UserTypeDetails type = new UserTypeDetails.Builder()
                 .setName("a.name")
                 .setEnabled(1)
@@ -98,6 +101,7 @@
                 .setDefaultSystemSettings(systemSettings)
                 .setDefaultSecureSettings(secureSettings)
                 .setDefaultCrossProfileIntentFilters(filters)
+                .setDefaultUserProperties(userProps)
                 .createUserTypeDetails();
 
         assertEquals("a.name", type.getName());
@@ -135,6 +139,8 @@
             assertEquals(filters.get(i), type.getDefaultCrossProfileIntentFilters().get(i));
         }
 
+        assertEquals(17, type.getDefaultUserPropertiesReference().getShowInLauncher());
+
         assertEquals(23, type.getBadgeLabel(0));
         assertEquals(24, type.getBadgeLabel(1));
         assertEquals(25, type.getBadgeLabel(2));
@@ -173,6 +179,11 @@
         assertTrue(type.getDefaultSecureSettings().isEmpty());
         assertTrue(type.getDefaultCrossProfileIntentFilters().isEmpty());
 
+        final UserProperties props = type.getDefaultUserPropertiesReference();
+        assertNotNull(props);
+        assertFalse(props.getStartWithParent());
+        assertEquals(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT, props.getShowInLauncher());
+
         assertFalse(type.hasBadge());
     }
 
@@ -250,19 +261,24 @@
 
         // Mock some "AOSP defaults".
         final Bundle restrictions = makeRestrictionsBundle("no_config_vpn", "no_config_tethering");
+        final UserProperties.Builder props = new UserProperties.Builder()
+                .setShowInLauncher(19)
+                .setStartWithParent(true);
         final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
         builders.put(userTypeAosp1, new UserTypeDetails.Builder()
                 .setName(userTypeAosp1)
                 .setBaseType(FLAG_PROFILE)
                 .setMaxAllowedPerParent(31)
-                .setDefaultRestrictions(restrictions));
+                .setDefaultRestrictions(restrictions)
+                .setDefaultUserProperties(props));
         builders.put(userTypeAosp2, new UserTypeDetails.Builder()
                 .setName(userTypeAosp1)
                 .setBaseType(FLAG_PROFILE)
                 .setMaxAllowedPerParent(32)
                 .setIconBadge(401)
                 .setBadgeColors(402, 403, 404)
-                .setDefaultRestrictions(restrictions));
+                .setDefaultRestrictions(restrictions)
+                .setDefaultUserProperties(props));
 
         final XmlResourceParser parser = mResources.getXml(R.xml.usertypes_test_profile);
         UserTypeFactory.customizeBuilders(builders, parser);
@@ -272,6 +288,8 @@
         assertEquals(31, aospType.getMaxAllowedPerParent());
         assertEquals(Resources.ID_NULL, aospType.getIconBadge());
         assertTrue(UserRestrictionsUtils.areEqual(restrictions, aospType.getDefaultRestrictions()));
+        assertEquals(19, aospType.getDefaultUserPropertiesReference().getShowInLauncher());
+        assertEquals(true, aospType.getDefaultUserPropertiesReference().getStartWithParent());
 
         // userTypeAosp2 should be modified.
         aospType = builders.get(userTypeAosp2).createUserTypeDetails();
@@ -300,6 +318,8 @@
         assertTrue(UserRestrictionsUtils.areEqual(
                 makeRestrictionsBundle("no_remove_user", "no_bluetooth"),
                 aospType.getDefaultRestrictions()));
+        assertEquals(2020, aospType.getDefaultUserPropertiesReference().getShowInLauncher());
+        assertEquals(false, aospType.getDefaultUserPropertiesReference().getStartWithParent());
 
         // userTypeOem1 should be created.
         UserTypeDetails.Builder customType = builders.get(userTypeOem1);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 5d48501..f567e80 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -31,6 +31,7 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.UserHandle;
@@ -564,6 +565,33 @@
         assertThat(userManagerForUser.isProfile()).isEqualTo(userTypeDetails.isProfile());
     }
 
+    /** Test that UserManager returns the correct UserProperties for a new managed profile. */
+    @MediumTest
+    @Test
+    public void testUserProperties() throws Exception {
+        assumeManagedUsersSupported();
+
+        // Get the default properties for a user type.
+        final UserTypeDetails userTypeDetails =
+                UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_PROFILE_MANAGED);
+        assertWithMessage("No %s type on device", UserManager.USER_TYPE_PROFILE_MANAGED)
+                .that(userTypeDetails).isNotNull();
+        final UserProperties typeProps = userTypeDetails.getDefaultUserPropertiesReference();
+
+        // Create an actual user (of this user type) and get its properties.
+        final int primaryUserId = mUserManager.getPrimaryUser().id;
+        final UserInfo userInfo = createProfileForUser("Managed",
+                UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId);
+        assertThat(userInfo).isNotNull();
+        final int userId = userInfo.id;
+        final UserProperties userProps = mUserManager.getUserProperties(UserHandle.of(userId));
+
+        // Check that this new user has the expected properties (relative to the defaults)
+        // provided that the test caller has the necessary permissions.
+        assertThat(userProps.getShowInLauncher()).isEqualTo(typeProps.getShowInLauncher());
+        assertThrows(SecurityException.class, userProps::getStartWithParent);
+    }
+
     // Make sure only max managed profiles can be created
     @MediumTest
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
index 36e988f..3848bab 100644
--- a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
@@ -381,6 +381,7 @@
         assertEquals(0, Arrays.asList(mService.mService.getCurrentTemperaturesWithType(
                         Temperature.TYPE_SKIN)).size());
         assertEquals(Temperature.THROTTLING_NONE, mService.mService.getCurrentThermalStatus());
+        assertTrue(Float.isNaN(mService.mService.getThermalHeadroom(0)));
     }
 
     @Test
@@ -397,6 +398,14 @@
     }
 
     @Test
+    public void testGetThermalHeadroomInputRange() throws RemoteException {
+        assertTrue(Float.isNaN(mService.mService.getThermalHeadroom(
+                ThermalManagerService.MIN_FORECAST_SEC - 1)));
+        assertTrue(Float.isNaN(mService.mService.getThermalHeadroom(
+                ThermalManagerService.MAX_FORECAST_SEC + 1)));
+    }
+
+    @Test
     public void testTemperatureWatcherUpdateSevereThresholds() throws RemoteException {
         ThermalManagerService.TemperatureWatcher watcher = mService.mTemperatureWatcher;
         watcher.mSevereThresholds.erase();
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
index fa3fcd9..235849c 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -127,9 +127,17 @@
         }
 
         @Override
-        public long compose(PrimitiveSegment[] effects, long vibrationId) {
+        public long compose(PrimitiveSegment[] primitives, long vibrationId) {
+            if (mSupportedPrimitives == null) {
+                return 0;
+            }
+            for (PrimitiveSegment primitive : primitives) {
+                if (Arrays.binarySearch(mSupportedPrimitives, primitive.getPrimitiveId()) < 0) {
+                    return 0;
+                }
+            }
             long duration = 0;
-            for (PrimitiveSegment primitive : effects) {
+            for (PrimitiveSegment primitive : primitives) {
                 duration += EFFECT_DURATION + primitive.getDelay();
                 recordEffectSegment(vibrationId, primitive);
             }
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationTest.java
new file mode 100644
index 0000000..b469299
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationTest.java
@@ -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.server.vibrator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.util.stream.Collectors.toList;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+
+/**
+ * Tests for {@link Vibration}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:VibrationTest
+ */
+@Presubmit
+public class VibrationTest {
+
+    @Test
+    public void status_hasUniqueProtoEnumValues() {
+        assertThat(
+                Arrays.stream(Vibration.Status.values())
+                        .map(Vibration.Status::getProtoEnumValue)
+                        .collect(toList()))
+                .containsNoDuplicates();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index de5f6ed..efc240d3 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -257,13 +257,18 @@
         assertTrue(mThread.isRunningVibrationId(vibrationId));
         assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
 
-        conductor.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false);
+        Vibration.EndInfo cancelVibrationInfo = new Vibration.EndInfo(
+                Vibration.Status.CANCELLED_SUPERSEDED, /* endedByUid= */ 1,
+                /* endedByUsage= */ VibrationAttributes.USAGE_ALARM);
+        conductor.notifyCancelled(
+                cancelVibrationInfo,
+                /* immediate= */ false);
         waitForCompletion();
         assertFalse(mThread.isRunningVibrationId(vibrationId));
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED);
+        verifyCallbacksTriggered(vibrationId, cancelVibrationInfo);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
         List<Float> playedAmplitudes = fakeVibrator.getAmplitudes();
@@ -288,7 +293,9 @@
         VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
 
         assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                /* immediate= */ false);
         waitForCompletion();
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
@@ -319,7 +326,9 @@
 
         assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
                 TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                /* immediate= */ false);
         waitForCompletion();
 
         // PWLE size max was used to generate a single vibrate call with 10 segments.
@@ -348,11 +357,13 @@
 
         assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
                 TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false);
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                /* immediate= */ false);
         waitForCompletion();
 
         // Composition size max was used to generate a single vibrate call with 10 primitives.
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED);
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
     }
@@ -370,7 +381,9 @@
         VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
 
         assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                /* immediate= */ false);
         waitForCompletion();
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
@@ -394,7 +407,9 @@
 
         assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() > 1,
                 5000 + TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                /* immediate= */ false);
         waitForCompletion();
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
@@ -414,6 +429,8 @@
     public void vibrate_singleVibratorPredefinedCancel_cancelsVibrationImmediately()
             throws Exception {
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
 
         long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startComposition()
@@ -431,7 +448,9 @@
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
         Thread cancellingThread =
                 new Thread(() -> conductor.notifyCancelled(
-                        Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE, /* immediate= */ false));
+                        new Vibration.EndInfo(
+                                Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+                        /* immediate= */ false));
         cancellingThread.start();
 
         waitForCompletion(/* timeout= */ 50);
@@ -458,7 +477,9 @@
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
         Thread cancellingThread =
                 new Thread(() -> conductor.notifyCancelled(
-                        Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false));
+                        new Vibration.EndInfo(
+                                Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                        /* immediate= */ false));
         cancellingThread.start();
 
         waitForCompletion(/* timeout= */ 50);
@@ -519,7 +540,7 @@
         startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion();
 
-        verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
         verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
@@ -530,6 +551,8 @@
     public void vibrate_singleVibratorComposed_runsVibration() throws Exception {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK,
+                VibrationEffect.Composition.PRIMITIVE_TICK);
 
         long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startComposition()
@@ -551,7 +574,7 @@
     }
 
     @Test
-    public void vibrate_singleVibratorComposedAndNoCapability_ignoresVibration() throws Exception {
+    public void vibrate_singleVibratorComposedAndNoCapability_ignoresVibration() {
         long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
@@ -559,7 +582,7 @@
         startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion();
 
-        verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
         verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
@@ -570,6 +593,10 @@
     public void vibrate_singleVibratorLargeComposition_splitsVibratorComposeCalls() {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        fakeVibrator.setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK,
+                VibrationEffect.Composition.PRIMITIVE_TICK,
+                VibrationEffect.Composition.PRIMITIVE_SPIN);
         fakeVibrator.setCompositionSizeMax(2);
 
         long vibrationId = 1;
@@ -639,6 +666,47 @@
     }
 
     @Test
+    public void vibrate_singleVibratorComposedWithFallback_replacedInTheMiddleOfComposition() {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        fakeVibrator.setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK,
+                VibrationEffect.Composition.PRIMITIVE_TICK);
+        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+
+        long vibrationId = 1;
+        VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+                .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_TICK))
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+                .compose();
+        Vibration vib = createVibration(vibrationId, CombinedVibration.createParallel(effect));
+        vib.addFallback(VibrationEffect.EFFECT_TICK, fallback);
+        startThreadAndDispatcher(vib);
+        waitForCompletion();
+
+        // Use first duration the vibrator is turned on since we cannot estimate the clicks.
+        verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+
+        List<VibrationEffectSegment> segments =
+                mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId);
+        assertTrue("Wrong segments: " + segments, segments.size() >= 4);
+        assertTrue(segments.get(0) instanceof PrebakedSegment);
+        assertTrue(segments.get(1) instanceof PrimitiveSegment);
+        for (int i = 2; i < segments.size() - 1; i++) {
+            // One or more step segments as fallback for the EFFECT_TICK.
+            assertTrue(segments.get(i) instanceof StepSegment);
+        }
+        assertTrue(segments.get(segments.size() - 1) instanceof PrimitiveSegment);
+    }
+
+    @Test
     public void vibrate_singleVibratorPwle_runsComposePwle() throws Exception {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
@@ -809,6 +877,8 @@
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(4).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
 
         long vibrationId = 1;
         VibrationEffect composed = VibrationEffect.startComposition()
@@ -854,6 +924,8 @@
         mockVibrators(1, 2, 3);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(2).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
         mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
 
         long vibrationId = 1;
@@ -902,7 +974,11 @@
         long vibrationId = 1;
         mockVibrators(vibratorIds);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(1).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(2).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
         when(mManagerHooks.prepareSyncedVibration(anyLong(), eq(vibratorIds))).thenReturn(true);
         when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
 
@@ -939,6 +1015,8 @@
         mockVibrators(vibratorIds);
         mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
         mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(4).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
         when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
         when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(true);
 
@@ -1125,7 +1203,9 @@
         // fail at waitForCompletion(cancellingThread).
         Thread cancellingThread = new Thread(
                 () -> conductor.notifyCancelled(
-                        Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false));
+                        new Vibration.EndInfo(
+                                Vibration.Status.CANCELLED_BY_USER),
+                        /* immediate= */ false));
         cancellingThread.start();
 
         // Cancelling the vibration should be fast and return right away, even if the thread is
@@ -1143,6 +1223,8 @@
         mockVibrators(1, 2);
         mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(2).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
 
         long vibrationId = 1;
         CombinedVibration effect = CombinedVibration.startParallel()
@@ -1163,13 +1245,15 @@
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
         Thread cancellingThread = new Thread(
                 () -> conductor.notifyCancelled(
-                        Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false));
+                        new Vibration.EndInfo(
+                                Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                        /* immediate= */ false));
         cancellingThread.start();
 
         waitForCompletion(/* timeout= */ 50);
         cancellingThread.join();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED);
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
         assertFalse(mControllers.get(1).isVibrating());
         assertFalse(mControllers.get(2).isVibrating());
     }
@@ -1195,9 +1279,11 @@
 
         // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
         // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
-        Thread cancellingThread =
-                new Thread(() -> conductor.notifyCancelled(
-                        Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false));
+        Thread cancellingThread = new Thread(
+                () -> conductor.notifyCancelled(
+                        new Vibration.EndInfo(
+                                Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                        /* immediate= */ false));
         cancellingThread.start();
 
         waitForCompletion(/* timeout= */ 50);
@@ -1266,7 +1352,7 @@
 
         // Vibration completed but vibrator not yet released.
         verify(mManagerHooks, timeout(TEST_TIMEOUT_MILLIS)).onVibrationCompleted(eq(vibrationId),
-                eq(Vibration.Status.FINISHED));
+                eq(new Vibration.EndInfo(Vibration.Status.FINISHED)));
         verify(mManagerHooks, never()).onVibrationThreadReleased(anyLong());
 
         // Thread still running ramp down.
@@ -1278,12 +1364,13 @@
 
         // Will stop the ramp down right away.
         conductor.notifyCancelled(
-                Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE, /* immediate= */ true);
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+                /* immediate= */ true);
         waitForCompletion();
 
         // Does not cancel already finished vibration, but releases vibrator.
         verify(mManagerHooks, never()).onVibrationCompleted(eq(vibrationId),
-                eq(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE));
+                eq(new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE)));
         verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
     }
 
@@ -1299,7 +1386,9 @@
         VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
         assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
                 TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+        conductor.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                /* immediate= */ false);
         waitForCompletion();
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
@@ -1422,7 +1511,9 @@
         VibrationStepConductor conductor2 = startThreadAndDispatcher(vibrationId2, effect2);
         // Effect2 won't complete on its own. Cancel it after a couple of repeats.
         Thread.sleep(150);  // More than two TICKs.
-        conductor2.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+        conductor2.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+                /* immediate= */ false);
         waitForCompletion();
 
         startThreadAndDispatcher(vibrationId3, effect3);
@@ -1431,7 +1522,9 @@
         // Effect4 is a long oneshot, but it gets cancelled as fast as possible.
         long start4 = System.currentTimeMillis();
         VibrationStepConductor conductor4 = startThreadAndDispatcher(vibrationId4, effect4);
-        conductor4.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ true);
+        conductor4.notifyCancelled(
+                new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+                /* immediate= */ true);
         waitForCompletion();
         long duration4 = System.currentTimeMillis() - start4;
 
@@ -1469,7 +1562,7 @@
                 fakeVibrator.getEffectSegments(vibrationId3));
 
         // Effect4: cancelled quickly.
-        verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED_SUPERSEDED);
+        verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
         assertTrue("Tested duration=" + duration4, duration4 < 2000);
 
         // Effect5: normal oneshot. Don't worry about amplitude, as effect4 may or may not have
@@ -1580,7 +1673,11 @@
     }
 
     private void verifyCallbacksTriggered(long vibrationId, Vibration.Status expectedStatus) {
-        verify(mManagerHooks).onVibrationCompleted(eq(vibrationId), eq(expectedStatus));
+        verifyCallbacksTriggered(vibrationId, new Vibration.EndInfo(expectedStatus));
+    }
+
+    private void verifyCallbacksTriggered(long vibrationId, Vibration.EndInfo expectedEndInfo) {
+        verify(mManagerHooks).onVibrationCompleted(eq(vibrationId), eq(expectedEndInfo));
         verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
new file mode 100644
index 0000000..c1ab1db
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Handler;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Tests for {@link VibratorFrameworkStatsLogger}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:VibratorFrameworkStatsLoggerTest
+ */
+@Presubmit
+public class VibratorFrameworkStatsLoggerTest {
+
+    @Rule public MockitoRule rule = MockitoJUnit.rule();
+
+    private TestLooper mTestLooper;
+    private VibratorFrameworkStatsLogger mLogger;
+
+    @Before
+    public void setUp() {
+        mTestLooper = new TestLooper();
+    }
+
+    @Test
+    public void writeVibrationReportedAsync_afterMinInterval_writesRightAway() {
+        setUpLogger(/* minIntervalMillis= */ 10, /* queueMaxSize= */ 10);
+
+        VibrationStats.StatsInfo firstStats = newEmptyStatsInfo();
+        assertFalse(firstStats.isWritten());
+
+        mLogger.writeVibrationReportedAsync(firstStats);
+        mTestLooper.dispatchAll();
+        assertTrue(firstStats.isWritten());
+    }
+
+    @Test
+    public void writeVibrationReportedAsync_rightAfterLogging_schedulesToRunAfterRemainingDelay() {
+        setUpLogger(/* minIntervalMillis= */ 100, /* queueMaxSize= */ 10);
+
+        VibrationStats.StatsInfo firstStats = newEmptyStatsInfo();
+        VibrationStats.StatsInfo secondStats = newEmptyStatsInfo();
+        assertFalse(firstStats.isWritten());
+        assertFalse(secondStats.isWritten());
+
+        // Write first message at current SystemClock.uptimeMillis
+        mLogger.writeVibrationReportedAsync(firstStats);
+        mTestLooper.dispatchAll();
+        assertTrue(firstStats.isWritten());
+
+        // Second message is not written right away, it needs to wait the configured interval.
+        mLogger.writeVibrationReportedAsync(secondStats);
+        mTestLooper.dispatchAll();
+        assertFalse(secondStats.isWritten());
+
+        // Second message is written after delay passes.
+        mTestLooper.moveTimeForward(100);
+        mTestLooper.dispatchAll();
+        assertTrue(secondStats.isWritten());
+    }
+
+    @Test
+    public void writeVibrationReportedAsync_tooFast_logsUsingIntervalAndDropsMessagesFromQueue() {
+        setUpLogger(/* minIntervalMillis= */ 100, /* queueMaxSize= */ 2);
+
+        VibrationStats.StatsInfo firstStats = newEmptyStatsInfo();
+        VibrationStats.StatsInfo secondStats = newEmptyStatsInfo();
+        VibrationStats.StatsInfo thirdStats = newEmptyStatsInfo();
+
+        mLogger.writeVibrationReportedAsync(firstStats);
+        mLogger.writeVibrationReportedAsync(secondStats);
+        mLogger.writeVibrationReportedAsync(thirdStats);
+
+        // Only first message is logged.
+        mTestLooper.dispatchAll();
+        assertTrue(firstStats.isWritten());
+        assertFalse(secondStats.isWritten());
+        assertFalse(thirdStats.isWritten());
+
+        // Wait one interval to check only the second one is logged.
+        mTestLooper.moveTimeForward(100);
+        mTestLooper.dispatchAll();
+        assertTrue(secondStats.isWritten());
+        assertFalse(thirdStats.isWritten());
+
+        // Wait a long interval to check the third one was dropped and will never be logged.
+        mTestLooper.moveTimeForward(1_000);
+        mTestLooper.dispatchAll();
+        assertFalse(thirdStats.isWritten());
+    }
+
+    private void setUpLogger(int minIntervalMillis, int queueMaxSize) {
+        mLogger = new VibratorFrameworkStatsLogger(new Handler(mTestLooper.getLooper()),
+                minIntervalMillis, queueMaxSize);
+    }
+
+    private static VibrationStats.StatsInfo newEmptyStatsInfo() {
+        return new VibrationStats.StatsInfo(
+                0, 0, 0, Vibration.Status.FINISHED, new VibrationStats(), 0L);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 8a96feb..b8e1612 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -34,6 +34,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -71,15 +72,18 @@
 import android.os.test.TestLooper;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationConfig;
 import android.os.vibrator.VibrationEffectSegment;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
+import android.util.SparseBooleanArray;
 import android.view.InputDevice;
 
 import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.LocalServices;
@@ -96,6 +100,7 @@
 
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -148,6 +153,8 @@
     private IInputManager mIInputManagerMock;
     @Mock
     private IBatteryStats mBatteryStatsMock;
+    @Mock
+    private VibratorFrameworkStatsLogger mVibratorFrameworkStatsLoggerMock;
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
 
@@ -233,6 +240,11 @@
                     }
 
                     @Override
+                    VibratorFrameworkStatsLogger getFrameworkStatsLogger(Handler handler) {
+                        return mVibratorFrameworkStatsLoggerMock;
+                    }
+
+                    @Override
                     VibratorController createVibratorController(int vibratorId,
                             VibratorController.OnVibrationCompleteListener listener) {
                         return mVibratorProviders.get(vibratorId)
@@ -381,22 +393,22 @@
         IVibratorStateListener listenerMock = mockVibratorStateListener();
         service.registerVibratorStateListener(1, listenerMock);
 
-        vibrate(service, VibrationEffect.createOneShot(40, 100), ALARM_ATTRS);
-        // Wait until service knows vibrator is on.
-        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-        // Wait until effect ends.
-        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+        long oneShotDuration = 20;
+        vibrateAndWaitUntilFinished(service,
+                VibrationEffect.createOneShot(oneShotDuration, VibrationEffect.DEFAULT_AMPLITUDE),
+                ALARM_ATTRS);
 
         InOrder inOrderVerifier = inOrder(listenerMock);
         // First notification done when listener is registered.
         inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
         inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
-        inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
+        // The last notification is after the vibration has completed.
+        inOrderVerifier.verify(listenerMock, timeout(TEST_TIMEOUT_MILLIS)).onVibrating(eq(false));
         inOrderVerifier.verifyNoMoreInteractions();
 
         InOrder batteryVerifier = inOrder(mBatteryStatsMock);
         batteryVerifier.verify(mBatteryStatsMock)
-                .noteVibratorOn(UID, 40 + mVibrationConfig.getRampDownDurationMs());
+                .noteVibratorOn(UID, oneShotDuration + mVibrationConfig.getRampDownDurationMs());
         batteryVerifier.verify(mBatteryStatsMock).noteVibratorOff(UID);
     }
 
@@ -567,22 +579,18 @@
 
         setRingerMode(AudioManager.RINGER_MODE_SILENT);
         VibratorManagerService service = createSystemReadyService();
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
-        // Wait before checking it never played.
-        assertFalse(waitUntil(s -> !fakeVibrator.getAllEffectSegments().isEmpty(),
-                service, /* timeout= */ 50));
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                RINGTONE_ATTRS);
 
         setRingerMode(AudioManager.RINGER_MODE_NORMAL);
         service = createSystemReadyService();
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), RINGTONE_ATTRS);
-        assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 1,
-                service, TEST_TIMEOUT_MILLIS));
+        vibrateAndWaitUntilFinished(
+                service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), RINGTONE_ATTRS);
 
         setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
         service = createSystemReadyService();
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), RINGTONE_ATTRS);
-        assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 2,
-                service, TEST_TIMEOUT_MILLIS));
+        vibrateAndWaitUntilFinished(
+                service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), RINGTONE_ATTRS);
 
         assertEquals(
                 Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_HEAVY_CLICK),
@@ -597,27 +605,18 @@
         fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_CLICK,
                 VibrationEffect.EFFECT_HEAVY_CLICK, VibrationEffect.EFFECT_DOUBLE_CLICK);
         VibratorManagerService service = createSystemReadyService();
-        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
 
-        // The haptic feedback should be ignored in low power, but not the ringtone. The end
-        // of the test asserts which actual effects ended up playing.
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
-        assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 1,
-                service, TEST_TIMEOUT_MILLIS));
-        // Allow the ringtone to complete, as the other vibrations won't cancel it.
-        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
+                HAPTIC_FEEDBACK_ATTRS);
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                RINGTONE_ATTRS);
 
         mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK),
-                /* attrs= */ null);
-        assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 2,
-                service, TEST_TIMEOUT_MILLIS));
-
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK),
-                NOTIFICATION_ATTRS);
-        assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 3,
-                service, TEST_TIMEOUT_MILLIS));
+        vibrateAndWaitUntilFinished(service,
+                VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), /* attrs= */ null);
+        vibrateAndWaitUntilFinished(service,
+                VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), NOTIFICATION_ATTRS);
 
         assertEquals(
                 Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK),
@@ -683,22 +682,17 @@
                 Vibrator.VIBRATION_INTENSITY_HIGH);
         VibratorManagerService service = createSystemReadyService();
 
-        VibrationAttributes enforceFreshAttrs = new VibrationAttributes.Builder()
+        VibrationAttributes notificationWithFreshAttrs = new VibrationAttributes.Builder()
                 .setUsage(VibrationAttributes.USAGE_NOTIFICATION)
                 .setFlags(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)
                 .build();
 
         setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
                 Vibrator.VIBRATION_INTENSITY_LOW);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), NOTIFICATION_ATTRS);
-        // VibrationThread will start this vibration async, so wait before vibrating a second time.
-        assertTrue(waitUntil(s -> mVibratorProviders.get(0).getAllEffectSegments().size() > 0,
-                service, TEST_TIMEOUT_MILLIS));
-
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), enforceFreshAttrs);
-        // VibrationThread will start this vibration async, so wait before checking.
-        assertTrue(waitUntil(s -> mVibratorProviders.get(0).getAllEffectSegments().size() > 1,
-                service, TEST_TIMEOUT_MILLIS));
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                NOTIFICATION_ATTRS);
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
+                notificationWithFreshAttrs);
 
         assertEquals(
                 Arrays.asList(
@@ -774,21 +768,22 @@
         vibrate(service, repeatingEffect, new VibrationAttributes.Builder().setUsage(
                 VibrationAttributes.USAGE_UNKNOWN).build());
 
-        // VibrationThread will start this vibration async, so wait before checking it started.
+        // VibrationThread will start this vibration async, wait until it has started.
         assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
                 service, TEST_TIMEOUT_MILLIS));
 
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), HAPTIC_FEEDBACK_ATTRS);
-
-        // Wait before checking it never played a second effect.
-        assertFalse(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
-                service, /* timeout= */ 50));
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                HAPTIC_FEEDBACK_ATTRS);
 
         // The time estimate is recorded when the vibration starts, repeating vibrations
         // are capped at BATTERY_STATS_REPEATING_VIBRATION_DURATION (=5000).
         verify(mBatteryStatsMock).noteVibratorOn(UID, 5000);
         // The second vibration shouldn't have recorded that the vibrators were turned on.
         verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
+        // No segment played is the prebaked CLICK from the second vibration.
+        assertFalse(
+                mVibratorProviders.get(1).getAllEffectSegments().stream()
+                        .anyMatch(segment -> segment instanceof PrebakedSegment));
     }
 
     @Test
@@ -801,16 +796,16 @@
                 new long[]{10_000, 10_000}, new int[]{128, 255}, -1);
         vibrate(service, alarmEffect, ALARM_ATTRS);
 
-        // VibrationThread will start this vibration async, so wait before checking it started.
+        // VibrationThread will start this vibration async, wait until it has started.
         assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
                 service, TEST_TIMEOUT_MILLIS));
 
         VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
-                new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
+                new long[]{10, 10}, new int[]{128, 255}, 1);
         vibrate(service, repeatingEffect, NOTIFICATION_ATTRS);
 
         // VibrationThread will start this vibration async, so wait before checking it started.
-        assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
+        assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 2,
                 service, TEST_TIMEOUT_MILLIS));
 
         // The second vibration should have recorded that the vibrators were turned on.
@@ -831,14 +826,15 @@
         assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
                 service, TEST_TIMEOUT_MILLIS));
 
-        vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
-
-        // Wait before checking it never played a second effect.
-        assertFalse(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
-                service, /* timeout= */ 50));
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                HAPTIC_FEEDBACK_ATTRS);
 
         // The second vibration shouldn't have recorded that the vibrators were turned on.
         verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
+        // The second vibration shouldn't have played any prebaked segment.
+        assertFalse(
+                mVibratorProviders.get(1).getAllEffectSegments().stream()
+                        .anyMatch(segment -> segment instanceof PrebakedSegment));
     }
 
     @Test
@@ -846,6 +842,7 @@
             throws Exception {
         mockVibrators(1);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
         VibratorManagerService service = createSystemReadyService();
 
         VibrationEffect effect = VibrationEffect.createWaveform(
@@ -856,14 +853,16 @@
         assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
                 service, TEST_TIMEOUT_MILLIS));
 
-        vibrate(service, effect, RINGTONE_ATTRS);
-
-        // VibrationThread will start this vibration async, so wait before checking it started.
-        assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
-                service, TEST_TIMEOUT_MILLIS));
+        vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                RINGTONE_ATTRS);
 
         // The second vibration should have recorded that the vibrators were turned on.
         verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
+        // One segment played is the prebaked CLICK from the second vibration.
+        assertEquals(1,
+                mVibratorProviders.get(1).getAllEffectSegments().stream()
+                        .filter(PrebakedSegment.class::isInstance)
+                        .count());
     }
 
     @Test
@@ -882,12 +881,10 @@
 
         CombinedVibration effect = CombinedVibration.createParallel(
                 VibrationEffect.createOneShot(10, 10));
-        vibrate(service, effect, ALARM_ATTRS);
-        verify(mIInputManagerMock).vibrateCombined(eq(1), eq(effect), any());
+        vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
 
-        // VibrationThread will start this vibration async, so wait before checking it never played.
-        assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
-                service, /* timeout= */ 50));
+        verify(mIInputManagerMock).vibrateCombined(eq(1), eq(effect), any());
+        assertTrue(mVibratorProviders.get(1).getAllEffectSegments().isEmpty());
     }
 
     @Test
@@ -916,7 +913,11 @@
         mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_COMPOSE);
         mockVibrators(1, 2);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(1).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(2).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
         // Mock alarm intensity equals to default value to avoid scaling in this test.
         setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
                 mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_ALARM));
@@ -978,9 +979,7 @@
                         .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
                         .compose())
                 .combine();
-        vibrate(service, effect, ALARM_ATTRS);
-        assertTrue(waitUntil(s -> !fakeVibrator1.getAllEffectSegments().isEmpty(), service,
-                TEST_TIMEOUT_MILLIS));
+        vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
 
         verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
         verify(mNativeWrapperMock).triggerSynced(anyLong());
@@ -1002,9 +1001,7 @@
                 .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
                 .addVibrator(2, VibrationEffect.createOneShot(10, 100))
                 .combine();
-        vibrate(service, effect, ALARM_ATTRS);
-        assertTrue(waitUntil(s -> !fakeVibrator1.getAllEffectSegments().isEmpty(), service,
-                TEST_TIMEOUT_MILLIS));
+        vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
 
         verify(mNativeWrapperMock, never()).prepareSynced(any());
         verify(mNativeWrapperMock, never()).triggerSynced(anyLong());
@@ -1022,9 +1019,7 @@
                 .addVibrator(1, VibrationEffect.createOneShot(10, 50))
                 .addVibrator(2, VibrationEffect.createOneShot(10, 100))
                 .combine();
-        vibrate(service, effect, ALARM_ATTRS);
-        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
-                service, TEST_TIMEOUT_MILLIS));
+        vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
 
         verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
         verify(mNativeWrapperMock, never()).triggerSynced(anyLong());
@@ -1043,9 +1038,7 @@
                 .addVibrator(1, VibrationEffect.createOneShot(10, 50))
                 .addVibrator(2, VibrationEffect.createOneShot(10, 100))
                 .combine();
-        vibrate(service, effect, ALARM_ATTRS);
-        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
-                service, TEST_TIMEOUT_MILLIS));
+        vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
 
         verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
         verify(mNativeWrapperMock).triggerSynced(anyLong());
@@ -1078,30 +1071,25 @@
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
         fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
                 IVibrator.CAP_COMPOSE_EFFECTS);
+        fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK,
+                VibrationEffect.Composition.PRIMITIVE_TICK);
         VibratorManagerService service = createSystemReadyService();
 
-        vibrate(service, VibrationEffect.startComposition()
+        vibrateAndWaitUntilFinished(service, VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
                 .compose(), HAPTIC_FEEDBACK_ATTRS);
-        assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 1,
-                service, TEST_TIMEOUT_MILLIS));
 
-        vibrate(service, CombinedVibration.startSequential()
+        vibrateAndWaitUntilFinished(service, CombinedVibration.startSequential()
                 .addNext(1, VibrationEffect.createOneShot(100, 125))
                 .combine(), NOTIFICATION_ATTRS);
-        assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 2,
-                service, TEST_TIMEOUT_MILLIS));
 
-        vibrate(service, VibrationEffect.startComposition()
+        vibrateAndWaitUntilFinished(service, VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
                 .compose(), ALARM_ATTRS);
-        assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 3,
-                service, TEST_TIMEOUT_MILLIS));
 
         // Ring vibrations have intensity OFF and are not played.
-        vibrate(service, VibrationEffect.createOneShot(100, 125), RINGTONE_ATTRS);
-        assertFalse(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() > 3,
-                service, /* timeout= */ 50));
+        vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(100, 125),
+                RINGTONE_ATTRS);
 
         // Only 3 effects played successfully.
         assertEquals(3, fakeVibrator.getAllEffectSegments().size());
@@ -1129,6 +1117,7 @@
                         .combine(),
                 HAPTIC_FEEDBACK_ATTRS);
 
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
         assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
 
         mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
@@ -1143,14 +1132,50 @@
         VibratorManagerService service = createSystemReadyService();
 
         vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
         assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
 
         service.updateServiceState();
+
         // Vibration is not stopped nearly after updating service.
         assertFalse(waitUntil(s -> !s.isVibrating(1), service, 50));
     }
 
     @Test
+    public void vibrate_prebakedAndComposedVibrationsWithFallbacks_playsFallbackOnlyForPredefined()
+            throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        mVibratorProviders.get(1).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
+
+        VibratorManagerService service = createSystemReadyService();
+        vibrateAndWaitUntilFinished(service,
+                VibrationEffect.startComposition()
+                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .compose(),
+                ALARM_ATTRS);
+
+        List<VibrationEffectSegment> segments = mVibratorProviders.get(1).getAllEffectSegments();
+        // At least one step segment played as fallback for unusupported vibration effect
+        assertTrue(segments.size() > 2);
+        // 0: Supported effect played
+        assertTrue(segments.get(0) instanceof PrebakedSegment);
+        // 1: No segment for unsupported primitive
+        // 2: One or more intermediate step segments as fallback for unsupported effect
+        for (int i = 1; i < segments.size() - 1; i++) {
+            assertTrue(segments.get(i) instanceof StepSegment);
+        }
+        // 3: Supported primitive played
+        assertTrue(segments.get(segments.size() - 1) instanceof PrimitiveSegment);
+    }
+
+    @Test
     public void cancelVibrate_withoutUsageFilter_stopsVibrating() throws Exception {
         mockVibrators(1);
         VibratorManagerService service = createSystemReadyService();
@@ -1159,9 +1184,13 @@
         assertFalse(service.isVibrating(1));
 
         vibrate(service, VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100), ALARM_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
         assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
 
         service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
+
+        // Alarm cancelled on filter match all.
         assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
     }
 
@@ -1171,6 +1200,8 @@
         VibratorManagerService service = createSystemReadyService();
 
         vibrate(service, VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100), ALARM_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
         assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
 
         // Vibration is not cancelled with a different usage.
@@ -1200,6 +1231,8 @@
         VibratorManagerService service = createSystemReadyService();
 
         vibrate(service, VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100), attrs);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
         assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
 
         // Do not cancel UNKNOWN vibration when filter is being applied for other usages.
@@ -1216,6 +1249,8 @@
         assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
 
         vibrate(service, VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100), attrs);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
         assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
 
         // Cancel UNKNOWN vibration when all vibrations are being cancelled.
@@ -1296,6 +1331,8 @@
 
         VibrationEffect effect = VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100);
         vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
         assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
 
         ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS,
@@ -1380,6 +1417,373 @@
         assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
     }
 
+    @Test
+    public void frameworkStats_externalVibration_reportsAllMetrics() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        createSystemReadyService();
+
+        AudioAttributes audioAttrs = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_ALARM)
+                .build();
+
+        ExternalVibration vib = new ExternalVibration(UID, PACKAGE_NAME, audioAttrs,
+                mock(IExternalVibrationController.class));
+        mExternalVibratorService.onExternalVibrationStart(vib);
+
+        Thread.sleep(10);
+        mExternalVibratorService.onExternalVibrationStop(vib);
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo statsInfo = argumentCaptor.getValue();
+        assertEquals(UID, statsInfo.uid);
+        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
+                statsInfo.vibrationType);
+        assertEquals(VibrationAttributes.USAGE_ALARM, statsInfo.usage);
+        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), statsInfo.status);
+        assertTrue(statsInfo.totalDurationMillis > 0);
+        assertTrue(
+                "Expected vibrator ON for at least 10ms, got " + statsInfo.vibratorOnMillis + "ms",
+                statsInfo.vibratorOnMillis >= 10);
+        assertEquals(2, statsInfo.halSetExternalControlCount);
+    }
+
+    @Test
+    public void frameworkStats_waveformVibration_reportsAllMetrics() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        VibratorManagerService service = createSystemReadyService();
+        vibrateAndWaitUntilFinished(service,
+                VibrationEffect.createWaveform(new long[] {0, 10, 20, 10}, -1), RINGTONE_ATTRS);
+
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOnAsync(eq(UID), anyLong());
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOffAsync(eq(UID));
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+        assertEquals(UID, metrics.uid);
+        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
+                metrics.vibrationType);
+        assertEquals(VibrationAttributes.USAGE_RINGTONE, metrics.usage);
+        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+        assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
+                metrics.totalDurationMillis >= 20);
+        assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
+                metrics.vibratorOnMillis >= 20);
+
+        // All unrelated metrics are empty.
+        assertEquals(0, metrics.repeatCount);
+        assertEquals(0, metrics.halComposeCount);
+        assertEquals(0, metrics.halComposePwleCount);
+        assertEquals(0, metrics.halPerformCount);
+        assertEquals(0, metrics.halSetExternalControlCount);
+        assertEquals(0, metrics.halCompositionSize);
+        assertEquals(0, metrics.halPwleSize);
+        assertNull(metrics.halSupportedCompositionPrimitivesUsed);
+        assertNull(metrics.halSupportedEffectsUsed);
+        assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+        assertNull(metrics.halUnsupportedEffectsUsed);
+
+        // Accommodate for ramping off config that might add extra setAmplitudes.
+        assertEquals(2, metrics.halOnCount);
+        assertTrue(metrics.halOffCount > 0);
+        assertTrue(metrics.halSetAmplitudeCount >= 2);
+    }
+
+    @Test
+    public void frameworkStats_repeatingVibration_reportsAllMetrics() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        VibratorManagerService service = createSystemReadyService();
+        vibrate(service, VibrationEffect.createWaveform(new long[] {10, 100}, 1), RINGTONE_ATTRS);
+
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOnAsync(eq(UID), anyLong());
+
+        // Wait for at least one loop before cancelling it.
+        Thread.sleep(100);
+        service.cancelVibrate(VibrationAttributes.USAGE_RINGTONE, service);
+
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOffAsync(eq(UID));
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+        assertEquals(UID, metrics.uid);
+        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED,
+                metrics.vibrationType);
+        assertEquals(VibrationAttributes.USAGE_RINGTONE, metrics.usage);
+        assertEquals(Vibration.Status.CANCELLED_BY_USER.getProtoEnumValue(), metrics.status);
+        assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
+                metrics.totalDurationMillis >= 100);
+        assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
+                metrics.vibratorOnMillis >= 100);
+
+        // All unrelated metrics are empty.
+        assertTrue(metrics.repeatCount > 0);
+        assertEquals(0, metrics.halComposeCount);
+        assertEquals(0, metrics.halComposePwleCount);
+        assertEquals(0, metrics.halPerformCount);
+        assertEquals(0, metrics.halSetExternalControlCount);
+        assertEquals(0, metrics.halCompositionSize);
+        assertEquals(0, metrics.halPwleSize);
+        assertNull(metrics.halSupportedCompositionPrimitivesUsed);
+        assertNull(metrics.halSupportedEffectsUsed);
+        assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+        assertNull(metrics.halUnsupportedEffectsUsed);
+
+        // Accommodate for ramping off config that might add extra setAmplitudes.
+        assertTrue(metrics.halOnCount > 0);
+        assertTrue(metrics.halOffCount > 0);
+        assertTrue(metrics.halSetAmplitudeCount > 0);
+    }
+
+    @Test
+    public void frameworkStats_prebakedAndComposedVibrations_reportsAllMetrics() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        mVibratorProviders.get(1).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_TICK);
+
+        VibratorManagerService service = createSystemReadyService();
+        vibrateAndWaitUntilFinished(service,
+                VibrationEffect.startComposition()
+                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+                        .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .compose(),
+                ALARM_ATTRS);
+
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOnAsync(eq(UID), anyLong());
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOffAsync(eq(UID));
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+        assertEquals(UID, metrics.uid);
+        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
+                metrics.vibrationType);
+        assertEquals(VibrationAttributes.USAGE_ALARM, metrics.usage);
+        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+
+        // At least 4 effect/primitive played, 20ms each, plus configured fallback.
+        assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
+                metrics.totalDurationMillis >= 80);
+        assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
+                metrics.vibratorOnMillis >= 80);
+
+        // Related metrics were collected.
+        assertEquals(2, metrics.halComposeCount); // TICK+TICK, then CLICK+CLICK
+        assertEquals(3, metrics.halPerformCount); // CLICK, TICK, then CLICK
+        assertEquals(4, metrics.halCompositionSize); // 2*TICK + 2*CLICK
+        // No repetitions in reported effect/primitive IDs.
+        assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_TICK},
+                metrics.halSupportedCompositionPrimitivesUsed);
+        assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_CLICK},
+                metrics.halUnsupportedCompositionPrimitivesUsed);
+        assertArrayEquals(new int[] {VibrationEffect.EFFECT_CLICK},
+                metrics.halSupportedEffectsUsed);
+        assertArrayEquals(new int[] {VibrationEffect.EFFECT_TICK},
+                metrics.halUnsupportedEffectsUsed);
+
+        // All unrelated metrics are empty.
+        assertEquals(0, metrics.repeatCount);
+        assertEquals(0, metrics.halComposePwleCount);
+        assertEquals(0, metrics.halSetExternalControlCount);
+        assertEquals(0, metrics.halPwleSize);
+
+        // Accommodate for ramping off config that might add extra setAmplitudes
+        // for the effect that plays the fallback instead of "perform".
+        assertTrue(metrics.halOnCount > 0);
+        assertTrue(metrics.halOffCount > 0);
+        assertTrue(metrics.halSetAmplitudeCount > 0);
+    }
+
+    @Test
+    public void frameworkStats_interruptingVibrations_reportsAllMetrics() throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+
+        vibrate(service, VibrationEffect.createOneShot(1_000, 128), HAPTIC_FEEDBACK_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait until vibration is triggered.
+        assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
+                service, TEST_TIMEOUT_MILLIS));
+
+        vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(10, 255), ALARM_ATTRS);
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS).times(2))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo touchMetrics = argumentCaptor.getAllValues().get(0);
+        assertEquals(UID, touchMetrics.uid);
+        assertEquals(VibrationAttributes.USAGE_TOUCH, touchMetrics.usage);
+        assertEquals(Vibration.Status.CANCELLED_SUPERSEDED.getProtoEnumValue(),
+                touchMetrics.status);
+        assertTrue(touchMetrics.endedBySameUid);
+        assertEquals(VibrationAttributes.USAGE_ALARM, touchMetrics.endedByUsage);
+        assertEquals(-1, touchMetrics.interruptedUsage);
+
+        VibrationStats.StatsInfo alarmMetrics = argumentCaptor.getAllValues().get(1);
+        assertEquals(UID, alarmMetrics.uid);
+        assertEquals(VibrationAttributes.USAGE_ALARM, alarmMetrics.usage);
+        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), alarmMetrics.status);
+        assertFalse(alarmMetrics.endedBySameUid);
+        assertEquals(-1, alarmMetrics.endedByUsage);
+        assertEquals(VibrationAttributes.USAGE_TOUCH, alarmMetrics.interruptedUsage);
+    }
+
+    @Test
+    public void frameworkStats_ignoredVibration_reportsStatus() throws Exception {
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_OFF);
+
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+        // Haptic feedback ignored in low power state
+        vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(100, 128),
+                HAPTIC_FEEDBACK_ATTRS);
+        // Ringtone vibration user settings are off
+        vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(200, 128),
+                RINGTONE_ATTRS);
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS).times(2))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo touchMetrics = argumentCaptor.getAllValues().get(0);
+        assertEquals(UID, touchMetrics.uid);
+        assertEquals(VibrationAttributes.USAGE_TOUCH, touchMetrics.usage);
+        assertEquals(Vibration.Status.IGNORED_FOR_POWER.getProtoEnumValue(), touchMetrics.status);
+
+        VibrationStats.StatsInfo ringtoneMetrics = argumentCaptor.getAllValues().get(1);
+        assertEquals(UID, ringtoneMetrics.uid);
+        assertEquals(VibrationAttributes.USAGE_RINGTONE, ringtoneMetrics.usage);
+        assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS.getProtoEnumValue(),
+                ringtoneMetrics.status);
+
+        for (VibrationStats.StatsInfo metrics : argumentCaptor.getAllValues()) {
+            // Latencies are empty since vibrations never started
+            assertEquals(0, metrics.startLatencyMillis);
+            assertEquals(0, metrics.endLatencyMillis);
+            assertEquals(0, metrics.vibratorOnMillis);
+
+            // All unrelated metrics are empty.
+            assertEquals(0, metrics.repeatCount);
+            assertEquals(0, metrics.halComposeCount);
+            assertEquals(0, metrics.halComposePwleCount);
+            assertEquals(0, metrics.halOffCount);
+            assertEquals(0, metrics.halOnCount);
+            assertEquals(0, metrics.halPerformCount);
+            assertEquals(0, metrics.halSetExternalControlCount);
+            assertEquals(0, metrics.halCompositionSize);
+            assertEquals(0, metrics.halPwleSize);
+            assertNull(metrics.halSupportedCompositionPrimitivesUsed);
+            assertNull(metrics.halSupportedEffectsUsed);
+            assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+            assertNull(metrics.halUnsupportedEffectsUsed);
+        }
+    }
+
+    @Test
+    public void frameworkStats_multiVibrators_reportsAllMetrics() throws Exception {
+        mockVibrators(1, 2);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(1).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_TICK);
+        mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_TICK);
+
+        VibratorManagerService service = createSystemReadyService();
+        vibrateAndWaitUntilFinished(service,
+                CombinedVibration.startParallel()
+                        .addVibrator(1,
+                                VibrationEffect.startComposition()
+                                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                                        .compose())
+                        .addVibrator(2,
+                                VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
+                        .combine(),
+                NOTIFICATION_ATTRS);
+
+        SparseBooleanArray expectedEffectsUsed = new SparseBooleanArray();
+        expectedEffectsUsed.put(VibrationEffect.EFFECT_TICK, true);
+
+        SparseBooleanArray expectedPrimitivesUsed = new SparseBooleanArray();
+        expectedPrimitivesUsed.put(VibrationEffect.Composition.PRIMITIVE_TICK, true);
+
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOnAsync(eq(UID), anyLong());
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibratorStateOffAsync(eq(UID));
+
+        ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+                ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+        verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+                .writeVibrationReportedAsync(argumentCaptor.capture());
+
+        VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+        assertEquals(UID, metrics.uid);
+        assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
+                metrics.vibrationType);
+        assertEquals(VibrationAttributes.USAGE_NOTIFICATION, metrics.usage);
+        assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+        assertTrue(metrics.totalDurationMillis >= 20);
+
+        // vibratorOnMillis accumulates both vibrators, it's 20 for each constant.
+        assertEquals(40, metrics.vibratorOnMillis);
+
+        // Related metrics were collected.
+        assertEquals(1, metrics.halComposeCount);
+        assertEquals(1, metrics.halPerformCount);
+        assertEquals(1, metrics.halCompositionSize);
+        assertEquals(2, metrics.halOffCount);
+        assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_TICK},
+                metrics.halSupportedCompositionPrimitivesUsed);
+        assertArrayEquals(new int[] {VibrationEffect.EFFECT_TICK},
+                metrics.halSupportedEffectsUsed);
+
+        // All unrelated metrics are empty.
+        assertEquals(0, metrics.repeatCount);
+        assertEquals(0, metrics.halComposePwleCount);
+        assertEquals(0, metrics.halOnCount);
+        assertEquals(0, metrics.halSetAmplitudeCount);
+        assertEquals(0, metrics.halSetExternalControlCount);
+        assertEquals(0, metrics.halPwleSize);
+        assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+        assertNull(metrics.halUnsupportedEffectsUsed);
+    }
+
     private VibrationEffectSegment expectedPrebaked(int effectId) {
         return expectedPrebaked(effectId, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
     }
@@ -1429,6 +1833,20 @@
                 mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
     }
 
+    private void vibrateAndWaitUntilFinished(VibratorManagerService service, VibrationEffect effect,
+            VibrationAttributes attrs) throws InterruptedException {
+        vibrateAndWaitUntilFinished(service, CombinedVibration.createParallel(effect), attrs);
+    }
+
+    private void vibrateAndWaitUntilFinished(VibratorManagerService service,
+            CombinedVibration effect, VibrationAttributes attrs) throws InterruptedException {
+        Vibration vib =
+                service.vibrateInternal(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
+        if (vib != null) {
+            vib.waitForEnd();
+        }
+    }
+
     private void vibrate(VibratorManagerService service, VibrationEffect effect,
             VibrationAttributes attrs) {
         vibrate(service, CombinedVibration.createParallel(effect), attrs);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 911fb6a..08c2c6e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -1301,6 +1301,21 @@
     }
 
     @Test
+    public void testA11yCrossUserEventNotSent() throws Exception {
+        final Notification n = new Builder(getContext(), "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
+        int userId = mUser.getIdentifier() + 1;
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 0, mTag, mUid,
+                mPid, n, UserHandle.of(userId), null, System.currentTimeMillis());
+        NotificationRecord r = new NotificationRecord(getContext(), sbn,
+                new NotificationChannel("test", "test", IMPORTANCE_HIGH));
+
+        mService.buzzBeepBlinkLocked(r);
+
+        verify(mAccessibilityService, never()).sendAccessibilityEvent(any(), anyInt());
+    }
+
+    @Test
     public void testLightsScreenOn() {
         mService.mScreenOn = true;
         NotificationRecord r = getLightsNotification();
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 44ca9f4..ec48e23 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -150,6 +150,7 @@
 import android.graphics.Color;
 import android.graphics.drawable.Icon;
 import android.media.AudioManager;
+import android.media.IRingtonePlayer;
 import android.media.session.MediaSession;
 import android.net.Uri;
 import android.os.Binder;
@@ -4238,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;
@@ -7738,6 +7792,34 @@
     }
 
     @Test
+    public void testOnBubbleMetadataChangedToSuppressNotification_soundStopped()
+            throws RemoteException {
+        IRingtonePlayer mockPlayer = mock(IRingtonePlayer.class);
+        when(mAudioManager.getRingtonePlayer()).thenReturn(mockPlayer);
+        // Set up volume to be above 0 for the sound to actually play
+        when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
+
+        setUpPrefsForBubbles(PKG, mUid,
+                true /* global */,
+                BUBBLE_PREFERENCE_ALL /* app */,
+                true /* channel */);
+
+        // Post a bubble notification
+        NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "tag");
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+        waitForIdle();
+
+        // Test: suppress notification via bubble metadata update
+        mService.mNotificationDelegate.onBubbleMetadataFlagChanged(nr.getKey(),
+                Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
+        waitForIdle();
+
+        // Check audio is stopped
+        verify(mockPlayer).stopAsync();
+    }
+
+    @Test
     public void testGrantInlineReplyUriPermission_recordExists() throws Exception {
         int userId = UserManager.isHeadlessSystemUserMode()
                 ? UserHandle.getUserId(UID_HEADLESS)
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
index 5a4ce5da..3a6c0eb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
@@ -20,10 +20,8 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
@@ -75,9 +73,6 @@
 
     @Test
     public void testScheduleJob() {
-        // if asked, the job doesn't currently exist yet
-        when(mMockJobScheduler.getPendingJob(anyInt())).thenReturn(null);
-
         final int rescheduleTimeMillis = 350;  // arbitrary number
 
         // attempt to schedule the job
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/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index 8ac729e..c7905a0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -8,7 +8,7 @@
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distriZenbuted on an "AS IS" BASIS,
+ * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
@@ -397,10 +397,10 @@
         Bundle inputWrong = makeExtrasBundleWithPeople(new String[]{"mailto:nope"});
         assertTrue(ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                 policy, UserHandle.SYSTEM,
-                inputMatches, null, 0, 0));
+                inputMatches, null, 0, 0, 0));
         assertFalse(ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                 policy, UserHandle.SYSTEM,
-                inputWrong, null, 0, 0));
+                inputWrong, null, 0, 0, 0));
     }
 
     @Test
@@ -428,19 +428,19 @@
         assertTrue("identical numbers should match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                 policy, UserHandle.SYSTEM,
-                identical, null, 0, 0));
+                identical, null, 0, 0, 0));
         assertTrue("equivalent but non-identical numbers should match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                 policy, UserHandle.SYSTEM,
-                same, null, 0, 0));
+                same, null, 0, 0, 0));
         assertFalse("non-equivalent numbers should not match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                 policy, UserHandle.SYSTEM,
-                different, null, 0, 0));
+                different, null, 0, 0, 0));
         assertFalse("non-tel strings should not match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                 policy, UserHandle.SYSTEM,
-                garbage, null, 0, 0));
+                garbage, null, 0, 0, 0));
     }
 
     @Test
@@ -469,23 +469,23 @@
         assertTrue("same number 1 should match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                         policy, UserHandle.SYSTEM,
-                        same1, null, 0, 0));
+                        same1, null, 0, 0, 0));
         assertTrue("same number 2 should match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                         policy, UserHandle.SYSTEM,
-                        same2, null, 0, 0));
+                        same2, null, 0, 0, 0));
         assertTrue("same number 3 should match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                         policy, UserHandle.SYSTEM,
-                        same3, null, 0, 0));
+                        same3, null, 0, 0, 0));
         assertFalse("different number 1 should not match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                         policy, UserHandle.SYSTEM,
-                        different1, null, 0, 0));
+                        different1, null, 0, 0, 0));
         assertFalse("different number 2 should not match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                         policy, UserHandle.SYSTEM,
-                        different2, null, 0, 0));
+                        different2, null, 0, 0, 0));
     }
 
     @Test
@@ -516,14 +516,14 @@
         assertTrue("contact number 1 should match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                         policy, UserHandle.SYSTEM,
-                        tel1, null, 0, 0));
+                        tel1, null, 0, 0, 0));
         assertTrue("contact number 2 should match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                         policy, UserHandle.SYSTEM,
-                        tel2, null, 0, 0));
+                        tel2, null, 0, 0, 0));
         assertFalse("different number should not match",
                 ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                         policy, UserHandle.SYSTEM,
-                        different, null, 0, 0));
+                        different, null, 0, 0, 0));
     }
 }
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 5146616..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);
 
@@ -2314,6 +2317,8 @@
 
         assertEquals(launchCookie, activity2.mLaunchCookie);
         assertNull(activity1.mLaunchCookie);
+        activity2.makeFinishingLocked();
+        assertTrue(activity1.getTask().getTaskInfo().launchCookies.contains(launchCookie));
     }
 
     private void verifyProcessInfoUpdate(ActivityRecord activity, State state,
@@ -2464,6 +2469,7 @@
         activity.addWindow(appWindow);
         spyOn(appWindow);
         doNothing().when(appWindow).onStartFreezingScreen();
+        doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
 
         // Set initial orientation and update.
         performRotation(displayRotation, Surface.ROTATION_90);
@@ -2472,8 +2478,6 @@
         // Update the rotation to perform 180 degree rotation and check that resize was reported.
         performRotation(displayRotation, Surface.ROTATION_270);
         assertTrue(appWindow.mResizeReported);
-
-        appWindow.removeImmediately();
     }
 
     private void performRotation(DisplayRotation spiedRotation, int rotationToReport) {
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/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 7415460..f61effa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -474,7 +474,7 @@
     }
 
     @Test
-    public void testActivityRecordReparentToTaskFragment() {
+    public void testActivityRecordReparentedToTaskFragment() {
         final ActivityRecord activity = createActivityRecord(mDc);
         final SurfaceControl activityLeash = mock(SurfaceControl.class);
         doNothing().when(activity).setDropInputMode(anyInt());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 2cf9c01..7244d94 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1252,7 +1252,15 @@
     public void testComputeImeControlTarget() throws Exception {
         final DisplayContent dc = createNewDisplay();
         dc.setRemoteInsetsController(createDisplayWindowInsetsController());
-        dc.setImeInputTarget(createWindow(null, TYPE_BASE_APPLICATION, "app"));
+        dc.mCurrentFocus = createWindow(null, TYPE_BASE_APPLICATION, "app");
+
+        // Expect returning null IME control target when the focus window has not yet been the
+        // IME input target (e.g. IME is restarting) in fullscreen windowing mode.
+        dc.setImeInputTarget(null);
+        assertFalse(dc.mCurrentFocus.inMultiWindowMode());
+        assertNull(dc.computeImeControlTarget());
+
+        dc.setImeInputTarget(dc.mCurrentFocus);
         dc.setImeLayeringTarget(dc.getImeInputTarget().getWindowState());
         assertEquals(dc.getImeInputTarget().getWindowState(), dc.computeImeControlTarget());
     }
@@ -1824,19 +1832,18 @@
 
     @Test
     public void testRemoteRotation() {
-        DisplayContent dc = createNewDisplay();
-
+        final DisplayContent dc = mDisplayContent;
         final DisplayRotation dr = dc.getDisplayRotation();
-        doCallRealMethod().when(dr).updateRotationUnchecked(anyBoolean());
+        spyOn(dr);
         // Rotate 180 degree so the display doesn't have configuration change. This condition is
         // used for the later verification of stop-freezing (without setting mWaitingForConfig).
         doReturn((dr.getRotation() + 2) % 4).when(dr).rotationForOrientation(anyInt(), anyInt());
         final boolean[] continued = new boolean[1];
-        doAnswer(
-                invocation -> {
-                    continued[0] = true;
-                    return true;
-                }).when(dc).updateDisplayOverrideConfigurationLocked();
+        doAnswer(invocation -> {
+            continued[0] = true;
+            mAtm.addWindowLayoutReasons(ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
+            return true;
+        }).when(dc).updateDisplayOverrideConfigurationLocked();
         final boolean[] called = new boolean[1];
         mWm.mDisplayChangeController =
                 new IDisplayChangeWindowController.Stub() {
@@ -1909,10 +1916,10 @@
         testPlayer.start();
         assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation());
         assertNotNull(testPlayer.mLastReady);
-        assertEquals(dc, DisplayRotation.getDisplayFromTransition(testPlayer.mLastTransit));
         WindowContainerToken dcToken = dc.mRemoteToken.toWindowContainerToken();
         assertNotEquals(testPlayer.mLastReady.getChange(dcToken).getEndRotation(),
                 testPlayer.mLastReady.getChange(dcToken).getStartRotation());
+        assertTrue(testPlayer.mLastTransit.applyDisplayChangeIfNeeded());
         testPlayer.finish();
     }
 
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 8546763..88e58ea 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(false, false, 0, 0, 0);
+        mController.mAppTransitionListener.onAppTransitionStartingLocked(0, 0);
         verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index fde6e3c..6d33aaf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -97,6 +97,7 @@
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -1537,6 +1538,108 @@
     }
 
     @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+    public void testOverrideSplitScreenAspectRatioForUnresizablePortraitApps() {
+        final int displayWidth = 1400;
+        final int displayHeight = 1600;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(mTask)
+                .setComponent(ComponentName.createRelative(mContext,
+                        SizeCompatTests.class.getName()))
+                .setMinAspectRatio(1.1f)
+                .setUid(android.os.Process.myUid())
+                .build();
+        // Setup Letterbox Configuration
+        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        activity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
+        // Non-resizable portrait activity
+        prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight);
+        final Rect afterBounds = activity.getBounds();
+        final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width();
+        Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+    public void testOverrideSplitScreenAspectRatioForUnresizablePortraitAppsFromLandscape() {
+        final int displayWidth = 1600;
+        final int displayHeight = 1400;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(mTask)
+                .setComponent(ComponentName.createRelative(mContext,
+                        SizeCompatTests.class.getName()))
+                .setMinAspectRatio(1.1f)
+                .setUid(android.os.Process.myUid())
+                .build();
+        // Setup Letterbox Configuration
+        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        activity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
+        // Non-resizable portrait activity
+        prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth);
+        final Rect afterBounds = activity.getBounds();
+        final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width();
+        Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+    @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
+    public void testOverrideSplitScreenAspectRatioForUnresizableLandscapeApps() {
+        final int displayWidth = 1400;
+        final int displayHeight = 1600;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(mTask)
+                .setComponent(ComponentName.createRelative(mContext,
+                        SizeCompatTests.class.getName()))
+                .setMinAspectRatio(1.1f)
+                .setUid(android.os.Process.myUid())
+                .build();
+        // Setup Letterbox Configuration
+        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        activity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
+        // Non-resizable portrait activity
+        prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+        float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight);
+        final Rect afterBounds = activity.getBounds();
+        final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height();
+        Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+    @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
+    public void testOverrideSplitScreenAspectRatioForUnresizableLandscapeAppsFromLandscape() {
+        final int displayWidth = 1600;
+        final int displayHeight = 1400;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(mTask)
+                .setComponent(ComponentName.createRelative(mContext,
+                        SizeCompatTests.class.getName()))
+                .setMinAspectRatio(1.1f)
+                .setUid(android.os.Process.myUid())
+                .build();
+        // Setup Letterbox Configuration
+        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        activity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
+        // Non-resizable portrait activity
+        prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+        float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth);
+        final Rect afterBounds = activity.getBounds();
+        final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height();
+        Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+    }
+
+    @Test
     public void testSplitAspectRatioForUnresizableLandscapeApps() {
         // Set up a display in portrait and ignoring orientation request.
         int screenWidth = 1400;
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 8b3cff8..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
@@ -158,7 +166,7 @@
         mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
         mController.dispatchPendingEvents();
 
-        verify(mOrganizer, never()).onTaskFragmentAppeared(any());
+        verify(mOrganizer, never()).onTaskFragmentAppeared(any(), any());
 
         // Send callback when the TaskFragment is attached.
         setupMockParent(mTaskFragment, mTask);
@@ -166,7 +174,7 @@
         mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
         mController.dispatchPendingEvents();
 
-        verify(mOrganizer).onTaskFragmentAppeared(any());
+        verify(mOrganizer).onTaskFragmentAppeared(any(), any());
     }
 
     @Test
@@ -179,13 +187,13 @@
                 mTaskFragment);
         mController.dispatchPendingEvents();
 
-        verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
+        verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
 
         // Call onTaskFragmentAppeared first.
         mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
         mController.dispatchPendingEvents();
 
-        verify(mOrganizer).onTaskFragmentAppeared(any());
+        verify(mOrganizer).onTaskFragmentAppeared(any(), any());
 
         // No callback if the info is not changed.
         doReturn(true).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any());
@@ -195,7 +203,7 @@
                 mTaskFragment);
         mController.dispatchPendingEvents();
 
-        verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
+        verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
 
         // Trigger callback if the info is changed.
         doReturn(false).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any());
@@ -204,7 +212,7 @@
                 mTaskFragment);
         mController.dispatchPendingEvents();
 
-        verify(mOrganizer).onTaskFragmentInfoChanged(mTaskFragmentInfo);
+        verify(mOrganizer).onTaskFragmentInfoChanged(any(), eq(mTaskFragmentInfo));
     }
 
     @Test
@@ -215,7 +223,7 @@
         mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
         mController.dispatchPendingEvents();
 
-        verify(mOrganizer).onTaskFragmentVanished(any());
+        verify(mOrganizer).onTaskFragmentVanished(any(), any());
     }
 
     @Test
@@ -228,10 +236,10 @@
         mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
         mController.dispatchPendingEvents();
 
-        verify(mOrganizer, never()).onTaskFragmentAppeared(any());
-        verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
-        verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(anyInt(), any());
-        verify(mOrganizer).onTaskFragmentVanished(mTaskFragmentInfo);
+        verify(mOrganizer, never()).onTaskFragmentAppeared(any(), any());
+        verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
+        verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), anyInt(), any());
+        verify(mOrganizer).onTaskFragmentVanished(any(), eq(mTaskFragmentInfo));
 
         // Not trigger onTaskFragmentInfoChanged.
         // Call onTaskFragmentAppeared before calling onTaskFragmentInfoChanged.
@@ -244,10 +252,10 @@
         mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
         mController.dispatchPendingEvents();
 
-        verify(mOrganizer, never()).onTaskFragmentAppeared(any());
-        verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
-        verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(anyInt(), any());
-        verify(mOrganizer).onTaskFragmentVanished(mTaskFragmentInfo);
+        verify(mOrganizer, never()).onTaskFragmentAppeared(any(), any());
+        verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
+        verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), anyInt(), any());
+        verify(mOrganizer).onTaskFragmentVanished(any(), eq(mTaskFragmentInfo));
     }
 
     @Test
@@ -260,7 +268,7 @@
                 mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
         mController.dispatchPendingEvents();
 
-        verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mTask.mTaskId), any());
+        verify(mOrganizer).onTaskFragmentParentInfoChanged(any(), eq(mTask.mTaskId), any());
 
         // No extra callback if the info is not changed.
         clearInvocations(mOrganizer);
@@ -269,7 +277,7 @@
                 mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
         mController.dispatchPendingEvents();
 
-        verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(anyInt(), any());
+        verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), anyInt(), any());
 
         // Trigger callback if the size is changed.
         mTask.getConfiguration().smallestScreenWidthDp = 100;
@@ -277,7 +285,7 @@
                 mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
         mController.dispatchPendingEvents();
 
-        verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mTask.mTaskId), any());
+        verify(mOrganizer).onTaskFragmentParentInfoChanged(any(), eq(mTask.mTaskId), any());
 
         // Trigger callback if the windowing mode is changed.
         clearInvocations(mOrganizer);
@@ -286,7 +294,7 @@
                 mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
         mController.dispatchPendingEvents();
 
-        verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mTask.mTaskId), any());
+        verify(mOrganizer).onTaskFragmentParentInfoChanged(any(), eq(mTask.mTaskId), any());
     }
 
     @Test
@@ -298,11 +306,12 @@
                 mErrorToken, null /* taskFragment */, -1 /* opType */, exception);
         mController.dispatchPendingEvents();
 
-        verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), eq(null), eq(-1), eq(exception));
+        verify(mOrganizer).onTaskFragmentError(any(), eq(mErrorToken), eq(null), eq(-1),
+                eq(exception));
     }
 
     @Test
-    public void testOnActivityReparentToTask_activityInOrganizerProcess_useActivityToken() {
+    public void testOnActivityReparentedToTask_activityInOrganizerProcess_useActivityToken() {
         // Make sure the activity pid/uid is the same as the organizer caller.
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
@@ -314,17 +323,18 @@
         task.effectiveUid = uid;
 
         // No need to notify organizer if it is not embedded.
-        mController.onActivityReparentToTask(activity);
+        mController.onActivityReparentedToTask(activity);
         mController.dispatchPendingEvents();
 
-        verify(mOrganizer, never()).onActivityReparentToTask(anyInt(), any(), any());
+        verify(mOrganizer, never()).onActivityReparentedToTask(any(), anyInt(), any(), any());
 
         // Notify organizer if it was embedded before entered Pip.
         activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
-        mController.onActivityReparentToTask(activity);
+        mController.onActivityReparentedToTask(activity);
         mController.dispatchPendingEvents();
 
-        verify(mOrganizer).onActivityReparentToTask(task.mTaskId, activity.intent, activity.token);
+        verify(mOrganizer).onActivityReparentedToTask(any(), eq(task.mTaskId), eq(activity.intent),
+                eq(activity.token));
 
         // Notify organizer if there is any embedded in the Task.
         final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
@@ -335,15 +345,16 @@
                 DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
         activity.reparent(taskFragment, POSITION_TOP);
         activity.mLastTaskFragmentOrganizerBeforePip = null;
-        mController.onActivityReparentToTask(activity);
+        mController.onActivityReparentedToTask(activity);
         mController.dispatchPendingEvents();
 
         verify(mOrganizer, times(2))
-                .onActivityReparentToTask(task.mTaskId, activity.intent, activity.token);
+                .onActivityReparentedToTask(any(), eq(task.mTaskId), eq(activity.intent),
+                        eq(activity.token));
     }
 
     @Test
-    public void testOnActivityReparentToTask_activityNotInOrganizerProcess_useTemporaryToken() {
+    public void testOnActivityReparentedToTask_activityNotInOrganizerProcess_useTemporaryToken() {
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
         mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid,
@@ -364,11 +375,11 @@
         // Notify organizer if it was embedded before entered Pip.
         // Create a temporary token since the activity doesn't belong to the same process.
         activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
-        mController.onActivityReparentToTask(activity);
+        mController.onActivityReparentedToTask(activity);
         mController.dispatchPendingEvents();
 
         // Allow organizer to reparent activity in other process using the temporary token.
-        verify(mOrganizer).onActivityReparentToTask(eq(task.mTaskId), eq(activity.intent),
+        verify(mOrganizer).onActivityReparentedToTask(any(), eq(task.mTaskId), eq(activity.intent),
                 token.capture());
         final IBinder temporaryToken = token.getValue();
         assertNotEquals(activity.token, temporaryToken);
@@ -798,7 +809,7 @@
         mController.dispatchPendingEvents();
 
         // Verifies that event was not sent
-        verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
+        verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
     }
 
     @Test
@@ -824,7 +835,7 @@
         mController.dispatchPendingEvents();
 
         // Verifies that event was not sent
-        verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
+        verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
 
         // Mock the task becomes visible, and activity resumed
         doReturn(true).when(task).shouldBeVisible(any());
@@ -832,7 +843,7 @@
 
         // Verifies that event is sent.
         mController.dispatchPendingEvents();
-        verify(mOrganizer).onTaskFragmentInfoChanged(any());
+        verify(mOrganizer).onTaskFragmentInfoChanged(any(), any());
     }
 
     /**
@@ -863,10 +874,10 @@
         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());
+        verify(mOrganizer).onTaskFragmentInfoChanged(any(), any());
     }
 
     /**
@@ -884,8 +895,8 @@
                 .createActivityCount(1)
                 .build();
         final ActivityRecord embeddedActivity = taskFragment.getTopNonFinishingActivity();
-        // Add another activity in the Task so that it always contains a non-finishing activitiy.
-        final ActivityRecord nonEmbeddedActivity = createActivityRecord(task);
+        // Add another activity in the Task so that it always contains a non-finishing activity.
+        createActivityRecord(task);
         assertTrue(task.shouldBeVisible(null));
 
         // Dispatch pending info changed event from creating the activity
@@ -893,21 +904,21 @@
         taskFragment.mTaskFragmentAppearedSent = true;
         mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
         mController.dispatchPendingEvents();
-        verify(mOrganizer).onTaskFragmentInfoChanged(any());
+        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();
-        verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
+        verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
 
         // Finish the embedded activity, and verify the info changed callback is called because the
         // TaskFragment is becoming empty.
         embeddedActivity.finishing = true;
         mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
         mController.dispatchPendingEvents();
-        verify(mOrganizer).onTaskFragmentInfoChanged(any());
+        verify(mOrganizer).onTaskFragmentInfoChanged(any(), any());
     }
 
     /**
@@ -1017,7 +1028,7 @@
         // The pending event will be dispatched on the handler (from requestTraversal).
         waitHandlerIdle(mWm.mAnimationHandler);
 
-        verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), any(),
+        verify(mOrganizer).onTaskFragmentError(any(), eq(mErrorToken), any(),
                 eq(HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT),
                 any(SecurityException.class));
     }
@@ -1056,7 +1067,7 @@
         // The pending event will be dispatched on the handler (from requestTraversal).
         waitHandlerIdle(mWm.mAnimationHandler);
 
-        verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), any(),
+        verify(mOrganizer).onTaskFragmentError(any(), eq(mErrorToken), any(),
                 eq(HIERARCHY_OP_TYPE_REPARENT_CHILDREN), any(SecurityException.class));
     }
 
@@ -1089,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/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 88eadfc..83f1789 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -134,6 +134,23 @@
     }
 
     @Test
+    public void testStartChangeTransition_doNotFreezeWhenOnlyMoved() {
+        final Rect startBounds = new Rect(0, 0, 1000, 1000);
+        final Rect endBounds = new Rect(startBounds);
+        endBounds.offset(500, 0);
+        mTaskFragment.setBounds(startBounds);
+        doReturn(true).when(mTaskFragment).isVisible();
+        doReturn(true).when(mTaskFragment).isVisibleRequested();
+
+        clearInvocations(mTransaction);
+        mTaskFragment.setBounds(endBounds);
+
+        // No change transition, but update the organized surface position.
+        verify(mTaskFragment, never()).initializeChangeTransition(any(), any());
+        verify(mTransaction).setPosition(mLeash, endBounds.left, endBounds.top);
+    }
+
+    @Test
     public void testNotOkToAnimate_doNotStartChangeTransition() {
         mockSurfaceFreezerSnapshot(mTaskFragment.mSurfaceFreezer);
         final Rect startBounds = new Rect(0, 0, 1000, 1000);
@@ -323,7 +340,7 @@
         activity.reparent(task, POSITION_TOP);
 
         // Notify the organizer about the reparent.
-        verify(mAtm.mTaskFragmentOrganizerController).onActivityReparentToTask(activity);
+        verify(mAtm.mTaskFragmentOrganizerController).onActivityReparentedToTask(activity);
         assertNull(activity.mLastTaskFragmentOrganizerBeforePip);
     }
 
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 851be9d..13da154 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -245,7 +245,7 @@
     }
 
     @Override
-    public void userActivity() {
+    public void userActivity(int displayGroupId, int event) {
     }
 
     @Override
@@ -310,11 +310,11 @@
     }
 
     @Override
-    public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
+    public void startKeyguardExitAnimation(long startTime) {
     }
 
     @Override
-    public int applyKeyguardOcclusionChange(boolean keyguardOccludingStarted) {
+    public int applyKeyguardOcclusionChange(boolean notify) {
         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 5a2d456..55ca5fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -36,8 +36,10 @@
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 import static android.window.TransitionInfo.isIndependent;
 
+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.WindowContainer.POSITION_TOP;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -55,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;
@@ -71,6 +74,7 @@
 import android.window.TaskFragmentOrganizer;
 import android.window.TransitionInfo;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
@@ -1082,6 +1086,69 @@
         assertTrue((info.getChanges().get(1).getFlags() & FLAG_IS_EMBEDDED) != 0);
     }
 
+    @Test
+    public void testIncludeEmbeddedActivityReparent() {
+        final Transition transition = createTestTransition(TRANSIT_OPEN);
+        final Task task = createTask(mDisplayContent);
+        task.setBounds(new Rect(0, 0, 2000, 1000));
+        final ActivityRecord activity = createActivityRecord(task);
+        activity.mVisibleRequested = true;
+        // Skip manipulate the SurfaceControl.
+        doNothing().when(activity).setDropInputMode(anyInt());
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        mAtm.mTaskFragmentOrganizerController.registerOrganizer(
+                ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
+        final TaskFragment embeddedTf = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .setOrganizer(organizer)
+                .build();
+        // TaskFragment with different bounds from Task.
+        embeddedTf.setBounds(new Rect(0, 0, 1000, 1000));
+
+        // Start states.
+        transition.collect(activity);
+        transition.collectExistenceChange(embeddedTf);
+
+        // End states.
+        activity.reparent(embeddedTf, POSITION_TOP);
+
+        // Verify that both activity and TaskFragment are included.
+        final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+                transition.mParticipants, transition.mChanges);
+        assertTrue(targets.contains(embeddedTf));
+        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/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 05cc0cf..46b4b76 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -24,6 +24,7 @@
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -41,6 +42,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -52,16 +54,20 @@
 
 import android.graphics.Rect;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.util.MergedConfiguration;
 import android.view.IWindowSessionCallback;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.InsetsVisibilities;
+import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowManager;
+import android.window.ClientWindowFrames;
 import android.window.WindowContainerToken;
 
 import androidx.test.filters.SmallTest;
@@ -166,6 +172,32 @@
     }
 
     @Test
+    public void testRelayoutExitingWindow() {
+        final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "appWin");
+        final WindowSurfaceController surfaceController = mock(WindowSurfaceController.class);
+        doReturn(true).when(surfaceController).hasSurface();
+        spyOn(win);
+        doReturn(true).when(win).isExitAnimationRunningSelfOrParent();
+        win.mWinAnimator.mSurfaceController = surfaceController;
+        win.mViewVisibility = View.VISIBLE;
+        win.mHasSurface = true;
+        win.mActivityRecord.mAppStopped = true;
+        win.mActivityRecord.mVisibleRequested = false;
+        win.mActivityRecord.setVisible(false);
+        mWm.mWindowMap.put(win.mClient.asBinder(), win);
+        final int w = 100;
+        final int h = 200;
+        mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.GONE, 0, 0, 0,
+                new ClientWindowFrames(), new MergedConfiguration(), new SurfaceControl(),
+                new InsetsState(), new InsetsSourceControl[0], new Bundle());
+        // Because the window is already invisible, it doesn't need to apply exiting animation
+        // and WMS#tryStartExitingAnimation() will destroy the surface directly.
+        assertFalse(win.mAnimatingExit);
+        assertFalse(win.mHasSurface);
+        assertNull(win.mWinAnimator.mSurfaceController);
+    }
+
+    @Test
     public void testMoveWindowTokenToDisplay_NullToken_DoNothing() {
         mWm.moveWindowTokenToDisplay(null, mDisplayContent.getDisplayId());
 
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/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 1e0e836..a131084 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -18,6 +18,8 @@
 
 import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
 import static android.Manifest.permission.RECORD_AUDIO;
+import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
+import static android.service.voice.HotwordDetectedResult.EXTRA_PROXIMITY_METERS;
 import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
 import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
 import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS;
@@ -58,6 +60,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
+import android.attention.AttentionManagerInternal;
 import android.content.ComponentName;
 import android.content.ContentCaptureOptions;
 import android.content.Context;
@@ -186,6 +189,12 @@
     final int mUser;
     final Context mContext;
 
+    @Nullable final AttentionManagerInternal mAttentionManagerInternal;
+
+    final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal =
+            this::setProximityMeters;
+
+
     volatile HotwordDetectionServiceIdentity mIdentity;
     private IMicrophoneHotwordDetectionVoiceInteractionCallback mSoftwareCallback;
     private Instant mLastRestartInstant;
@@ -206,6 +215,8 @@
     private @NonNull ServiceConnection mRemoteHotwordDetectionService;
     private IBinder mAudioFlinger;
     private boolean mDebugHotwordLogging = false;
+    @GuardedBy("mLock")
+    private double mProximityMeters = PROXIMITY_UNKNOWN;
 
     HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
             Identity voiceInteractorIdentity, ComponentName serviceName, int userId,
@@ -233,6 +244,10 @@
         mServiceConnectionFactory = new ServiceConnectionFactory(intent, bindInstantServiceAllowed);
 
         mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
+        mAttentionManagerInternal = LocalServices.getService(AttentionManagerInternal.class);
+        if (mAttentionManagerInternal != null) {
+            mAttentionManagerInternal.onStartProximityUpdates(mProximityCallbackInternal);
+        }
 
         mLastRestartInstant = Instant.now();
         updateStateAfterProcessStart(options, sharedMemory);
@@ -397,6 +412,9 @@
         if (mAudioFlinger != null) {
             mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
         }
+        if (mAttentionManagerInternal != null) {
+            mAttentionManagerInternal.onStopProximityUpdates(mProximityCallbackInternal);
+        }
     }
 
     void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory) {
@@ -464,6 +482,7 @@
                         mSoftwareCallback.onError();
                         return;
                     }
+                    saveProximityMetersToBundle(result);
                     mSoftwareCallback.onDetected(result, null, null);
                     if (result != null) {
                         Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
@@ -591,6 +610,7 @@
                         externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
                         return;
                     }
+                    saveProximityMetersToBundle(result);
                     externalCallback.onKeyphraseDetected(recognitionEvent, result);
                     if (result != null) {
                         Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
@@ -1159,6 +1179,20 @@
         });
     }
 
+    private void saveProximityMetersToBundle(HotwordDetectedResult result) {
+        synchronized (mLock) {
+            if (result != null && mProximityMeters != PROXIMITY_UNKNOWN) {
+                result.getExtras().putDouble(EXTRA_PROXIMITY_METERS, mProximityMeters);
+            }
+        }
+    }
+
+    private void setProximityMeters(double proximityMeters) {
+        synchronized (mLock) {
+            mProximityMeters = proximityMeters;
+        }
+    }
+
     private static void bestEffortClose(Closeable... closeables) {
         for (Closeable closeable : closeables) {
             bestEffortClose(closeable);
diff --git a/startop/view_compiler/dex_builder_test/Android.bp b/startop/view_compiler/dex_builder_test/Android.bp
index 2048964..bcba2fe 100644
--- a/startop/view_compiler/dex_builder_test/Android.bp
+++ b/startop/view_compiler/dex_builder_test/Android.bp
@@ -46,7 +46,6 @@
 android_test {
     name: "dex-builder-test",
     srcs: [
-        "src/android/startop/test/ApkLayoutCompilerTest.java",
         "src/android/startop/test/DexBuilderTest.java",
         "src/android/startop/test/LayoutCompilerTest.java",
         "src/android/startop/test/TestClass.java",
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java
deleted file mode 100644
index 230e8df..0000000
--- a/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package android.startop.test;
-
-import android.content.Context;
-import androidx.test.InstrumentationRegistry;
-import android.view.View;
-import dalvik.system.PathClassLoader;
-import java.lang.reflect.Method;
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-public class ApkLayoutCompilerTest {
-    static ClassLoader loadDexFile() throws Exception {
-        Context context = InstrumentationRegistry.getTargetContext();
-        return new PathClassLoader(context.getCodeCacheDir() + "/compiled_view.dex",
-                ClassLoader.getSystemClassLoader());
-    }
-
-    @BeforeClass
-    public static void setup() throws Exception {
-        // ensure PackageManager has compiled the layouts.
-        Process pm = Runtime.getRuntime().exec("pm compile --compile-layouts android.startop.test");
-        pm.waitFor();
-    }
-
-    @Test
-    public void loadAndInflateLayout1() throws Exception {
-        ClassLoader dex_file = loadDexFile();
-        Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView");
-        Method layout1 = compiled_view.getMethod("layout1", Context.class, int.class);
-        Context context = InstrumentationRegistry.getTargetContext();
-        layout1.invoke(null, context, R.layout.layout1);
-    }
-
-    @Test
-    public void loadAndInflateLayout2() throws Exception {
-        ClassLoader dex_file = loadDexFile();
-        Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView");
-        Method layout2 = compiled_view.getMethod("layout2", Context.class, int.class);
-        Context context = InstrumentationRegistry.getTargetContext();
-        layout2.invoke(null, context, R.layout.layout2);
-    }
-}
diff --git a/telephony/OWNERS b/telephony/OWNERS
index 9681ee8..e0c5f8f 100644
--- a/telephony/OWNERS
+++ b/telephony/OWNERS
@@ -10,3 +10,10 @@
 chinmayd@google.com
 amruthr@google.com
 sasindran@google.com
+
+# Temporarily reduced the owner during refactoring
+per-file SubscriptionManager.java=set noparent
+per-file SubscriptionManager.java=jackyu@google.com,amruthr@google.com
+per-file SubscriptionInfo.java=set noparent
+per-file SubscriptionInfo.java=jackyu@google.com,amruthr@google.com
+
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 4d18dfe..4af8cde 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -39,6 +39,7 @@
 import android.telecom.TelecomManager;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.data.ApnSetting;
+import android.telephony.data.DataCallResponse;
 import android.telephony.gba.TlsParams;
 import android.telephony.gba.UaSecurityProtocolIdentifier;
 import android.telephony.ims.ImsReasonInfo;
@@ -1125,6 +1126,27 @@
     public static final String KEY_DEFAULT_MTU_INT = "default_mtu_int";
 
     /**
+     * The data call retry configuration for different types of APN.
+     * @hide
+     */
+    public static final String KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS =
+            "carrier_data_call_retry_config_strings";
+
+    /**
+     * Delay in milliseconds between trying APN from the pool
+     * @hide
+     */
+    public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG =
+            "carrier_data_call_apn_delay_default_long";
+
+    /**
+     * Faster delay in milliseconds between trying APN from the pool
+     * @hide
+     */
+    public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG =
+            "carrier_data_call_apn_delay_faster_long";
+
+    /**
      * Delay in milliseconds for retrying APN after disconnect
      * @hide
      */
@@ -1132,25 +1154,94 @@
             "carrier_data_call_apn_retry_after_disconnect_long";
 
     /**
+     * The maximum times for telephony to retry data setup on the same APN requested by
+     * network through the data setup response retry timer
+     * {@link DataCallResponse#getRetryDurationMillis()}. This is to prevent that network keeps
+     * asking device to retry data setup forever and causes power consumption issue. For infinite
+     * retring same APN, configure this as 2147483647 (i.e. {@link Integer#MAX_VALUE}).
+     *
+     * Note if network does not suggest any retry timer, frameworks uses the retry configuration
+     * from {@link #KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS}, and the maximum retry times could
+     * be configured there.
+     * @hide
+     */
+    public static final String KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT =
+            "carrier_data_call_retry_network_requested_max_count_int";
+
+    /**
      * Data call setup permanent failure causes by the carrier
      */
     public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS =
             "carrier_data_call_permanent_failure_strings";
 
     /**
-     * Default APN types that are metered by the carrier
-     * @hide
+     * A string array indicating the default APN types that are metered by the carrier.
+     *
+     * The string in the array is the name of the APN type. For example, "default" for
+     * {@link ApnSetting#TYPE_DEFAULT}, "mms" for {@link ApnSetting#TYPE_MMS}, etc.
+     *
+     * The default value is {@code {"default", "mms", "dun", "supl"}}.
+     *
+     * @see ApnSetting#TYPE_DEFAULT
+     * @see ApnSetting#TYPE_MMS
+     * @see ApnSetting#TYPE_SUPL
+     * @see ApnSetting#TYPE_DUN
+     * @see ApnSetting#TYPE_HIPRI
+     * @see ApnSetting#TYPE_FOTA
+     * @see ApnSetting#TYPE_IMS
+     * @see ApnSetting#TYPE_CBS
+     * @see ApnSetting#TYPE_IA
+     * @see ApnSetting#TYPE_EMERGENCY
+     * @see ApnSetting#TYPE_MCX
+     * @see ApnSetting#TYPE_XCAP
+     * @see ApnSetting#TYPE_BIP
+     * @see ApnSetting#TYPE_VSIM
+     * @see ApnSetting#TYPE_ENTERPRISE
      */
     public static final String KEY_CARRIER_METERED_APN_TYPES_STRINGS =
             "carrier_metered_apn_types_strings";
+
     /**
-     * Default APN types that are roaming-metered by the carrier
-     * @hide
+     * A string array indicating the default APN types that are roaming-metered by the carrier.
+     *
+     * The string in the array is the name of the APN type. For example, "default" for
+     * {@link ApnSetting#TYPE_DEFAULT}, "mms" for {@link ApnSetting#TYPE_MMS}, etc.
+     *
+     * The default value is {@code {"default", "mms", "dun", "supl"}}.
+     *
+     * @see ApnSetting#TYPE_DEFAULT
+     * @see ApnSetting#TYPE_MMS
+     * @see ApnSetting#TYPE_SUPL
+     * @see ApnSetting#TYPE_DUN
+     * @see ApnSetting#TYPE_HIPRI
+     * @see ApnSetting#TYPE_FOTA
+     * @see ApnSetting#TYPE_IMS
+     * @see ApnSetting#TYPE_CBS
+     * @see ApnSetting#TYPE_IA
+     * @see ApnSetting#TYPE_EMERGENCY
+     * @see ApnSetting#TYPE_MCX
+     * @see ApnSetting#TYPE_XCAP
+     * @see ApnSetting#TYPE_BIP
+     * @see ApnSetting#TYPE_VSIM
+     * @see ApnSetting#TYPE_ENTERPRISE
      */
     public static final String KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS =
             "carrier_metered_roaming_apn_types_strings";
 
     /**
+     * APN types that are not allowed on cellular
+     * @hide
+     */
+    public static final String KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY =
+            "carrier_wwan_disallowed_apn_types_string_array";
+
+    /**
+     * APN types that are not allowed on IWLAN
+     * @hide
+     */
+    public static final String KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY =
+            "carrier_wlan_disallowed_apn_types_string_array";
+    /**
      * CDMA carrier ERI (Enhanced Roaming Indicator) file name
      * @hide
      */
@@ -8369,6 +8460,7 @@
      * "1800000, maximum_retries=20" means for those capabilities, retry happens in 2.5s, 3s, 5s,
      * 10s, 15s, 20s, 40s, 1m, 2m, 4m, 10m, 20m, 30m, 30m, 30m, until reaching 20 retries.
      *
+     * // TODO: remove KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS
      * @hide
      */
     public static final String KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY =
@@ -8770,13 +8862,27 @@
         sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false);
         sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
         sDefaults.putInt(KEY_DEFAULT_MTU_INT, 1500);
+        sDefaults.putStringArray(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS, new String[]{
+                "default:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
+                        + "320000:5000,640000:5000,1280000:5000,1800000:5000",
+                "mms:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
+                        + "320000:5000,640000:5000,1280000:5000,1800000:5000",
+                "ims:max_retries=10, 5000, 5000, 5000",
+                "others:max_retries=3, 5000, 5000, 5000"});
+        sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG, 20000);
+        sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG, 3000);
         sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_RETRY_AFTER_DISCONNECT_LONG, 3000);
+        sDefaults.putInt(KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT, 3);
         sDefaults.putString(KEY_CARRIER_ERI_FILE_NAME_STRING, "eri.xml");
         sDefaults.putInt(KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT, 7200);
         sDefaults.putStringArray(KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{"default", "mms", "dun", "supl"});
         sDefaults.putStringArray(KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
                 new String[]{"default", "mms", "dun", "supl"});
+        sDefaults.putStringArray(KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
+                new String[]{""});
+        sDefaults.putStringArray(KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
+                new String[]{""});
         sDefaults.putIntArray(KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY,
                 new int[] {TelephonyManager.NETWORK_TYPE_CDMA, TelephonyManager.NETWORK_TYPE_1xRTT,
                         TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyManager.NETWORK_TYPE_EVDO_A,
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index f28408e1a..4fb6587 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -139,24 +139,19 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static final Uri CONTENT_URI = SimInfo.CONTENT_URI;
 
-    /** @hide */
-    public static final String CACHE_KEY_DEFAULT_SUB_ID_PROPERTY =
+    private static final String CACHE_KEY_DEFAULT_SUB_ID_PROPERTY =
             "cache_key.telephony.get_default_sub_id";
 
-    /** @hide */
-    public static final String CACHE_KEY_DEFAULT_DATA_SUB_ID_PROPERTY =
+    private static final String CACHE_KEY_DEFAULT_DATA_SUB_ID_PROPERTY =
             "cache_key.telephony.get_default_data_sub_id";
 
-    /** @hide */
-    public static final String CACHE_KEY_DEFAULT_SMS_SUB_ID_PROPERTY =
+    private static final String CACHE_KEY_DEFAULT_SMS_SUB_ID_PROPERTY =
             "cache_key.telephony.get_default_sms_sub_id";
 
-    /** @hide */
-    public static final String CACHE_KEY_ACTIVE_DATA_SUB_ID_PROPERTY =
+    private static final String CACHE_KEY_ACTIVE_DATA_SUB_ID_PROPERTY =
             "cache_key.telephony.get_active_data_sub_id";
 
-    /** @hide */
-    public static final String CACHE_KEY_SLOT_INDEX_PROPERTY =
+    private static final String CACHE_KEY_SLOT_INDEX_PROPERTY =
             "cache_key.telephony.get_slot_index";
 
     /** @hide */
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 7893992..c9a63c6 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -140,6 +140,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -234,6 +235,54 @@
     public static final int NETWORK_SELECTION_MODE_AUTO = 1;
     public static final int NETWORK_SELECTION_MODE_MANUAL = 2;
 
+    /**
+     * Reasons for Radio being powered off.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"RADIO_POWER_REASON_"},
+            value = {
+                    RADIO_POWER_REASON_USER,
+                    RADIO_POWER_REASON_THERMAL,
+                    RADIO_POWER_REASON_CARRIER,
+                    RADIO_POWER_REASON_NEARBY_DEVICE})
+    public @interface RadioPowerReason {}
+
+    /**
+     * This reason is used when users want to turn off radio, e.g., users turn on airplane mode.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RADIO_POWER_REASON_USER = 0;
+    /**
+     * This reason is used when radio needs to be turned off due to thermal.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RADIO_POWER_REASON_THERMAL = 1;
+    /**
+     * This reason is used when carriers want to turn off radio. A privileged app can request to
+     * turn off radio via the system service
+     * {@link com.android.carrierdefaultapp.CaptivePortalLoginActivity}, which subsequently calls
+     * the system APIs {@link requestRadioPowerOffForReason} and
+     * {@link clearRadioPowerOffForReason}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RADIO_POWER_REASON_CARRIER = 2;
+    /**
+     * Used to reduce power on a battery-constrained device when Telephony services are available
+     * via a paired device which is nearby.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RADIO_POWER_REASON_NEARBY_DEVICE = 3;
+
     /** The otaspMode passed to PhoneStateListener#onOtaspChanged */
     /** @hide */
     static public final int OTASP_UNINITIALIZED = 0;
@@ -9039,7 +9088,7 @@
      * @param executor The executor through which the callback should be invoked. Since the scan
      *        request may trigger multiple callbacks and they must be invoked in the same order as
      *        they are received by the platform, the user should provide an executor which executes
-     *        tasks one at a time in serial order. For example AsyncTask.SERIAL_EXECUTOR.
+     *        tasks one at a time in serial order.
      * @param callback Returns network scan results or errors.
      * @return A NetworkScan obj which contains a callback which can be used to stop the scan.
      */
@@ -9083,7 +9132,7 @@
      * @param executor The executor through which the callback should be invoked. Since the scan
      *        request may trigger multiple callbacks and they must be invoked in the same order as
      *        they are received by the platform, the user should provide an executor which executes
-     *        tasks one at a time in serial order. For example AsyncTask.SERIAL_EXECUTOR.
+     *        tasks one at a time in serial order.
      * @param callback Returns network scan results or errors.
      * @return A NetworkScan obj which contains a callback which can be used to stop the scan.
      */
@@ -10506,34 +10555,155 @@
         }
     }
 
-    /** @hide */
+    /**
+     * @deprecated - use the APIs {@link requestRadioPowerOffForReason} and
+     * {@link clearRadioPowerOffForReason}.
+     *
+     * @hide
+     */
+    @Deprecated
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public boolean setRadio(boolean turnOn) {
+        boolean result = true;
         try {
-            ITelephony telephony = getITelephony();
-            if (telephony != null)
-                return telephony.setRadio(turnOn);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#setRadio", e);
+            if (turnOn) {
+                clearRadioPowerOffForReason(RADIO_POWER_REASON_USER);
+            } else {
+                requestRadioPowerOffForReason(RADIO_POWER_REASON_USER);
+            }
+        } catch (Exception e) {
+            String calledFunction =
+                    turnOn ? "clearRadioPowerOffForReason" : "requestRadioPowerOffForReason";
+            Log.e(TAG, "Error calling " + calledFunction, e);
+            result = false;
         }
-        return false;
+        return result;
     }
 
-    /** @hide */
+    /**
+     * @deprecated - use the APIs {@link requestRadioPowerOffForReason} and
+     * {@link clearRadioPowerOffForReason}.
+     *
+     * @hide
+     */
+    @Deprecated
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public boolean setRadioPower(boolean turnOn) {
+        boolean result = true;
+        try {
+            if (turnOn) {
+                clearRadioPowerOffForReason(RADIO_POWER_REASON_USER);
+            } else {
+                requestRadioPowerOffForReason(RADIO_POWER_REASON_USER);
+            }
+        } catch (Exception e) {
+            String calledFunction =
+                    turnOn ? "clearRadioPowerOffForReason" : "requestRadioPowerOffForReason";
+            Log.e(TAG, "Error calling " + calledFunction, e);
+            result = false;
+        }
+        return result;
+    }
+
+    /**
+     * Vote on powering off the radio for a reason. The radio will be turned on only when there is
+     * no reason to power it off. When any of the voters want to power it off, it will be turned
+     * off. In case of emergency, the radio will be turned on even if there are some reasons for
+     * powering it off, and these radio off votes will be cleared.
+     * Multiple apps can vote for the same reason and the last vote will take effect. Each app is
+     * responsible for its vote. A powering-off vote of a reason will be maintained until it is
+     * cleared by calling {@link clearRadioPowerOffForReason} for that reason, or an emergency call
+     * is made, or the device is rebooted. When an app comes backup from a crash, it needs to make
+     * sure if its vote is as expected. An app can use the API {@link getRadioPowerOffReasons} to
+     * check its vote.
+     *
+     * @param reason The reason for powering off radio.
+     * @throws SecurityException if the caller does not have MODIFY_PHONE_STATE permission.
+     * @throws IllegalStateException if the Telephony service is not currently available.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
+    public void requestRadioPowerOffForReason(@RadioPowerReason int reason) {
         try {
             ITelephony telephony = getITelephony();
-            if (telephony != null)
-                return telephony.setRadioPower(turnOn);
+            if (telephony != null) {
+                if (!telephony.requestRadioPowerOffForReason(getSubId(), reason)) {
+                    throw new IllegalStateException("Telephony service is not available.");
+                }
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
         } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#setRadioPower", e);
+            Log.e(TAG, "Error calling ITelephony#requestRadioPowerOffForReason", e);
+            e.rethrowAsRuntimeException();
         }
-        return false;
+    }
+
+    /**
+     * Remove the vote on powering off the radio for a reason, as requested by
+     * {@link requestRadioPowerOffForReason}.
+     *
+     * @param reason The reason for powering off radio.
+     * @throws SecurityException if the caller does not have MODIFY_PHONE_STATE permission.
+     * @throws IllegalStateException if the Telephony service is not currently available.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
+    public void clearRadioPowerOffForReason(@RadioPowerReason int reason) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                if (!telephony.clearRadioPowerOffForReason(getSubId(), reason)) {
+                    throw new IllegalStateException("Telephony service is not available.");
+                }
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#clearRadioPowerOffForReason", e);
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Get reasons for powering off radio, as requested by {@link requestRadioPowerOffForReason}.
+     * If the reason set is empty, the radio is on in all cases.
+     *
+     * @return Set of reasons for powering off radio.
+     * @throws SecurityException if the caller does not have READ_PRIVILEGED_PHONE_STATE permission.
+     * @throws IllegalStateException if the Telephony service is not currently available.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
+    @NonNull
+    public Set<Integer> getRadioPowerOffReasons() {
+        Set<Integer> result = new HashSet<>();
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                result.addAll(telephony.getRadioPowerOffReasons(getSubId(),
+                        mContext.getOpPackageName(), mContext.getAttributionTag()));
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#getRadioPowerOffReasons", e);
+            e.rethrowAsRuntimeException();
+        }
+        return result;
     }
 
     /**
@@ -13037,20 +13207,21 @@
      *
      * @param enabled control enable or disable radio.
      * @see #resetAllCarrierActions()
+     *
+     * @deprecated - use the APIs {@link requestRadioPowerOffForReason} and
+     * {@link clearRadioPowerOffForReason}.
+     *
      * @hide
      */
+    @Deprecated
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public void setRadioEnabled(boolean enabled) {
-        try {
-            ITelephony service = getITelephony();
-            if (service != null) {
-                service.carrierActionSetRadioEnabled(
-                        getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enabled);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#carrierActionSetRadioEnabled", e);
+        if (enabled) {
+            clearRadioPowerOffForReason(RADIO_POWER_REASON_CARRIER);
+        } else {
+            requestRadioPowerOffForReason(RADIO_POWER_REASON_CARRIER);
         }
     }
 
@@ -16273,7 +16444,12 @@
      * the appropriate callback method on the callback object and passes the current (updated)
      * values.
      * <p>
-     *
+     * Note: Be aware of the permission requirements stated on the {@link TelephonyCallback}
+     * listeners you implement.  Your application must be granted these permissions in order to
+     * register a {@link TelephonyCallback} which requires them; a {@link SecurityException} will be
+     * thrown if you do not hold the required permissions for all {@link TelephonyCallback}
+     * listeners you implement.
+     * <p>
      * If this TelephonyManager object has been created with {@link #createForSubscriptionId},
      * applies to the given subId. Otherwise, applies to
      * {@link SubscriptionManager#getDefaultSubscriptionId()}. To register events for multiple
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 3ed87e1..f794a79 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -24,7 +24,6 @@
 import android.database.Cursor;
 import android.hardware.radio.V1_5.ApnTypes;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.provider.Telephony;
@@ -964,7 +963,7 @@
                 ServiceState.convertBearerBitmaskToNetworkTypeBitmask(bearerBitmask);
         }
         int mtuV4 = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU_V4));
-        if (mtuV4 == -1) {
+        if (mtuV4 == UNSET_MTU) {
             mtuV4 = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU));
         }
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index da1ffcd..42cac66 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -244,6 +244,44 @@
     boolean setRadioPower(boolean turnOn);
 
     /**
+     * Vote on powering off the radio for a reason. The radio will be turned on only when there is
+     * no reason to power it off. When any of the voters want to power it off, it will be turned
+     * off. In case of emergency, the radio will be turned on even if there are some reasons for
+     * powering it off, and these radio off votes will be cleared.
+     * Multiple apps can vote for the same reason and the last vote will take effect. Each app is
+     * responsible for its vote. A powering-off vote of a reason will be maintained until it is
+     * cleared by calling {@link clearRadioPowerOffForReason} for that reason, or an emergency call
+     * is made, or the device is rebooted. When an app comes backup from a crash, it needs to make
+     * sure if its vote is as expected. An app can use the API {@link getRadioPowerOffReasons} to
+     * check its vote.
+     *
+     * @param subId The subscription ID.
+     * @param reason The reason for powering off radio.
+     * @return true on success and false on failure.
+     */
+    boolean requestRadioPowerOffForReason(int subId, int reason);
+
+    /**
+     * Remove the vote on powering off the radio for a reasonas, requested by
+     * {@link requestRadioPowerOffForReason}.
+     *
+     * @param subId The subscription ID.
+     * @param reason The reason for powering off radio.
+     * @return true on success and false on failure.
+     */
+    boolean clearRadioPowerOffForReason(int subId, int reason);
+
+    /**
+     * Get reasons for powering off radio, as requested by {@link requestRadioPowerOffForReason}.
+     *
+     * @param subId The subscription ID.
+     * @param callingPackage The package making the call.
+     * @param callingFeatureId The feature in the package.
+     * @return List of reasons for powering off radio.
+     */
+    List getRadioPowerOffReasons(int subId, String callingPackage, String callingFeatureId);
+
+    /**
      * This method has been removed due to security and stability issues.
      */
     @UnsupportedAppUsage
@@ -2510,6 +2548,9 @@
     CellIdentity getLastKnownCellIdentity(int subId, String callingPackage,
             String callingFeatureId);
 
+    /** Check if telephony new data stack is enabled. */
+    boolean isUsingNewDataStack();
+
     /**
      *  @return true if the modem service is set successfully, false otherwise.
      */
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 e138d33..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
@@ -38,7 +37,10 @@
 ) {
     init {
         testSpec.setIsTablet(
-            WindowManagerStateHelper(instrumentation).currentState.wmState.isTablet
+            WindowManagerStateHelper(
+                instrumentation,
+                clearCacheAfterParsing = false
+            ).currentState.wmState.isTablet
         )
         tapl.setExpectedRotationCheckEnabled(true)
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 5e21252..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,20 @@
  */
 fun FlickerTestParameter.navBarWindowIsAlwaysVisible() {
     assertWm {
-        this.isAboveAppWindowVisible(ComponentMatcher.NAV_BAR)
+        this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR)
+    }
+}
+
+/**
+ * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the start
+ * and end of the WM trace
+ */
+fun FlickerTestParameter.navBarWindowIsVisibleAtStartAndEnd() {
+    assertWmStart {
+        this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR)
+    }
+    assertWmEnd {
+        this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR)
     }
 }
 
@@ -48,7 +61,7 @@
  */
 fun FlickerTestParameter.taskBarWindowIsAlwaysVisible() {
     assertWm {
-        this.isAboveAppWindowVisible(ComponentMatcher.TASK_BAR)
+        this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR)
     }
 }
 
@@ -58,7 +71,7 @@
  */
 fun FlickerTestParameter.taskBarWindowIsVisibleAtEnd() {
     assertWmEnd {
-        this.isAboveAppWindowVisible(ComponentMatcher.TASK_BAR)
+        this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR)
     }
 }
 
@@ -101,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)
     }
 }
 
@@ -123,7 +136,7 @@
  */
 fun FlickerTestParameter.taskBarLayerIsVisibleAtStart() {
     assertLayersStart {
-        this.isVisible(ComponentMatcher.TASK_BAR)
+        this.isVisible(ComponentNameMatcher.TASK_BAR)
     }
 }
 
@@ -133,7 +146,7 @@
  */
 fun FlickerTestParameter.taskBarLayerIsVisibleAtEnd() {
     assertLayersEnd {
-        this.isVisible(ComponentMatcher.TASK_BAR)
+        this.isVisible(ComponentNameMatcher.TASK_BAR)
     }
 }
 
@@ -143,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)
     }
 }
 
@@ -158,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))
     }
 }
@@ -171,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))
     }
 }
@@ -193,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))
     }
 }
@@ -206,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))
     }
 }
@@ -231,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()) {
@@ -278,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/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
index 75900df..d08cb55 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
@@ -26,4 +26,9 @@
  * @param rotation New device rotation
  */
 fun Flicker.setRotation(rotation: Int) =
-    ChangeDisplayOrientationRule.setRotation(rotation, instrumentation, wmHelper)
+    ChangeDisplayOrientationRule.setRotation(
+        rotation,
+        instrumentation,
+        clearCacheAfterParsing = false,
+        wmHelper = wmHelper
+)
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 457e973..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
@@ -17,14 +17,17 @@
 package com.android.server.wm.flicker.ime
 
 import android.platform.test.annotations.FlakyTest
+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.annotation.Group4
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -65,4 +68,20 @@
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    /** {@inheritDoc} */
+    @Ignore("Nav bar window becomes invisible during quick switch")
+    @Test
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+    /**
+     * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the start
+     * and end of the WM trace
+     */
+    @Presubmit
+    @Test
+    fun navBarWindowIsVisibleAtStartAndEnd() {
+        Assume.assumeFalse(testSpec.isTablet)
+        testSpec.navBarWindowIsVisibleAtStartAndEnd()
+    }
 }
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 e007fe3..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
@@ -17,14 +17,17 @@
 package com.android.server.wm.flicker.quickswitch
 
 import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -76,4 +79,20 @@
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    /** {@inheritDoc} */
+    @Ignore("Nav bar window becomes invisible during quick switch")
+    @Test
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+    /**
+     * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the start
+     * and end of the WM trace
+     */
+    @Presubmit
+    @Test
+    fun navBarWindowIsVisibleAtStartAndEnd() {
+        Assume.assumeFalse(testSpec.isTablet)
+        testSpec.navBarWindowIsVisibleAtStartAndEnd()
+    }
 }
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 6f78ba8..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
@@ -17,14 +17,17 @@
 package com.android.server.wm.flicker.quickswitch
 
 import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -66,4 +69,20 @@
     @FlakyTest(bugId = 228009808)
     @Test
     override fun endsWithApp2BeingOnTop() = super.endsWithApp2BeingOnTop()
+
+    /** {@inheritDoc} */
+    @Ignore("Nav bar window becomes invisible during quick switch")
+    @Test
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+    /**
+     * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the start
+     * and end of the WM trace
+     */
+    @Presubmit
+    @Test
+    fun navBarWindowIsVisibleAtStartAndEnd() {
+        Assume.assumeFalse(testSpec.isTablet)
+        testSpec.navBarWindowIsVisibleAtStartAndEnd()
+    }
 }
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 510043b..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
@@ -29,9 +29,12 @@
 import com.android.server.wm.flicker.annotation.Group1
 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.flicker.navBarWindowIsVisibleAtStartAndEnd
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.common.Rect
+import org.junit.Assume
 import org.junit.FixMethodOrder
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -149,7 +152,7 @@
     @Test
     fun startsWithLauncherWindowsCoverFullScreen() {
         testSpec.assertWmStart {
-            this.visibleRegion(ComponentMatcher.LAUNCHER).coversExactly(startDisplayBounds)
+            this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
         }
     }
 
@@ -161,7 +164,7 @@
     @Test
     fun startsWithLauncherLayersCoverFullScreen() {
         testSpec.assertLayersStart {
-            this.visibleRegion(ComponentMatcher.LAUNCHER).coversExactly(startDisplayBounds)
+            this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
         }
     }
 
@@ -172,7 +175,7 @@
     @Test
     fun startsWithLauncherBeingOnTop() {
         testSpec.assertWmStart {
-            this.isAppWindowOnTop(ComponentMatcher.LAUNCHER)
+            this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
         }
     }
 
@@ -225,9 +228,9 @@
     @Test
     fun launcherWindowBecomesAndStaysInvisible() {
         testSpec.assertWm {
-            this.isAppWindowOnTop(ComponentMatcher.LAUNCHER)
+            this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
                     .then()
-                    .isAppWindowNotOnTop(ComponentMatcher.LAUNCHER)
+                    .isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER)
         }
     }
 
@@ -240,9 +243,9 @@
     @Test
     fun launcherLayerBecomesAndStaysInvisible() {
         testSpec.assertLayers {
-            this.isVisible(ComponentMatcher.LAUNCHER)
+            this.isVisible(ComponentNameMatcher.LAUNCHER)
                     .then()
-                    .isInvisible(ComponentMatcher.LAUNCHER)
+                    .isInvisible(ComponentNameMatcher.LAUNCHER)
         }
     }
 
@@ -255,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)
         }
@@ -272,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)
         }
@@ -296,6 +299,22 @@
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
 
+    /** {@inheritDoc} */
+    @Ignore("Nav bar window becomes invisible during quick switch")
+    @Test
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+    /**
+     * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the start
+     * and end of the WM trace
+     */
+    @Presubmit
+    @Test
+    fun navBarWindowIsVisibleAtStartAndEnd() {
+        Assume.assumeFalse(testSpec.isTablet)
+        testSpec.navBarWindowIsVisibleAtStartAndEnd()
+    }
+
     companion object {
         /** {@inheritDoc} */
         private var startDisplayBounds = Rect.EMPTY
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/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
index c4cb33d..4426551 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -123,16 +123,27 @@
         }
 
         boolean found = false;
+        boolean remountSystem = false;
+        boolean remountVendor = false;
         for (String file : files) {
             CommandResult result = getDevice().executeShellV2Command("ls " + file);
             if (result.getStatus() == CommandStatus.SUCCESS) {
                 found = true;
-                break;
+                if (file.startsWith("/system")) {
+                    remountSystem = true;
+                } else if (file.startsWith("/vendor")) {
+                    remountVendor = true;
+                }
             }
         }
 
         if (found) {
-            getDevice().remountSystemWritable();
+            if (remountSystem) {
+                getDevice().remountSystemWritable();
+            }
+            if (remountVendor) {
+                getDevice().remountVendorWritable();
+            }
             for (String file : files) {
                 getDevice().executeShellCommand("rm -rf " + file);
             }
@@ -150,7 +161,11 @@
         if (!getDevice().isAdbRoot()) {
             getDevice().enableAdbRoot();
         }
-        getDevice().remountSystemWritable();
+        if ("system".equals(partition)) {
+            getDevice().remountSystemWritable();
+        } else if ("vendor".equals(partition)) {
+            getDevice().remountVendorWritable();
+        }
         assertTrue(getDevice().pushFile(apex, "/" + partition + "/apex/" + fileName));
     }
 
@@ -158,7 +173,7 @@
         if (!getDevice().isAdbRoot()) {
             getDevice().enableAdbRoot();
         }
-        getDevice().remountSystemWritable();
+        getDevice().remountVendorWritable();
         File file = File.createTempFile("test-vendor-apex-allow-list", ".xml");
         try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
             final String fmt =
diff --git a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
index 1664746..a2842b6 100644
--- a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
+++ b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
@@ -22,6 +22,7 @@
 import android.graphics.Paint;
 import android.graphics.Paint.Align;
 import android.os.Bundle;
+import android.os.Trace;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.Display;
@@ -30,9 +31,9 @@
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
-import android.os.Trace;
 import android.view.Window;
 import android.view.WindowManager;
+
 import java.math.RoundingMode;
 import java.text.DecimalFormat;
 
@@ -280,6 +281,17 @@
         WindowManager.LayoutParams params = w.getAttributes();
 
         int modeIndex = (mCurrentModeIndex + 1) % mDisplayModes.length;
+        while (modeIndex != mCurrentModeIndex) {
+            // skip modes with different resolutions
+            Mode currentMode = mDisplayModes[mCurrentModeIndex];
+            Mode nextMode = mDisplayModes[modeIndex];
+            if (currentMode.getPhysicalHeight() == nextMode.getPhysicalHeight()
+                    && currentMode.getPhysicalWidth() == nextMode.getPhysicalWidth()) {
+                break;
+            }
+            modeIndex = (modeIndex + 1) % mDisplayModes.length;
+        }
+
         params.preferredDisplayModeId = mDisplayModes[modeIndex].getModeId();
         w.setAttributes(params);
 
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index ebe9b57..edd6dd3 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -30,6 +30,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.lang.reflect.Field;
 import java.util.Map;
 
 /**
@@ -45,6 +46,9 @@
      * catch crashes.
      */
     public static final boolean HOLD_MAIN_THREAD = false;
+    private static final Field MESSAGE_QUEUE_MESSAGES_FIELD;
+    private static final Field MESSAGE_NEXT_FIELD;
+    private static final Field MESSAGE_WHEN_FIELD;
 
     private Looper mLooper;
     private MessageQueue mQueue;
@@ -54,6 +58,19 @@
     private Runnable mEmptyMessage;
     private TestLooperManager mQueueWrapper;
 
+    static {
+        try {
+            MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
+            MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
+            MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
+            MESSAGE_NEXT_FIELD.setAccessible(true);
+            MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
+            MESSAGE_WHEN_FIELD.setAccessible(true);
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException("Failed to initialize TestableLooper", e);
+        }
+    }
+
     public TestableLooper(Looper l) throws Exception {
         this(acquireLooperManager(l), l);
     }
@@ -119,6 +136,33 @@
         while (processQueuedMessages() != 0) ;
     }
 
+    public void moveTimeForward(long milliSeconds) {
+        try {
+            Message msg = getMessageLinkedList();
+            while (msg != null) {
+                long updatedWhen = msg.getWhen() - milliSeconds;
+                if (updatedWhen < 0) {
+                    updatedWhen = 0;
+                }
+                MESSAGE_WHEN_FIELD.set(msg, updatedWhen);
+                msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
+            }
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException("Access failed in TestableLooper: set - Message.when", e);
+        }
+    }
+
+    private Message getMessageLinkedList() {
+        try {
+            MessageQueue queue = mLooper.getQueue();
+            return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(
+                    "Access failed in TestableLooper: get - MessageQueue.mMessages",
+                    e);
+        }
+    }
+
     private int processQueuedMessages() {
         int count = 0;
         mEmptyMessage = () -> { };
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/testables/tests/src/android/testing/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java
index 25f6a488..0f491b8 100644
--- a/tests/testables/tests/src/android/testing/TestableLooperTest.java
+++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java
@@ -19,15 +19,19 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
 
 import android.os.Handler;
 import android.os.Looper;
@@ -162,7 +166,7 @@
 
     @Test
     public void testCorrectLooperExecution() throws Exception {
-        boolean[] hasRun = new boolean[] { false };
+        boolean[] hasRun = new boolean[]{false};
         Runnable r = () -> {
             assertEquals("Should run on main looper", Looper.getMainLooper(), Looper.myLooper());
             hasRun[0] = true;
@@ -177,4 +181,63 @@
             testableLooper.destroy();
         }
     }
+
+    @Test
+    public void testDelayedDispatchNoTimeMove() {
+        Handler handler = spy(new Handler(mTestableLooper.getLooper()));
+        InOrder inOrder = inOrder(handler);
+
+        final Message messageA = handler.obtainMessage(1);
+        final Message messageB = handler.obtainMessage(2);
+
+        handler.sendMessageDelayed(messageA, 0);
+        handler.sendMessageDelayed(messageB, 0);
+
+        mTestableLooper.processAllMessages();
+
+        inOrder.verify(handler).dispatchMessage(messageA);
+        inOrder.verify(handler).dispatchMessage(messageB);
+    }
+
+    @Test
+    public void testDelayedMessageDoesntSend() {
+        Handler handler = spy(new Handler(mTestableLooper.getLooper()));
+        InOrder inOrder = inOrder(handler);
+
+        final Message messageA = handler.obtainMessage(1);
+        final Message messageB = handler.obtainMessage(2);
+        final Message messageC = handler.obtainMessage(3);
+
+        handler.sendMessageDelayed(messageA, 0);
+        handler.sendMessageDelayed(messageB, 0);
+        handler.sendMessageDelayed(messageC, 500);
+
+        mTestableLooper.processAllMessages();
+
+        inOrder.verify(handler).dispatchMessage(messageA);
+        inOrder.verify(handler).dispatchMessage(messageB);
+        verify(handler, never()).dispatchMessage(messageC);
+    }
+
+    @Test
+    public void testMessageSendsAfterDelay() {
+        Handler handler = spy(new Handler(mTestableLooper.getLooper()));
+        InOrder inOrder = inOrder(handler);
+
+        final Message messageA = handler.obtainMessage(1);
+        final Message messageB = handler.obtainMessage(2);
+        final Message messageC = handler.obtainMessage(3);
+
+        handler.sendMessageDelayed(messageA, 0);
+        handler.sendMessageDelayed(messageB, 0);
+        handler.sendMessageDelayed(messageC, 500);
+
+        mTestableLooper.moveTimeForward(500);
+        mTestableLooper.processAllMessages();
+
+        inOrder.verify(handler).dispatchMessage(messageA);
+        inOrder.verify(handler).dispatchMessage(messageB);
+        inOrder.verify(handler).dispatchMessage(messageC);
+    }
+
 }
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/aapt/Command.cpp b/tools/aapt/Command.cpp
index fecc7b3..d02fd83 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -1028,7 +1028,6 @@
             // These permissions are required by services implementing services
             // the system binds to (IME, Accessibility, PrintServices, etc.)
             bool hasBindDeviceAdminPermission = false;
-            bool hasBindInputMethodPermission = false;
             bool hasBindAccessibilityServicePermission = false;
             bool hasBindPrintServicePermission = false;
             bool hasBindNfcServicePermission = false;
@@ -1757,7 +1756,6 @@
                     hasMetaHostPaymentCategory = false;
                     hasMetaOffHostPaymentCategory = false;
                     hasBindDeviceAdminPermission = false;
-                    hasBindInputMethodPermission = false;
                     hasBindAccessibilityServicePermission = false;
                     hasBindPrintServicePermission = false;
                     hasBindNfcServicePermission = false;
@@ -1871,9 +1869,7 @@
                             String8 permission = AaptXml::getAttribute(tree, PERMISSION_ATTR,
                                     &error);
                             if (error == "") {
-                                if (permission == "android.permission.BIND_INPUT_METHOD") {
-                                    hasBindInputMethodPermission = true;
-                                } else if (permission ==
+                                if (permission ==
                                         "android.permission.BIND_ACCESSIBILITY_SERVICE") {
                                     hasBindAccessibilityServicePermission = true;
                                 } else if (permission ==
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index b9de11b..47750fc 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -2970,14 +2970,6 @@
                     }
                     e->setNameIndex(keyStrings.add(e->getName(), true));
 
-                    // If this entry has no values for other configs,
-                    // and is the default config, then it is special.  Otherwise
-                    // we want to add it with the config info.
-                    ConfigDescription* valueConfig = NULL;
-                    if (N != 1 || config == nullConfig) {
-                        valueConfig = &config;
-                    }
-
                     status_t err = e->prepareFlatten(&valueStrings, this,
                             &configTypeName, &config);
                     if (err != NO_ERROR) {
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/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
index d19f4cc..12f3a16 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -19,6 +19,8 @@
 import com.android.tools.lint.client.api.IssueRegistry
 import com.android.tools.lint.client.api.Vendor
 import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.android.lint.aidl.EnforcePermissionDetector
+import com.google.android.lint.aidl.ManualPermissionCheckDetector
 import com.google.android.lint.parcel.SaferParcelChecker
 import com.google.auto.service.AutoService
 
@@ -36,6 +38,7 @@
         CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED,
         EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
         EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
+        ManualPermissionCheckDetector.ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
         SaferParcelChecker.ISSUE_UNSAFE_API_USAGE,
         PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS
     )
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt b/tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt
new file mode 100644
index 0000000..82eb8ed
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint
+
+import com.google.android.lint.model.Method
+
+const val CLASS_STUB = "Stub"
+const val CLASS_CONTEXT = "android.content.Context"
+const val CLASS_ACTIVITY_MANAGER_SERVICE = "com.android.server.am.ActivityManagerService"
+const val CLASS_ACTIVITY_MANAGER_INTERNAL = "android.app.ActivityManagerInternal"
+
+// Enforce permission APIs
+val ENFORCE_PERMISSION_METHODS = listOf(
+        Method(CLASS_CONTEXT, "checkPermission"),
+        Method(CLASS_CONTEXT, "checkCallingPermission"),
+        Method(CLASS_CONTEXT, "checkCallingOrSelfPermission"),
+        Method(CLASS_CONTEXT, "enforcePermission"),
+        Method(CLASS_CONTEXT, "enforceCallingPermission"),
+        Method(CLASS_CONTEXT, "enforceCallingOrSelfPermission"),
+        Method(CLASS_ACTIVITY_MANAGER_SERVICE, "checkPermission"),
+        Method(CLASS_ACTIVITY_MANAGER_INTERNAL, "enforceCallingPermission")
+)
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
index 192dba1..48540b1d 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
@@ -30,13 +30,13 @@
 import com.android.tools.lint.detector.api.interprocedural.searchForPaths
 import com.intellij.psi.PsiAnonymousClass
 import com.intellij.psi.PsiMethod
+import java.util.LinkedList
 import org.jetbrains.uast.UCallExpression
 import org.jetbrains.uast.UElement
 import org.jetbrains.uast.UMethod
 import org.jetbrains.uast.UParameter
 import org.jetbrains.uast.USimpleNameReferenceExpression
 import org.jetbrains.uast.visitor.AbstractUastVisitor
-import java.util.LinkedList
 
 /**
  * A lint checker to detect potential package visibility issues for system's APIs. APIs working
@@ -362,14 +362,18 @@
             name: String,
             matchArgument: Boolean = true,
             checkCaller: Boolean = false
-        ): this(clazz, name) {
+        ) : this(clazz, name) {
             this.matchArgument = matchArgument
             this.checkCaller = checkCaller
         }
 
         constructor(
             method: PsiMethod
-        ): this(method.containingClass?.qualifiedName ?: "", method.name)
+        ) : this(method.containingClass?.qualifiedName ?: "", method.name)
+
+        constructor(
+            method: com.google.android.lint.model.Method
+        ) : this(method.clazz, method.name)
     }
 
     /**
@@ -380,7 +384,7 @@
         val typeName: String,
         val parameterName: String
     ) {
-        constructor(uParameter: UParameter): this(
+        constructor(uParameter: UParameter) : this(
             uParameter.type.canonicalText,
             uParameter.name.lowercase()
         )
@@ -405,19 +409,13 @@
         // A valid call path list needs to contain a start node and a sink node
         private const val VALID_CALL_PATH_NODES_SIZE = 2
 
-        private const val CLASS_STUB = "Stub"
         private const val CLASS_STRING = "java.lang.String"
         private const val CLASS_PACKAGE_MANAGER = "android.content.pm.PackageManager"
         private const val CLASS_IPACKAGE_MANAGER = "android.content.pm.IPackageManager"
         private const val CLASS_APPOPS_MANAGER = "android.app.AppOpsManager"
-        private const val CLASS_CONTEXT = "android.content.Context"
         private const val CLASS_BINDER = "android.os.Binder"
         private const val CLASS_PACKAGE_MANAGER_INTERNAL =
             "android.content.pm.PackageManagerInternal"
-        private const val CLASS_ACTIVITY_MANAGER_SERVICE =
-            "com.android.server.am.ActivityManagerService"
-        private const val CLASS_ACTIVITY_MANAGER_INTERNAL =
-            "android.app.ActivityManagerInternal"
 
         // Patterns of package name parameter
         private val PACKAGE_NAME_PATTERNS = setOf(
@@ -455,16 +453,9 @@
         )
 
         // Enforce permission APIs
-        private val ENFORCE_PERMISSION_METHODS = listOf(
-            Method(CLASS_CONTEXT, "checkPermission"),
-            Method(CLASS_CONTEXT, "checkCallingPermission"),
-            Method(CLASS_CONTEXT, "checkCallingOrSelfPermission"),
-            Method(CLASS_CONTEXT, "enforcePermission"),
-            Method(CLASS_CONTEXT, "enforceCallingPermission"),
-            Method(CLASS_CONTEXT, "enforceCallingOrSelfPermission"),
-            Method(CLASS_ACTIVITY_MANAGER_SERVICE, "checkPermission"),
-            Method(CLASS_ACTIVITY_MANAGER_INTERNAL, "enforceCallingPermission")
-        )
+        private val ENFORCE_PERMISSION_METHODS =
+                com.google.android.lint.ENFORCE_PERMISSION_METHODS
+                        .map(PackageVisibilityDetector::Method)
 
         private val BYPASS_STUBS = listOf(
             "android.content.pm.IPackageDataObserver.Stub",
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/Constants.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
new file mode 100644
index 0000000..8ee3763
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/Constants.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.google.android.lint.aidl
+
+const val ANNOTATION_ENFORCE_PERMISSION = "android.annotation.EnforcePermission"
+const val ANNOTATION_REQUIRES_NO_PERMISSION = "android.annotation.RequiresNoPermission"
+const val ANNOTATION_PERMISSION_MANUALLY_ENFORCED = "android.annotation.PermissionManuallyEnforced"
+
+val AIDL_PERMISSION_ANNOTATIONS = listOf(
+        ANNOTATION_ENFORCE_PERMISSION,
+        ANNOTATION_REQUIRES_NO_PERMISSION,
+        ANNOTATION_PERMISSION_MANUALLY_ENFORCED
+)
+
+const val IINTERFACE_INTERFACE = "android.os.IInterface"
+
+/**
+ * If a non java (e.g. c++) backend is enabled, the @EnforcePermission
+ * annotation cannot be used.  At time of writing, the mechanism
+ * is not implemented for non java backends.
+ * TODO: b/242564874 (have lint know which interfaces have the c++ backend enabled)
+ * rather than hard coding this list?
+ */
+val EXCLUDED_CPP_INTERFACES = listOf(
+        "AdbTransportType",
+        "FingerprintAndPairDevice",
+        "IAdbCallback",
+        "IAdbManager",
+        "PairDevice",
+        "IStatsBootstrapAtomService",
+        "StatsBootstrapAtom",
+        "StatsBootstrapAtomValue",
+        "FixedSizeArrayExample",
+        "PlaybackTrackMetadata",
+        "RecordTrackMetadata",
+        "SinkMetadata",
+        "SourceMetadata",
+        "IUpdateEngineStable",
+        "IUpdateEngineStableCallback",
+        "AudioCapabilities",
+        "ConfidenceLevel",
+        "ModelParameter",
+        "ModelParameterRange",
+        "Phrase",
+        "PhraseRecognitionEvent",
+        "PhraseRecognitionExtra",
+        "PhraseSoundModel",
+        "Properties",
+        "RecognitionConfig",
+        "RecognitionEvent",
+        "RecognitionMode",
+        "RecognitionStatus",
+        "SoundModel",
+        "SoundModelType",
+        "Status",
+        "IThermalService",
+        "IPowerManager",
+        "ITunerResourceManager"
+)
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
similarity index 96%
rename from tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt
rename to tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
index 9f21618..a415217 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package com.google.android.lint
+package com.google.android.lint.aidl
 
 import com.android.tools.lint.client.api.UElementHandler
 import com.android.tools.lint.detector.api.AnnotationInfo
 import com.android.tools.lint.detector.api.AnnotationOrigin
 import com.android.tools.lint.detector.api.AnnotationUsageInfo
 import com.android.tools.lint.detector.api.AnnotationUsageType
-import com.android.tools.lint.detector.api.ConstantEvaluator
 import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.ConstantEvaluator
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Implementation
 import com.android.tools.lint.detector.api.Issue
@@ -34,8 +34,8 @@
 import com.intellij.psi.PsiClass
 import com.intellij.psi.PsiMethod
 import org.jetbrains.uast.UAnnotation
-import org.jetbrains.uast.UElement
 import org.jetbrains.uast.UClass
+import org.jetbrains.uast.UElement
 import org.jetbrains.uast.UMethod
 
 /**
@@ -54,12 +54,11 @@
  */
 class EnforcePermissionDetector : Detector(), SourceCodeScanner {
 
-    val ENFORCE_PERMISSION = "android.annotation.EnforcePermission"
     val BINDER_CLASS = "android.os.Binder"
     val JAVA_OBJECT = "java.lang.Object"
 
     override fun applicableAnnotations(): List<String> {
-        return listOf(ENFORCE_PERMISSION)
+        return listOf(ANNOTATION_ENFORCE_PERMISSION)
     }
 
     override fun getApplicableUastTypes(): List<Class<out UElement>> {
@@ -99,8 +98,8 @@
         overriddenMethod: PsiMethod,
         checkEquivalence: Boolean = true
     ) {
-        val overridingAnnotation = overridingMethod.getAnnotation(ENFORCE_PERMISSION)
-        val overriddenAnnotation = overriddenMethod.getAnnotation(ENFORCE_PERMISSION)
+        val overridingAnnotation = overridingMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
+        val overriddenAnnotation = overriddenMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
         val location = context.getLocation(element)
         val overridingClass = overridingMethod.parent as PsiClass
         val overriddenClass = overriddenMethod.parent as PsiClass
@@ -133,8 +132,8 @@
         extendedClass: PsiClass,
         checkEquivalence: Boolean = true
     ) {
-        val newAnnotation = newClass.getAnnotation(ENFORCE_PERMISSION)
-        val extendedAnnotation = extendedClass.getAnnotation(ENFORCE_PERMISSION)
+        val newAnnotation = newClass.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
+        val extendedAnnotation = extendedClass.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
 
         val location = context.getLocation(element)
         val newClassName = newClass.qualifiedName
@@ -180,7 +179,7 @@
     override fun createUastHandler(context: JavaContext): UElementHandler {
         return object : UElementHandler() {
             override fun visitAnnotation(node: UAnnotation) {
-                if (node.qualifiedName != ENFORCE_PERMISSION) {
+                if (node.qualifiedName != ANNOTATION_ENFORCE_PERMISSION) {
                     return
                 }
                 val method = node.uastParent as? UMethod
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
new file mode 100644
index 0000000..5106111
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint.aidl
+
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Location
+import com.intellij.psi.PsiVariable
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.ULiteralExpression
+import org.jetbrains.uast.UQualifiedReferenceExpression
+import org.jetbrains.uast.USimpleNameReferenceExpression
+import org.jetbrains.uast.asRecursiveLogString
+
+/**
+ * Helper ADT class that facilitates the creation of lint auto fixes
+ *
+ * Handles "Single" permission checks that should be migrated to @EnforcePermission(...), as well as consecutive checks
+ * that should be migrated to @EnforcePermission(allOf={...})
+ *
+ * TODO: handle anyOf style annotations
+ */
+sealed class EnforcePermissionFix {
+    abstract fun locations(): List<Location>
+    abstract fun javaAnnotationParameter(): String
+
+    fun javaAnnotation(): String = "@$ANNOTATION_ENFORCE_PERMISSION(${javaAnnotationParameter()})"
+
+    companion object {
+        fun fromCallExpression(callExpression: UCallExpression, context: JavaContext): SingleFix =
+            SingleFix(
+                getPermissionCheckLocation(context, callExpression),
+                getPermissionCheckArgumentValue(callExpression)
+            )
+
+        fun maybeAddManifestPrefix(permissionName: String): String =
+            if (permissionName.contains(".")) permissionName
+            else "android.Manifest.permission.$permissionName"
+
+        /**
+         * Given a permission check, get its proper location
+         * so that a lint fix can remove the entire expression
+         */
+        private fun getPermissionCheckLocation(
+            context: JavaContext,
+            callExpression: UCallExpression
+        ):
+                Location {
+            val javaPsi = callExpression.javaPsi!!
+            return Location.create(
+                context.file,
+                javaPsi.containingFile?.text,
+                javaPsi.textRange.startOffset,
+                // unfortunately the element doesn't include the ending semicolon
+                javaPsi.textRange.endOffset + 1
+            )
+        }
+
+        /**
+         * Given a permission check and an argument,
+         * pull out the permission value that is being used
+         */
+        private fun getPermissionCheckArgumentValue(
+            callExpression: UCallExpression,
+            argumentPosition: Int = 0
+        ): String {
+
+            val identifier = when (
+                val argument = callExpression.valueArguments.getOrNull(argumentPosition)
+            ) {
+                is UQualifiedReferenceExpression -> when (val selector = argument.selector) {
+                    is USimpleNameReferenceExpression ->
+                        ((selector.resolve() as PsiVariable).computeConstantValue() as String)
+
+                    else -> throw RuntimeException(
+                        "Couldn't resolve argument: ${selector.asRecursiveLogString()}"
+                    )
+                }
+
+                is USimpleNameReferenceExpression -> (
+                        (argument.resolve() as PsiVariable).computeConstantValue() as String)
+
+                is ULiteralExpression -> argument.value as String
+
+                else -> throw RuntimeException(
+                    "Couldn't resolve argument: ${argument?.asRecursiveLogString()}"
+                )
+            }
+
+            return identifier.substringAfterLast(".")
+        }
+    }
+}
+
+data class SingleFix(val location: Location, val permissionName: String) : EnforcePermissionFix() {
+    override fun locations(): List<Location> = listOf(this.location)
+    override fun javaAnnotationParameter(): String = maybeAddManifestPrefix(this.permissionName)
+}
+data class AllOfFix(val checks: List<SingleFix>) : EnforcePermissionFix() {
+    override fun locations(): List<Location> = this.checks.map { it.location }
+    override fun javaAnnotationParameter(): String =
+        "allOf={${
+            this.checks.joinToString(", ") { maybeAddManifestPrefix(it.permissionName) }
+        }}"
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt
new file mode 100644
index 0000000..2cea394
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint.aidl
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.google.android.lint.CLASS_STUB
+import com.google.android.lint.ENFORCE_PERMISSION_METHODS
+import com.intellij.psi.PsiAnonymousClass
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UIfExpression
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UQualifiedReferenceExpression
+
+/**
+ * Looks for methods implementing generated AIDL interface stubs
+ * that can have simple permission checks migrated to
+ * @EnforcePermission annotations
+ *
+ * TODO: b/242564870 (enable parse and autoFix of .aidl files)
+ */
+@Suppress("UnstableApiUsage")
+class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
+    override fun getApplicableUastTypes(): List<Class<out UElement?>> =
+        listOf(UMethod::class.java)
+
+    override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
+
+    private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
+        override fun visitMethod(node: UMethod) {
+            val interfaceName = getContainingAidlInterface(node)
+                .takeUnless(EXCLUDED_CPP_INTERFACES::contains) ?: return
+            val body = (node.uastBody as? UBlockExpression) ?: return
+            val fix = accumulateSimplePermissionCheckFixes(body) ?: return
+
+            val javaRemoveFixes = fix.locations().map {
+                fix()
+                    .replace()
+                    .reformat(true)
+                    .range(it)
+                    .with("")
+                    .autoFix()
+                    .build()
+            }
+
+            val javaAnnotateFix = fix()
+                .annotate(fix.javaAnnotation())
+                .range(context.getLocation(node))
+                .autoFix()
+                .build()
+
+            val message =
+                "$interfaceName permission check can be converted to @EnforcePermission annotation"
+
+            context.report(
+                ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
+                fix.locations().last(),
+                message,
+                fix().composite(*javaRemoveFixes.toTypedArray(), javaAnnotateFix)
+            )
+        }
+
+        /**
+         * Walk the expressions in the method, looking for simple permission checks.
+         *
+         * If a single permission check is found at the beginning of the method,
+         * this should be migrated to @EnforcePermission(value).
+         *
+         * If multiple consecutive permission checks are found,
+         * these should be migrated to @EnforcePermission(allOf={value1, value2, ...})
+         *
+         * As soon as something other than a permission check is encountered, stop looking,
+         * as some other business logic is happening that prevents an automated fix.
+         */
+        private fun accumulateSimplePermissionCheckFixes(methodBody: UBlockExpression):
+                EnforcePermissionFix? {
+            val singleFixes = mutableListOf<SingleFix>()
+            for (expression in methodBody.expressions) {
+                singleFixes.add(getPermissionCheckFix(expression) ?: break)
+            }
+            return when (singleFixes.size) {
+                0 -> null
+                1 -> singleFixes[0]
+                else -> AllOfFix(singleFixes)
+            }
+        }
+
+        /**
+         * If an expression boils down to a permission check, return
+         * the helper for creating a lint auto fix to @EnforcePermission
+         */
+        private fun getPermissionCheckFix(startingExpression: UElement?):
+                SingleFix? {
+            return when (startingExpression) {
+                is UQualifiedReferenceExpression -> getPermissionCheckFix(
+                    startingExpression.selector
+                )
+
+                is UIfExpression -> getPermissionCheckFix(startingExpression.condition)
+
+                is UCallExpression -> {
+                    return if (isPermissionCheck(startingExpression))
+                        EnforcePermissionFix.fromCallExpression(startingExpression, context)
+                    else null
+                }
+
+                else -> null
+            }
+        }
+    }
+
+    companion object {
+
+        private val EXPLANATION = """
+            Whenever possible, method implementations of AIDL interfaces should use the @EnforcePermission
+            annotation to declare the permissions to be enforced.  The verification code is then
+            generated by the AIDL compiler, which also takes care of annotating the generated java
+            code.
+
+            This reduces the risk of bugs around these permission checks (that often become vulnerabilities).
+            It also enables easier auditing and review.
+
+            Please migrate to an @EnforcePermission annotation. (See: go/aidl-enforce-howto)
+        """.trimIndent()
+
+        @JvmField
+        val ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION = Issue.create(
+            id = "UseEnforcePermissionAnnotation",
+            briefDescription = "Manual permission check can be @EnforcePermission annotation",
+            explanation = EXPLANATION,
+            category = Category.SECURITY,
+            priority = 5,
+            severity = Severity.WARNING,
+            implementation = Implementation(
+                ManualPermissionCheckDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            ),
+            enabledByDefault = false, // TODO: enable once b/241171714 is resolved
+        )
+
+        private fun isPermissionCheck(callExpression: UCallExpression): Boolean {
+            val method = callExpression.resolve() ?: return false
+            val className = method.containingClass?.qualifiedName
+            return ENFORCE_PERMISSION_METHODS.any {
+                it.clazz == className && it.name == method.name
+            }
+        }
+
+        /**
+         * given a UMethod, determine if this method is
+         * an entrypoint to an interface generated by AIDL,
+         * returning the interface name if so
+         */
+        fun getContainingAidlInterface(node: UMethod): String? {
+            if (!isInClassCalledStub(node)) return null
+            for (superMethod in node.findSuperMethods()) {
+                for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements
+                    ?: continue) {
+                    if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) {
+                        return superMethod.containingClass?.name
+                    }
+                }
+            }
+            return null
+        }
+
+        private fun isInClassCalledStub(node: UMethod): Boolean {
+            (node.containingClass as? PsiAnonymousClass)?.let {
+                return it.baseClassReference.referenceName == CLASS_STUB
+            }
+            return node.containingClass?.extendsList?.referenceElements?.any {
+                it.referenceName == CLASS_STUB
+            } ?: false
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/tools/lint/checks/src/main/java/com/google/android/lint/model/Method.kt
similarity index 73%
copy from packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
copy to tools/lint/checks/src/main/java/com/google/android/lint/model/Method.kt
index 7c9df10..3939b61 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/model/Method.kt
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.common.data.model
+package com.google.android.lint.model
 
-/** Models a two-dimensional position */
-data class Position(
-    val x: Int,
-    val y: Int,
-)
+/**
+ * Data class to represent a Method
+ */
+data class Method(val clazz: String, val name: String) {
+    override fun toString(): String {
+        return "$clazz#$name"
+    }
+}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
similarity index 99%
rename from tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt
rename to tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
index 2cfc3fb..3c1d1e8 100644
--- a/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.google.android.lint
+package com.google.android.lint.aidl
 
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.checks.infrastructure.TestFile
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
new file mode 100644
index 0000000..1a1c6bc
--- /dev/null
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class ManualPermissionCheckDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = ManualPermissionCheckDetector()
+    override fun getIssues(): List<Issue> = listOf(
+        ManualPermissionCheckDetector
+            .ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION
+    )
+
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk()
+
+    fun testClass() {
+        lint().files(
+            java(
+                """
+                    import android.content.Context;
+                    import android.test.ITest;
+                    public class Foo extends ITest.Stub {
+                        private Context mContext;
+                        @Override
+                        public void test() throws android.os.RemoteException {
+                            mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
+                        }
+                    }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+                        mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
+                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
+                @@ -5 +5
+                +     @android.annotation.EnforcePermission(android.Manifest.permission.READ_CONTACTS)
+                @@ -7 +8
+                -         mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
+                """
+            )
+    }
+
+    fun testAnonClass() {
+        lint().files(
+            java(
+                """
+                    import android.content.Context;
+                    import android.test.ITest;
+                    public class Foo {
+                        private Context mContext;
+                        private ITest itest = new ITest.Stub() {
+                            @Override
+                            public void test() throws android.os.RemoteException {
+                                mContext.enforceCallingOrSelfPermission(
+                                    "android.Manifest.permission.READ_CONTACTS", "foo");
+                            }
+                        };
+                    }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:8: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+                            mContext.enforceCallingOrSelfPermission(
+                            ^
+                0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 8: Annotate with @EnforcePermission:
+                @@ -6 +6
+                +         @android.annotation.EnforcePermission(android.Manifest.permission.READ_CONTACTS)
+                @@ -8 +9
+                -             mContext.enforceCallingOrSelfPermission(
+                -                 "android.Manifest.permission.READ_CONTACTS", "foo");
+                """
+            )
+    }
+
+    fun testAllOf() {
+        lint().files(
+            java(
+                """
+                    import android.content.Context;
+                    import android.test.ITest;
+                    public class Foo {
+                        private Context mContext;
+                        private ITest itest = new ITest.Stub() {
+                            @Override
+                            public void test() throws android.os.RemoteException {
+                                mContext.enforceCallingOrSelfPermission(
+                                    "android.Manifest.permission.READ_CONTACTS", "foo");
+                                mContext.enforceCallingOrSelfPermission(
+                                    "android.Manifest.permission.WRITE_CONTACTS", "foo");
+                            }
+                        };
+                    }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Foo.java:10: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+                            mContext.enforceCallingOrSelfPermission(
+                            ^
+                0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+                Fix for src/Foo.java line 10: Annotate with @EnforcePermission:
+                @@ -6 +6                                                                                                                                                                                                       
+                +         @android.annotation.EnforcePermission(allOf={android.Manifest.permission.READ_CONTACTS, android.Manifest.permission.WRITE_CONTACTS})
+                @@ -8 +9
+                -             mContext.enforceCallingOrSelfPermission(
+                -                 "android.Manifest.permission.READ_CONTACTS", "foo");
+                -             mContext.enforceCallingOrSelfPermission(
+                -                 "android.Manifest.permission.WRITE_CONTACTS", "foo");
+                """
+            )
+    }
+
+    fun testPrecedingExpressions() {
+        lint().files(
+            java(
+                """
+                    import android.os.Binder;
+                    import android.test.ITest;
+                    public class Foo extends ITest.Stub {
+                        private mContext Context;
+                        @Override
+                        public void test() throws android.os.RemoteException {
+                            long uid = Binder.getCallingUid();
+                            mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
+                        }
+                    }
+                """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    companion object {
+        private val aidlStub: TestFile = java(
+            """
+               package android.test;
+               public interface ITest extends android.os.IInterface {
+                    public static abstract class Stub extends android.os.Binder implements android.test.ITest {}
+                    public void test() throws android.os.RemoteException;
+               }
+            """
+        ).indented()
+
+        private val contextStub: TestFile = java(
+            """
+                package android.content;
+                public class Context {
+                    public void enforceCallingOrSelfPermission(String permission, String message) {}
+                }
+            """
+        ).indented()
+
+        private val binderStub: TestFile = java(
+            """
+                package android.os;
+                public class Binder {
+                    public static int getCallingUid() {}
+                }
+            """
+        ).indented()
+
+        val stubs = arrayOf(aidlStub, contextStub, binderStub)
+    }
+}
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)")