Merge "Implement fade animation on SplitDecorManager" into tm-dev
diff --git a/apct-tests/perftests/packagemanager/src/android/content/pm/PackageInstallerBenchmark.java b/apct-tests/perftests/packagemanager/src/android/content/pm/PackageInstallerBenchmark.java
index 3b4f72b..e20f30d 100644
--- a/apct-tests/perftests/packagemanager/src/android/content/pm/PackageInstallerBenchmark.java
+++ b/apct-tests/perftests/packagemanager/src/android/content/pm/PackageInstallerBenchmark.java
@@ -17,10 +17,12 @@
 package android.content.pm;
 
 import android.Manifest;
+import android.app.UiAutomation;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
 import android.os.HandlerThread;
+import android.os.ParcelFileDescriptor;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
 import android.util.Log;
@@ -39,7 +41,10 @@
 import org.junit.Rule;
 import org.junit.Test;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
@@ -203,13 +208,33 @@
     }
 
     private void uninstallSession(BenchmarkState state, String...packageNames)
-            throws InterruptedException {
+            throws Exception {
         state.pauseTiming();
         uninstall(true /* stop at fail */, packageNames);
         mPackageInstaller.unregisterSessionCallback(mSessionCallback);
+        executeShellCommand("pm gc");
         state.resumeTiming();
     }
 
+    private static String executeShellCommand(String command) throws IOException {
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        final ParcelFileDescriptor stdout = uiAutomation.executeShellCommand(command);
+        try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(stdout);
+             ByteArrayOutputStream result = new ByteArrayOutputStream()) {
+            writeFullStream(inputStream, result);
+            return result.toString("UTF-8");
+        }
+    }
+
+    private static void writeFullStream(InputStream inputStream, OutputStream outputStream)
+            throws IOException {
+        final byte[] buffer = new byte[1024];
+        int length;
+        while ((length = inputStream.read(buffer)) != -1) {
+            outputStream.write(buffer, 0, length);
+        }
+    }
+
     @Test(timeout = 600_000L)
     public void commit_aSingleApkSession_untilFinishBenchmark() throws Exception {
         uninstall(false /* stop at fail */, TestApp.A);
@@ -247,8 +272,7 @@
     }
 
     @Test(timeout = 600_000L)
-    public void commit_aMultiplePackagesSession_untilFinishBenchmark()
-            throws IOException, InterruptedException {
+    public void commit_aMultiplePackagesSession_untilFinishBenchmark() throws Exception {
         uninstall(false /* stop at fail */, TestApp.A, TestApp.B, TestApp.C);
 
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
@@ -269,8 +293,7 @@
     }
 
     @Test(timeout = 600_000L)
-    public void commit_threeMultiplePackageSessions_untilFinishBenchmark()
-            throws Exception {
+    public void commit_threeMultiplePackageSessions_untilFinishBenchmark() throws Exception {
         uninstall(false /* stop at fail */, TestApp.A, TestApp.B, TestApp.C);
 
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
@@ -293,8 +316,7 @@
     }
 
     @Test(timeout = 600_000L)
-    public void commit_aMultipleApksSession_untilFinishBenchmark()
-            throws IOException, InterruptedException {
+    public void commit_aMultipleApksSession_untilFinishBenchmark() throws Exception {
         uninstall(false /* stop at fail */, TestApp.A);
 
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
index 88082f7..dd0d07f 100644
--- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
+++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
@@ -284,9 +284,9 @@
     /** @hide */
     public static final int DEFAULT_AM_MAX_SATIATED_BALANCE = 1440;
     /** @hide */
-    public static final int DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT = 28800;
+    public static final int DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT = 4000;
     /** @hide */
-    public static final int DEFAULT_AM_HARD_CONSUMPTION_LIMIT = 52000;
+    public static final int DEFAULT_AM_HARD_CONSUMPTION_LIMIT = 28_800;
     // TODO: add AlarmManager modifier default values
     /** @hide */
     public static final int DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT = 0;
@@ -373,9 +373,9 @@
     /** @hide */
     public static final int DEFAULT_JS_MAX_SATIATED_BALANCE = 60000;
     /** @hide */
-    public static final int DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT = 460_000;
+    public static final int DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT = 100_000;
     /** @hide */
-    public static final int DEFAULT_JS_HARD_CONSUMPTION_LIMIT = 900_000;
+    public static final int DEFAULT_JS_HARD_CONSUMPTION_LIMIT = 460_000;
     // TODO: add JobScheduler modifier default values
     /** @hide */
     public static final int DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT = 0;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 23056b5..405b12d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -68,7 +68,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -578,7 +577,7 @@
             Slog.d(TAG, printPendingQueueLocked());
         }
 
-        final List<JobStatus> pendingJobs = mService.mPendingJobs;
+        final PendingJobQueue pendingJobQueue = mService.mPendingJobQueue;
         final List<JobServiceContext> activeServices = mActiveServices;
 
         // To avoid GC churn, we recycle the arrays.
@@ -597,7 +596,7 @@
         // Update the priorities of jobs that aren't running, and also count the pending work types.
         // Do this before the following loop to hopefully reduce the cost of
         // shouldStopRunningJobLocked().
-        updateNonRunningPrioritiesLocked(pendingJobs, true);
+        updateNonRunningPrioritiesLocked(pendingJobQueue, true);
 
         for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
             final JobServiceContext js = activeServices.get(i);
@@ -620,9 +619,9 @@
 
         mWorkCountTracker.onCountDone();
 
-        for (int i = 0; i < pendingJobs.size(); i++) {
-            final JobStatus nextPending = pendingJobs.get(i);
-
+        JobStatus nextPending;
+        pendingJobQueue.resetIterator();
+        while ((nextPending = pendingJobQueue.next()) != null) {
             if (mRunningJobs.contains(nextPending)) {
                 continue;
             }
@@ -841,10 +840,11 @@
     }
 
     @GuardedBy("mLock")
-    private void updateNonRunningPrioritiesLocked(@NonNull final List<JobStatus> pendingJobs,
+    private void updateNonRunningPrioritiesLocked(@NonNull final PendingJobQueue jobQueue,
             boolean updateCounter) {
-        for (int i = 0; i < pendingJobs.size(); i++) {
-            final JobStatus pending = pendingJobs.get(i);
+        JobStatus pending;
+        jobQueue.resetIterator();
+        while ((pending = jobQueue.next()) != null) {
 
             // If job is already running, go to next job.
             if (mRunningJobs.contains(pending)) {
@@ -882,7 +882,8 @@
         }
         // Use < instead of <= as that gives us a little wiggle room in case a new job comes
         // along very shortly.
-        if (mService.mPendingJobs.size() + mRunningJobs.size() < mWorkTypeConfig.getMaxTotal()) {
+        if (mService.mPendingJobQueue.size() + mRunningJobs.size()
+                < mWorkTypeConfig.getMaxTotal()) {
             // Don't artificially limit a single package if we don't even have enough jobs to use
             // the maximum number of slots. We'll preempt the job later if we need the slot.
             return false;
@@ -936,8 +937,7 @@
                         jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
                         packageStats);
             }
-            final List<JobStatus> pendingJobs = mService.mPendingJobs;
-            if (pendingJobs.remove(jobStatus)) {
+            if (mService.mPendingJobQueue.remove(jobStatus)) {
                 mService.mJobPackageTracker.noteNonpending(jobStatus);
             }
         } finally {
@@ -962,11 +962,11 @@
             }
         }
 
-        final List<JobStatus> pendingJobs = mService.mPendingJobs;
+        final PendingJobQueue pendingJobQueue = mService.mPendingJobQueue;
         if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) {
             updateCounterConfigLocked();
             // Preemption case needs special care.
-            updateNonRunningPrioritiesLocked(pendingJobs, false);
+            updateNonRunningPrioritiesLocked(pendingJobQueue, false);
 
             JobStatus highestBiasJob = null;
             int highBiasWorkType = workType;
@@ -974,9 +974,10 @@
             JobStatus backupJob = null;
             int backupWorkType = WORK_TYPE_NONE;
             int backupAllWorkTypes = WORK_TYPE_NONE;
-            for (int i = 0; i < pendingJobs.size(); i++) {
-                final JobStatus nextPending = pendingJobs.get(i);
 
+            JobStatus nextPending;
+            pendingJobQueue.resetIterator();
+            while ((nextPending = pendingJobQueue.next()) != null) {
                 if (mRunningJobs.contains(nextPending)) {
                     continue;
                 }
@@ -1041,16 +1042,18 @@
                     startJobLocked(worker, backupJob, backupWorkType);
                 }
             }
-        } else if (pendingJobs.size() > 0) {
+        } else if (pendingJobQueue.size() > 0) {
             updateCounterConfigLocked();
-            updateNonRunningPrioritiesLocked(pendingJobs, false);
+            updateNonRunningPrioritiesLocked(pendingJobQueue, false);
 
             // This slot is now free and we have pending jobs. Start the highest bias job we find.
             JobStatus highestBiasJob = null;
             int highBiasWorkType = workType;
             int highBiasAllWorkTypes = workType;
-            for (int i = 0; i < pendingJobs.size(); i++) {
-                final JobStatus nextPending = pendingJobs.get(i);
+
+            JobStatus nextPending;
+            pendingJobQueue.resetIterator();
+            while ((nextPending = pendingJobQueue.next()) != null) {
 
                 if (mRunningJobs.contains(nextPending)) {
                     continue;
@@ -1127,8 +1130,8 @@
             return "too many jobs running";
         }
 
-        final List<JobStatus> pendingJobs = mService.mPendingJobs;
-        final int numPending = pendingJobs.size();
+        final PendingJobQueue pendingJobQueue = mService.mPendingJobQueue;
+        final int numPending = pendingJobQueue.size();
         if (numPending == 0) {
             // All quiet. We can let this job run to completion.
             return null;
@@ -1163,8 +1166,9 @@
 
         // Harder check. We need to see if a different work type can replace this job.
         int remainingWorkTypes = ALL_WORK_TYPES;
-        for (int i = 0; i < numPending; ++i) {
-            final JobStatus pending = pendingJobs.get(i);
+        JobStatus pending;
+        pendingJobQueue.resetIterator();
+        while ((pending = pendingJobQueue.next()) != null) {
             final int workTypes = getJobWorkTypes(pending);
             if ((workTypes & remainingWorkTypes) > 0
                     && mWorkCountTracker.canJobStart(workTypes, workType) != WORK_TYPE_NONE) {
@@ -1201,9 +1205,10 @@
     @GuardedBy("mLock")
     private String printPendingQueueLocked() {
         StringBuilder s = new StringBuilder("Pending queue: ");
-        Iterator<JobStatus> it = mService.mPendingJobs.iterator();
-        while (it.hasNext()) {
-            JobStatus js = it.next();
+        PendingJobQueue pendingJobQueue = mService.mPendingJobQueue;
+        JobStatus js;
+        pendingJobQueue.resetIterator();
+        while ((js = pendingJobQueue.next()) != null) {
             s.append("(")
                     .append(js.getJob().getId())
                     .append(", ")
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 3d74bc9..2028be7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -21,7 +21,6 @@
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
-import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -81,7 +80,6 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
-import android.util.SparseLongArray;
 import android.util.SparseSetArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
@@ -288,7 +286,7 @@
      * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
      * when ready to execute them.
      */
-    final ArrayList<JobStatus> mPendingJobs = new ArrayList<>();
+    final PendingJobQueue mPendingJobQueue = new PendingJobQueue();
 
     int[] mStartedUsers = EmptyArray.INT;
 
@@ -828,189 +826,6 @@
     final Constants mConstants;
     final ConstantsObserver mConstantsObserver;
 
-    @VisibleForTesting
-    class PendingJobComparator implements Comparator<JobStatus> {
-        private static final int EJ_PRIORITY_MODIFIER = 10;
-
-        /** Cache of the earliest non-PRIORITY_MAX enqueue time found per UID. */
-        private final SparseLongArray mEarliestNonMaxEnqueueTimeCache = new SparseLongArray();
-        /**
-         * Cache of the last enqueue time of each priority for each UID. The SparseArray is keyed
-         * by UID and the SparseLongArray is keyed by the priority.
-         */
-        private final SparseArray<SparseLongArray> mLastPriorityEnqueueTimeCache =
-                new SparseArray<>();
-        /**
-         * The earliest enqueue time each UID's priority's jobs should use. The SparseArray is keyed
-         * by UID and the SparseLongArray is keyed by the value returned from
-         * {@link #getPriorityIndex(int, boolean)}.
-         */
-        private final SparseArray<SparseLongArray> mEarliestAllowedEnqueueTimes =
-                new SparseArray<>();
-
-        private int getPriorityIndex(int priority, boolean isEJ) {
-            // We need to separate HIGH priority EJs from HIGH priority regular jobs.
-            if (isEJ) {
-                return priority * EJ_PRIORITY_MODIFIER;
-            }
-            return priority;
-        }
-
-        /**
-         * Refresh sorting determinants based on the current state of {@link #mPendingJobs}.
-         */
-        @GuardedBy("mLock")
-        @VisibleForTesting
-        void refreshLocked() {
-            mEarliestNonMaxEnqueueTimeCache.clear();
-            for (int i = 0; i < mPendingJobs.size(); ++i) {
-                final JobStatus job = mPendingJobs.get(i);
-                final int uid = job.getSourceUid();
-                if (job.getEffectivePriority() < JobInfo.PRIORITY_MAX) {
-                    final long earliestEnqueueTime =
-                            mEarliestNonMaxEnqueueTimeCache.get(uid, Long.MAX_VALUE);
-                    mEarliestNonMaxEnqueueTimeCache.put(uid,
-                            Math.min(earliestEnqueueTime, job.enqueueTime));
-                }
-
-                final int pIdx =
-                        getPriorityIndex(job.getEffectivePriority(), job.isRequestedExpeditedJob());
-                SparseLongArray lastPriorityEnqueueTime = mLastPriorityEnqueueTimeCache.get(uid);
-                if (lastPriorityEnqueueTime == null) {
-                    lastPriorityEnqueueTime = new SparseLongArray();
-                    mLastPriorityEnqueueTimeCache.put(uid, lastPriorityEnqueueTime);
-                }
-                lastPriorityEnqueueTime.put(pIdx,
-                        Math.max(job.enqueueTime, lastPriorityEnqueueTime.get(pIdx, 0)));
-            }
-
-            // Move lower priority jobs behind higher priority jobs (instead of moving higher
-            // priority jobs ahead of lower priority jobs), except for EJs.
-            for (int i = 0; i < mLastPriorityEnqueueTimeCache.size(); ++i) {
-                final int uid = mLastPriorityEnqueueTimeCache.keyAt(i);
-                SparseLongArray lastEnqueueTimes = mLastPriorityEnqueueTimeCache.valueAt(i);
-                SparseLongArray earliestAllowedEnqueueTimes = new SparseLongArray();
-                mEarliestAllowedEnqueueTimes.put(uid, earliestAllowedEnqueueTimes);
-                long earliestAllowedEnqueueTime = mEarliestNonMaxEnqueueTimeCache.get(uid,
-                        lastEnqueueTimes.get(getPriorityIndex(JobInfo.PRIORITY_MAX, true), -1));
-                earliestAllowedEnqueueTimes.put(getPriorityIndex(JobInfo.PRIORITY_MAX, true),
-                        earliestAllowedEnqueueTime);
-                earliestAllowedEnqueueTime = 1
-                        + Math.max(earliestAllowedEnqueueTime,
-                        lastEnqueueTimes.get(getPriorityIndex(JobInfo.PRIORITY_HIGH, true), -1));
-                earliestAllowedEnqueueTimes.put(getPriorityIndex(JobInfo.PRIORITY_HIGH, true),
-                        earliestAllowedEnqueueTime);
-                earliestAllowedEnqueueTime++;
-                for (int p = JobInfo.PRIORITY_HIGH; p >= JobInfo.PRIORITY_MIN; --p) {
-                    final int pIdx = getPriorityIndex(p, false);
-                    earliestAllowedEnqueueTimes.put(pIdx, earliestAllowedEnqueueTime);
-                    final long lastEnqueueTime = lastEnqueueTimes.get(pIdx, -1);
-                    if (lastEnqueueTime != -1) {
-                        // Add additional millisecond for the next priority to ensure sorting is
-                        // stable/accurate when comparing to other apps.
-                        earliestAllowedEnqueueTime = 1
-                                + Math.max(earliestAllowedEnqueueTime, lastEnqueueTime);
-                    }
-                }
-            }
-
-            // Clear intermediate state that we don't need to reduce steady state memory usage.
-            mLastPriorityEnqueueTimeCache.clear();
-        }
-
-        @ElapsedRealtimeLong
-        private long getEffectiveEnqueueTime(@NonNull JobStatus job) {
-            // Move lower priority jobs behind higher priority jobs (instead of moving higher
-            // priority jobs ahead of lower priority jobs), except for MAX EJs.
-            final int uid = job.getSourceUid();
-            if (job.isRequestedExpeditedJob()
-                    && job.getEffectivePriority() == JobInfo.PRIORITY_MAX) {
-                return Math.min(job.enqueueTime,
-                        mEarliestNonMaxEnqueueTimeCache.get(uid, Long.MAX_VALUE));
-            }
-            final int priorityIdx =
-                    getPriorityIndex(job.getEffectivePriority(), job.isRequestedExpeditedJob());
-            final SparseLongArray earliestAllowedEnqueueTimes =
-                    mEarliestAllowedEnqueueTimes.get(uid);
-            if (earliestAllowedEnqueueTimes == null) {
-                // We're probably trying to insert directly without refreshing the internal arrays.
-                // Since we haven't seen this UID before, we can just use the job's enqueue time.
-                return job.enqueueTime;
-            }
-            return Math.max(job.enqueueTime, earliestAllowedEnqueueTimes.get(priorityIdx));
-        }
-
-        @Override
-        public int compare(JobStatus o1, JobStatus o2) {
-            if (o1 == o2) {
-                return 0;
-            }
-            // Jobs with an override state set (via adb) should be put first as tests/developers
-            // expect the jobs to run immediately.
-            if (o1.overrideState != o2.overrideState) {
-                // Higher override state (OVERRIDE_FULL) should be before lower state
-                // (OVERRIDE_SOFT)
-                return o2.overrideState - o1.overrideState;
-            }
-            final boolean o1EJ = o1.isRequestedExpeditedJob();
-            final boolean o2EJ = o2.isRequestedExpeditedJob();
-            if (o1.getSourceUid() == o2.getSourceUid()) {
-                if (o1EJ != o2EJ) {
-                    // Attempt to run requested expedited jobs ahead of regular jobs, regardless of
-                    // expedited job quota.
-                    return o1EJ ? -1 : 1;
-                }
-                if (o1.getEffectivePriority() != o2.getEffectivePriority()) {
-                    // Use the priority set by an app for intra-app job ordering. Higher
-                    // priority should be before lower priority.
-                    return o2.getEffectivePriority() - o1.getEffectivePriority();
-                }
-            } else {
-                // TODO: see if we can simplify this using explicit topological sorting
-                // Since we order jobs within a UID by the job's priority, in order to satisfy the
-                // transitivity constraint of the comparator, we must ensure consistent/appropriate
-                // ordering between apps as well. That is, if a job is ordered before or behind
-                // another job because of its priority, that ordering must translate to the
-                // relative ordering against other jobs.
-                // The effective ordering implementation here is to use HIGH priority EJs as a
-                // pivot point. MAX priority EJs are moved *ahead* of HIGH priority EJs. All
-                // regular jobs are moved *behind* HIGH priority EJs. The intention for moving jobs
-                // "behind" the EJs instead of moving all high priority jobs before lower priority
-                // jobs is to reduce any potential abuse (or just unfortunate execution) cases where
-                // there are early low priority jobs that don't get to run because so many of the
-                // app's high priority jobs are pushed before low priority job. This may still
-                // happen because of the job ordering mechanism, but moving jobs back prevents
-                // one app's jobs from always being at the front (due to the early scheduled low
-                // priority job and our base case of sorting by enqueue time).
-
-                final long o1EffectiveEnqueueTime = getEffectiveEnqueueTime(o1);
-                final long o2EffectiveEnqueueTime = getEffectiveEnqueueTime(o2);
-
-                if (o1EffectiveEnqueueTime < o2EffectiveEnqueueTime) {
-                    return -1;
-                } else if (o1EffectiveEnqueueTime > o2EffectiveEnqueueTime) {
-                    return 1;
-                }
-            }
-
-            if (o1.enqueueTime < o2.enqueueTime) {
-                return -1;
-            }
-            return o1.enqueueTime > o2.enqueueTime ? 1 : 0;
-        }
-    }
-
-    @VisibleForTesting
-    final PendingJobComparator mPendingJobComparator = new PendingJobComparator();
-
-    static <T> void addOrderedItem(ArrayList<T> array, T newItem, Comparator<T> comparator) {
-        int where = Collections.binarySearch(array, newItem, comparator);
-        if (where < 0) {
-            where = ~where;
-        }
-        array.add(where, newItem);
-    }
-
     /**
      * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
      * still clean up. On reinstall the package will have a new uid.
@@ -1434,7 +1249,7 @@
                 // This is a new job, we can just immediately put it on the pending
                 // list and try to run it.
                 mJobPackageTracker.notePending(jobStatus);
-                addOrderedItem(mPendingJobs, jobStatus, mPendingJobComparator);
+                mPendingJobQueue.add(jobStatus);
                 maybeRunPendingJobsLocked();
             } else {
                 evaluateControllerStatesLocked(jobStatus);
@@ -1563,7 +1378,7 @@
         cancelled.unprepareLocked();
         stopTrackingJobLocked(cancelled, incomingJob, true /* writeBack */);
         // Remove from pending queue.
-        if (mPendingJobs.remove(cancelled)) {
+        if (mPendingJobQueue.remove(cancelled)) {
             mJobPackageTracker.noteNonpending(cancelled);
         }
         // Cancel if running.
@@ -1658,8 +1473,8 @@
 
     void reportActiveLocked() {
         // active is true if pending queue contains jobs OR some job is running.
-        boolean active = mPendingJobs.size() > 0;
-        if (mPendingJobs.size() <= 0) {
+        boolean active = mPendingJobQueue.size() > 0;
+        if (!active) {
             final ArraySet<JobStatus> runningJobs = mConcurrencyManager.getRunningJobsLocked();
             for (int i = runningJobs.size() - 1; i >= 0; --i) {
                 final JobStatus job = runningJobs.valueAt(i);
@@ -1952,10 +1767,13 @@
         mJobPackageTracker.noteNonpending(job);
     }
 
-    void noteJobsNonpending(List<JobStatus> jobs) {
-        for (int i = jobs.size() - 1; i >= 0; i--) {
-            noteJobNonPending(jobs.get(i));
+    private void clearPendingJobQueue() {
+        JobStatus job;
+        mPendingJobQueue.resetIterator();
+        while ((job = mPendingJobQueue.next()) != null) {
+            noteJobNonPending(job);
         }
+        mPendingJobQueue.clear();
     }
 
     /**
@@ -2236,7 +2054,7 @@
                         if (js != null) {
                             if (isReadyToBeExecutedLocked(js)) {
                                 mJobPackageTracker.notePending(js);
-                                addOrderedItem(mPendingJobs, js, mPendingJobComparator);
+                                mPendingJobQueue.add(js);
                             }
                             mChangedJobList.remove(js);
                         } else {
@@ -2382,14 +2200,13 @@
         if (DEBUG) {
             Slog.d(TAG, "queuing all ready jobs for execution:");
         }
-        noteJobsNonpending(mPendingJobs);
-        mPendingJobs.clear();
+        clearPendingJobQueue();
         stopNonReadyActiveJobsLocked();
         mJobs.forEachJob(mReadyQueueFunctor);
         mReadyQueueFunctor.postProcessLocked();
 
         if (DEBUG) {
-            final int queuedJobs = mPendingJobs.size();
+            final int queuedJobs = mPendingJobQueue.size();
             if (queuedJobs == 0) {
                 Slog.d(TAG, "No jobs pending.");
             } else {
@@ -2416,11 +2233,7 @@
         @GuardedBy("mLock")
         private void postProcessLocked() {
             noteJobsPending(newReadyJobs);
-            mPendingJobs.addAll(newReadyJobs);
-            mPendingJobComparator.refreshLocked();
-            if (mPendingJobs.size() > 1) {
-                mPendingJobs.sort(mPendingJobComparator);
-            }
+            mPendingJobQueue.addAll(newReadyJobs);
 
             newReadyJobs.clear();
         }
@@ -2453,7 +2266,7 @@
                         mHandler.obtainMessage(MSG_STOP_JOB,
                                 JobParameters.STOP_REASON_BACKGROUND_RESTRICTION, 0, job)
                                 .sendToTarget();
-                    } else if (mPendingJobs.remove(job)) {
+                    } else if (mPendingJobQueue.remove(job)) {
                         noteJobNonPending(job);
                     }
                     return;
@@ -2530,7 +2343,7 @@
                     }
                     mConcurrencyManager.stopJobOnServiceContextLocked(job, job.getStopReason(),
                             internalStopReason, debugReason);
-                } else if (mPendingJobs.remove(job)) {
+                } else if (mPendingJobQueue.remove(job)) {
                     noteJobNonPending(job);
                 }
                 evaluateControllerStatesLocked(job);
@@ -2546,11 +2359,7 @@
                     Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Running jobs.");
                 }
                 noteJobsPending(runnableJobs);
-                mPendingJobs.addAll(runnableJobs);
-                mPendingJobComparator.refreshLocked();
-                if (mPendingJobs.size() > 1) {
-                    mPendingJobs.sort(mPendingJobComparator);
-                }
+                mPendingJobQueue.addAll(runnableJobs);
             } else {
                 if (DEBUG) {
                     Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Not running anything.");
@@ -2574,14 +2383,13 @@
     @GuardedBy("mLock")
     private void maybeQueueReadyJobsForExecutionLocked() {
         mHandler.removeMessages(MSG_CHECK_JOB);
-        // This method will evaluate all jobs, so we don't need to keep any messages for a suubset
+        // This method will evaluate all jobs, so we don't need to keep any messages for a subset
         // of jobs in the queue.
         mHandler.removeMessages(MSG_CHECK_CHANGED_JOB_LIST);
         mChangedJobList.clear();
         if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
 
-        noteJobsNonpending(mPendingJobs);
-        mPendingJobs.clear();
+        clearPendingJobQueue();
         stopNonReadyActiveJobsLocked();
         mJobs.forEachJob(mMaybeQueueFunctor);
         mMaybeQueueFunctor.postProcessLocked();
@@ -2682,7 +2490,7 @@
             return false;
         }
 
-        final boolean jobPending = mPendingJobs.contains(job);
+        final boolean jobPending = mPendingJobQueue.contains(job);
         final boolean jobActive = rejectActive && mConcurrencyManager.isJobRunningLocked(job);
 
         if (DEBUG) {
@@ -2802,7 +2610,7 @@
      */
     void maybeRunPendingJobsLocked() {
         if (DEBUG) {
-            Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
+            Slog.d(TAG, "pending queue: " + mPendingJobQueue.size() + " jobs.");
         }
         mConcurrencyManager.assignJobsToContextsLocked();
         reportActiveLocked();
@@ -3634,7 +3442,7 @@
                 }
 
                 boolean printed = false;
-                if (mPendingJobs.contains(js)) {
+                if (mPendingJobQueue.contains(js)) {
                     pw.print("pending");
                     printed = true;
                 }
@@ -3836,7 +3644,7 @@
                     pw.print(" !restricted=");
                     pw.print(!isRestricted);
                     pw.print(" !pending=");
-                    pw.print(!mPendingJobs.contains(job));
+                    pw.print(!mPendingJobQueue.contains(job));
                     pw.print(" !active=");
                     pw.print(!mConcurrencyManager.isJobRunningLocked(job));
                     pw.print(" !backingup=");
@@ -3929,8 +3737,11 @@
             boolean pendingPrinted = false;
             pw.println("Pending queue:");
             pw.increaseIndent();
-            for (int i = 0; i < mPendingJobs.size(); i++) {
-                JobStatus job = mPendingJobs.get(i);
+            JobStatus job;
+            int pendingIdx = 0;
+            mPendingJobQueue.resetIterator();
+            while ((job = mPendingJobQueue.next()) != null) {
+                pendingIdx++;
                 if (!predicate.test(job)) {
                     continue;
                 }
@@ -3938,7 +3749,7 @@
                     pendingPrinted = true;
                 }
 
-                pw.print("Pending #"); pw.print(i); pw.print(": ");
+                pw.print("Pending #"); pw.print(pendingIdx); pw.print(": ");
                 pw.println(job.toShortString());
 
                 pw.increaseIndent();
@@ -3969,7 +3780,7 @@
                 // Print most recent first
                 final int idx = (mLastCompletedJobIndex + NUM_COMPLETED_JOB_HISTORY - r)
                         % NUM_COMPLETED_JOB_HISTORY;
-                final JobStatus job = mLastCompletedJobs[idx];
+                job = mLastCompletedJobs[idx];
                 if (job != null) {
                     if (!predicate.test(job)) {
                         continue;
@@ -4062,7 +3873,7 @@
                             JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_RESTRICTED,
                             checkIfRestricted(job) != null);
                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_PENDING,
-                            mPendingJobs.contains(job));
+                            mPendingJobQueue.contains(job));
                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_CURRENTLY_ACTIVE,
                             mConcurrencyManager.isJobRunningLocked(job));
                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_UID_BACKING_UP,
@@ -4109,7 +3920,9 @@
             mJobPackageTracker.dumpHistory(proto, JobSchedulerServiceDumpProto.HISTORY,
                     filterAppId);
 
-            for (JobStatus job : mPendingJobs) {
+            JobStatus job;
+            mPendingJobQueue.resetIterator();
+            while ((job = mPendingJobQueue.next()) != null) {
                 final long pjToken = proto.start(JobSchedulerServiceDumpProto.PENDING_JOBS);
 
                 job.writeToShortProto(proto, PendingJob.INFO);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
index 993e178..f91472b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
@@ -21,6 +21,7 @@
 import android.util.Pools;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.job.controllers.JobStatus;
 
 import java.util.ArrayList;
@@ -54,11 +55,33 @@
                 } else if (t2 == AppJobQueue.NO_NEXT_TIMESTAMP) {
                     return -1;
                 }
+                final int o1 = ajq1.peekNextOverrideState();
+                final int o2 = ajq2.peekNextOverrideState();
+                if (o1 != o2) {
+                    // Higher override state (OVERRIDE_FULL) should be before lower state
+                    // (OVERRIDE_SOFT)
+                    return Integer.compare(o2, o1);
+                }
                 return Long.compare(t1, t2);
             });
 
     private int mSize = 0;
 
+    /**
+     * Whether to batch iteration so that we pull several of an app's jobs from the queue at the
+     * same time (resulting in some out of order pulls) instead of pulling purely based on the
+     * sort order. Batching it this way will mean we try to run several jobs of the same app at the
+     * same, resulting in fewer process restarts, and can allow the iteration runtime to amortize
+     * to O(A*J) instead of O(A*J*log(A)), where A = # apps and J = average # jobs per app.
+     */
+    private boolean mOptimizeIteration = true;
+
+    /**
+     * Number of jobs that have been pulled from the queue in succession. Used when
+     * {@link #mOptimizeIteration} is true to know when to switch to the next AppJobQueue.
+     */
+    private int mPullCount = 0;
+
     private boolean mNeedToResetIterators = false;
 
     void add(@NonNull JobStatus job) {
@@ -132,16 +155,44 @@
                 mOrderedQueues.offer(ajq);
             }
             mNeedToResetIterators = false;
+            // Reset the pull count when the front of the queue changes.
+            mPullCount = 0;
         } else if (mOrderedQueues.size() == 0) {
+            // Something significant changed, so the priority queue was cleared. Lazily regenerate
+            // the queue.
             for (int i = mCurrentQueues.size() - 1; i >= 0; --i) {
                 final AppJobQueue ajq = mCurrentQueues.valueAt(i);
                 mOrderedQueues.offer(ajq);
             }
+            // Reset the pull count when the front of the queue changes.
+            mPullCount = 0;
         }
-        final AppJobQueue earliestQueue = mOrderedQueues.poll();
+        final int numQueues = mOrderedQueues.size();
+        if (numQueues == 0) {
+            return null;
+        }
+
+        // Increase the pull limit at a slightly faster rate than log(A) increases (until A>=33).
+        // The pull limit increase is intended to balance fairness (one app can't starve out others)
+        // with efficiency (reducing process restarts).
+        // 1-4 apps --> pullLimit = 1, 5-8 apps --> pullLimit = 2, 9+ apps --> pullLimit = 3
+        final int pullLimit = mOptimizeIteration ? Math.min(3, ((numQueues - 1) >>> 2) + 1) : 1;
+
+        final AppJobQueue earliestQueue = mOrderedQueues.peek();
         if (earliestQueue != null) {
-            JobStatus job = earliestQueue.next();
-            mOrderedQueues.offer(earliestQueue);
+            final JobStatus job = earliestQueue.next();
+            // Change the front of the queue if we've pulled pullLimit jobs from the current head
+            // or the current head has no more jobs to provide.
+            if (++mPullCount >= pullLimit
+                    || earliestQueue.peekNextTimestamp() == AppJobQueue.NO_NEXT_TIMESTAMP) {
+                mOrderedQueues.poll();
+                if (earliestQueue.peekNextTimestamp() != AppJobQueue.NO_NEXT_TIMESTAMP) {
+                    // No need to put back in the queue if it has no more jobs to give.
+                    mOrderedQueues.offer(earliestQueue);
+                }
+                // Reset the pull count when the front of the queue changes.
+                mPullCount = 0;
+            }
             return job;
         }
         return null;
@@ -179,12 +230,18 @@
         mNeedToResetIterators = true;
     }
 
+    @VisibleForTesting
+    void setOptimizeIteration(boolean optimize) {
+        mOptimizeIteration = optimize;
+    }
+
     int size() {
         return mSize;
     }
 
     private static final class AppJobQueue {
         static final long NO_NEXT_TIMESTAMP = -1L;
+        static final int NO_NEXT_OVERRIDE_STATE = -1;
 
         private static class AdjustedJobStatus {
             public long adjustedEnqueueTime;
@@ -207,7 +264,7 @@
             if (job1.overrideState != job2.overrideState) {
                 // Higher override state (OVERRIDE_FULL) should be before lower state
                 // (OVERRIDE_SOFT)
-                return job2.overrideState - job1.overrideState;
+                return Integer.compare(job2.overrideState, job1.overrideState);
             }
 
             final boolean job1EJ = job1.isRequestedExpeditedJob();
@@ -223,18 +280,15 @@
             if (job1Priority != job2Priority) {
                 // Use the priority set by an app for intra-app job ordering. Higher
                 // priority should be before lower priority.
-                return job2Priority - job1Priority;
+                return Integer.compare(job2Priority, job1Priority);
             }
 
             if (job1.lastEvaluatedBias != job2.lastEvaluatedBias) {
                 // Higher bias should go first.
-                return job2.lastEvaluatedBias - job1.lastEvaluatedBias;
+                return Integer.compare(job2.lastEvaluatedBias, job1.lastEvaluatedBias);
             }
 
-            if (job1.enqueueTime < job2.enqueueTime) {
-                return -1;
-            }
-            return job1.enqueueTime > job2.enqueueTime ? 1 : 0;
+            return Long.compare(job1.enqueueTime, job2.enqueueTime);
         };
 
         private static final Pools.Pool<AdjustedJobStatus> mAdjustedJobStatusPool =
@@ -344,9 +398,14 @@
             if (mCurIndex >= mJobs.size()) {
                 return null;
             }
-            JobStatus next = mJobs.get(mCurIndex).job;
-            mCurIndex++;
-            return next;
+            return mJobs.get(mCurIndex++).job;
+        }
+
+        int peekNextOverrideState() {
+            if (mCurIndex >= mJobs.size()) {
+                return NO_NEXT_OVERRIDE_STATE;
+            }
+            return mJobs.get(mCurIndex).job.overrideState;
         }
 
         long peekNextTimestamp() {
diff --git a/core/api/current.txt b/core/api/current.txt
index 37022d0..7254135 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1755,7 +1755,7 @@
     field public static final int windowShowWallpaper = 16843410; // 0x1010292
     field public static final int windowSoftInputMode = 16843307; // 0x101022b
     field public static final int windowSplashScreenAnimatedIcon = 16844333; // 0x101062d
-    field public static final int windowSplashScreenAnimationDuration = 16844334; // 0x101062e
+    field @Deprecated public static final int windowSplashScreenAnimationDuration = 16844334; // 0x101062e
     field public static final int windowSplashScreenBackground = 16844332; // 0x101062c
     field public static final int windowSplashScreenBehavior;
     field public static final int windowSplashScreenBrandingImage = 16844335; // 0x101062f
@@ -39631,22 +39631,34 @@
 
   public final class RecognitionSupport implements android.os.Parcelable {
     method public int describeContents();
-    method @NonNull public java.util.List<java.lang.String> getInstalledLanguages();
-    method @NonNull public java.util.List<java.lang.String> getPendingLanguages();
-    method @NonNull public java.util.List<java.lang.String> getSupportedLanguages();
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getInstalledLanguages();
+    method @NonNull public java.util.List<java.lang.String> getInstalledOnDeviceLanguages();
+    method @NonNull public java.util.List<java.lang.String> getOnlineLanguages();
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getPendingLanguages();
+    method @NonNull public java.util.List<java.lang.String> getPendingOnDeviceLanguages();
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getSupportedLanguages();
+    method @NonNull public java.util.List<java.lang.String> getSupportedOnDeviceLanguages();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.speech.RecognitionSupport> CREATOR;
   }
 
   public static final class RecognitionSupport.Builder {
     ctor public RecognitionSupport.Builder();
-    method @NonNull public android.speech.RecognitionSupport.Builder addInstalledLanguage(@NonNull String);
-    method @NonNull public android.speech.RecognitionSupport.Builder addPendingLanguage(@NonNull String);
-    method @NonNull public android.speech.RecognitionSupport.Builder addSupportedLanguage(@NonNull String);
+    method @Deprecated @NonNull public android.speech.RecognitionSupport.Builder addInstalledLanguage(@NonNull String);
+    method @NonNull public android.speech.RecognitionSupport.Builder addInstalledOnDeviceLanguage(@NonNull String);
+    method @NonNull public android.speech.RecognitionSupport.Builder addOnlineLanguage(@NonNull String);
+    method @Deprecated @NonNull public android.speech.RecognitionSupport.Builder addPendingLanguage(@NonNull String);
+    method @NonNull public android.speech.RecognitionSupport.Builder addPendingOnDeviceLanguage(@NonNull String);
+    method @Deprecated @NonNull public android.speech.RecognitionSupport.Builder addSupportedLanguage(@NonNull String);
+    method @NonNull public android.speech.RecognitionSupport.Builder addSupportedOnDeviceLanguage(@NonNull String);
     method @NonNull public android.speech.RecognitionSupport build();
-    method @NonNull public android.speech.RecognitionSupport.Builder setInstalledLanguages(@NonNull java.util.List<java.lang.String>);
-    method @NonNull public android.speech.RecognitionSupport.Builder setPendingLanguages(@NonNull java.util.List<java.lang.String>);
-    method @NonNull public android.speech.RecognitionSupport.Builder setSupportedLanguages(@NonNull java.util.List<java.lang.String>);
+    method @Deprecated @NonNull public android.speech.RecognitionSupport.Builder setInstalledLanguages(@NonNull java.util.List<java.lang.String>);
+    method @NonNull public android.speech.RecognitionSupport.Builder setInstalledOnDeviceLanguages(@NonNull java.util.List<java.lang.String>);
+    method @NonNull public android.speech.RecognitionSupport.Builder setOnlineLanguages(@NonNull java.util.List<java.lang.String>);
+    method @Deprecated @NonNull public android.speech.RecognitionSupport.Builder setPendingLanguages(@NonNull java.util.List<java.lang.String>);
+    method @NonNull public android.speech.RecognitionSupport.Builder setPendingOnDeviceLanguages(@NonNull java.util.List<java.lang.String>);
+    method @Deprecated @NonNull public android.speech.RecognitionSupport.Builder setSupportedLanguages(@NonNull java.util.List<java.lang.String>);
+    method @NonNull public android.speech.RecognitionSupport.Builder setSupportedOnDeviceLanguages(@NonNull java.util.List<java.lang.String>);
   }
 
   public interface RecognitionSupportCallback {
@@ -44820,7 +44832,7 @@
   public class BoringLayout extends android.text.Layout implements android.text.TextUtils.EllipsizeCallback {
     ctor public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean);
     ctor public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int);
-    ctor public BoringLayout(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, float, float, @NonNull android.text.BoringLayout.Metrics, boolean, @NonNull android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean);
+    ctor public BoringLayout(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, float, float, @NonNull android.text.BoringLayout.Metrics, boolean, @Nullable android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean);
     method public void ellipsized(int, int);
     method public int getBottomPadding();
     method public int getEllipsisCount(int);
@@ -44838,9 +44850,9 @@
     method @Nullable public static android.text.BoringLayout.Metrics isBoring(@NonNull CharSequence, @NonNull android.text.TextPaint, @NonNull android.text.TextDirectionHeuristic, boolean, @Nullable android.text.BoringLayout.Metrics);
     method public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean);
     method public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int);
-    method @NonNull public static android.text.BoringLayout make(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @NonNull android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean);
+    method @NonNull public static android.text.BoringLayout make(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @Nullable android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean);
     method public android.text.BoringLayout replaceOrMake(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean);
-    method @NonNull public android.text.BoringLayout replaceOrMake(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @NonNull android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean);
+    method @NonNull public android.text.BoringLayout replaceOrMake(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @Nullable android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean);
     method public android.text.BoringLayout replaceOrMake(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int);
   }
 
@@ -52262,7 +52274,7 @@
     method protected android.view.animation.Animation clone() throws java.lang.CloneNotSupportedException;
     method public long computeDurationHint();
     method protected void ensureInterpolator();
-    method @Deprecated @ColorInt public int getBackgroundColor();
+    method @ColorInt public int getBackgroundColor();
     method @Deprecated public boolean getDetachWallpaper();
     method public long getDuration();
     method public boolean getFillAfter();
@@ -52287,7 +52299,7 @@
     method public void restrictDuration(long);
     method public void scaleCurrentDuration(float);
     method public void setAnimationListener(android.view.animation.Animation.AnimationListener);
-    method @Deprecated public void setBackgroundColor(@ColorInt int);
+    method public void setBackgroundColor(@ColorInt int);
     method @Deprecated public void setDetachWallpaper(boolean);
     method public void setDuration(long);
     method public void setFillAfter(boolean);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 61309e3..f3867d8 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -439,6 +439,7 @@
     field public static final int config_systemContacts = 17039403; // 0x104002b
     field public static final int config_systemGallery = 17039399; // 0x1040027
     field public static final int config_systemNotificationIntelligence = 17039413; // 0x1040035
+    field public static final int config_systemSettingsIntelligence;
     field public static final int config_systemShell = 17039402; // 0x104002a
     field public static final int config_systemSpeechRecognizer = 17039406; // 0x104002e
     field public static final int config_systemSupervision;
@@ -5247,7 +5248,7 @@
 
   public static final class SoundTrigger.KeyphraseRecognitionExtra implements android.os.Parcelable {
     method public int describeContents();
-    method public int getCoarseConfidenceLevel();
+    method @IntRange(from=0, to=100) public int getCoarseConfidenceLevel();
     method public int getKeyphraseId();
     method public int getRecognitionModes();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -9804,8 +9805,8 @@
     method public boolean isRestrictedProfile();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public boolean isRestrictedProfile(@NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isSameProfileGroup(@NonNull android.os.UserHandle, @NonNull android.os.UserHandle);
-    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public boolean isUserNameSet();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUserOfType(@NonNull String);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public boolean isUserNameSet();
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isUserOfType(@NonNull String);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isUserUnlockingOrUnlocked(@NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public int removeUserWhenPossible(@NonNull android.os.UserHandle, boolean);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index e8c903f..5b05b45 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4475,23 +4475,6 @@
     }
 
     /**
-     * Logs out current current foreground user by switching to the system user and stopping the
-     * user being switched from.
-     * @hide
-     */
-    public static void logoutCurrentUser() {
-        int currentUser = ActivityManager.getCurrentUser();
-        if (currentUser != UserHandle.USER_SYSTEM) {
-            try {
-                getService().switchUser(UserHandle.USER_SYSTEM);
-                getService().stopUser(currentUser, /* force= */ false, null);
-            } catch (RemoteException e) {
-                e.rethrowFromSystemServer();
-            }
-        }
-    }
-
-    /**
      * Stops the given {@code userId}.
      *
      * @hide
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 0178fa1..0d81036 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1162,6 +1162,7 @@
             case ANIM_CUSTOM:
                 mCustomEnterResId = opts.getInt(KEY_ANIM_ENTER_RES_ID, 0);
                 mCustomExitResId = opts.getInt(KEY_ANIM_EXIT_RES_ID, 0);
+                mCustomBackgroundColor = opts.getInt(KEY_ANIM_BACKGROUND_COLOR, 0);
                 mAnimationStartedListener = IRemoteCallback.Stub.asInterface(
                         opts.getBinder(KEY_ANIM_START_LISTENER));
                 break;
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index c363909..a9d665c8 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -29,6 +29,7 @@
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -1630,7 +1631,8 @@
          * @hide
          */
         @UnsupportedAppUsage
-        public KeyphraseRecognitionExtra(int id, int recognitionModes, int coarseConfidenceLevel,
+        public KeyphraseRecognitionExtra(int id, int recognitionModes,
+                @IntRange(from = 0, to = 100) int coarseConfidenceLevel,
                 @Nullable ConfidenceLevel[] confidenceLevels) {
             this.id = id;
             this.recognitionModes = recognitionModes;
@@ -1660,6 +1662,7 @@
          *
          * <p>The confidence level is expressed in percent (0% -100%).
          */
+        @IntRange(from = 0, to = 100)
         public int getCoarseConfidenceLevel() {
             return coarseConfidenceLevel;
         }
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 043e688..a4f6004 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -107,9 +107,6 @@
     public static final long TRACE_TAG_RRO = 1L << 26;
     /** @hide */
     public static final long TRACE_TAG_THERMAL = 1L << 27;
-    /** @hide */
-
-    public static final long TRACE_TAG_APEX_MANAGER = 1L << 18;
 
     private static final long TRACE_TAG_NOT_READY = 1L << 63;
     private static final int MAX_SECTION_NAME_LEN = 127;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 608c424..c4cb319 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2186,9 +2186,17 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.GET_ACCOUNTS_PRIVILEGED})
-    @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.CREATE_USERS,
+            android.Manifest.permission.QUERY_USERS,
+            android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED})
+    @UserHandleAware(
+            enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU,
+            requiresAnyOfPermissionsIfNotCaller = {
+                    android.Manifest.permission.MANAGE_USERS,
+                    android.Manifest.permission.CREATE_USERS,
+                    android.Manifest.permission.QUERY_USERS})
     public boolean isUserNameSet() {
         try {
             return mService.isUserNameSet(getContextUserIfAppropriate());
@@ -2292,8 +2300,11 @@
      * @hide
      */
     @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.CREATE_USERS,
+            android.Manifest.permission.QUERY_USERS})
     @UserHandleAware
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public boolean isUserOfType(@NonNull String userType) {
         try {
             return mService.isUserOfType(mUserId, userType);
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 642c618..5bed32c 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -167,11 +167,27 @@
     public static final int FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF = 0x2;
 
     /**
+     * Flag requesting vibration effect to be played with fresh user settings values.
+     *
+     * <p>This flag is not protected by any permission, but vibrations that use it require an extra
+     * query of user vibration intensity settings, ringer mode and other controls that affect the
+     * vibration effect playback, which can increase the latency for the overall request.
+     *
+     * <p>This is intended to be used on scenarios where the user settings might have changed
+     * recently, and needs to be applied to this vibration, like settings controllers that preview
+     * newly set intensities to the user.
+     *
+     * @hide
+     */
+    public static final int FLAG_INVALIDATE_SETTINGS_CACHE = 0x3;
+
+    /**
      * All flags supported by vibrator service, update it when adding new flag.
      * @hide
      */
     public static final int FLAG_ALL_SUPPORTED =
-            FLAG_BYPASS_INTERRUPTION_POLICY | FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
+            FLAG_BYPASS_INTERRUPTION_POLICY | FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF
+                    | FLAG_INVALIDATE_SETTINGS_CACHE;
 
     /** Creates a new {@link VibrationAttributes} instance with given usage. */
     public static @NonNull VibrationAttributes createForUsage(@Usage int usage) {
@@ -446,8 +462,10 @@
         }
 
         /**
-         * Set flags
-         * @param flags combination of flags to be set.
+         * Sets only the flags specified in the bitmask, leaving the other supported flag values
+         * unchanged in the builder.
+         *
+         * @param flags Combination of flags to be set.
          * @param mask Bit range that should be changed.
          * @return the same Builder instance.
          */
@@ -456,5 +474,18 @@
             mFlags = (mFlags & ~mask) | (flags & mask);
             return this;
         }
+
+        /**
+         * Set all supported flags with given combination of flags, overriding any previous values
+         * set to this builder.
+         *
+         * @param flags combination of flags to be set.
+         * @return the same Builder instance.
+         *
+         * @hide
+         */
+        public @NonNull Builder setFlags(@Flag int flags) {
+            return setFlags(flags, FLAG_ALL_SUPPORTED);
+        }
     }
 }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 9971cbc..646a709 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -301,28 +301,7 @@
 
     /** @hide The volume is not encrypted. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int ENCRYPTION_STATE_NONE =
-            IVold.ENCRYPTION_STATE_NONE;
-
-    /** @hide The volume has been encrypted succesfully. */
-    public static final int ENCRYPTION_STATE_OK =
-            IVold.ENCRYPTION_STATE_OK;
-
-    /** @hide The volume is in a bad state. */
-    public static final int ENCRYPTION_STATE_ERROR_UNKNOWN =
-            IVold.ENCRYPTION_STATE_ERROR_UNKNOWN;
-
-    /** @hide Encryption is incomplete */
-    public static final int ENCRYPTION_STATE_ERROR_INCOMPLETE =
-            IVold.ENCRYPTION_STATE_ERROR_INCOMPLETE;
-
-    /** @hide Encryption is incomplete and irrecoverable */
-    public static final int ENCRYPTION_STATE_ERROR_INCONSISTENT =
-            IVold.ENCRYPTION_STATE_ERROR_INCONSISTENT;
-
-    /** @hide Underlying data is corrupt */
-    public static final int ENCRYPTION_STATE_ERROR_CORRUPT =
-            IVold.ENCRYPTION_STATE_ERROR_CORRUPT;
+    public static final int ENCRYPTION_STATE_NONE = 1;
 
     private static volatile IStorageManager sStorageManager = null;
 
@@ -3033,15 +3012,10 @@
     @GuardedBy("mFuseAppLoopLock")
     private @Nullable FuseAppLoop mFuseAppLoop = null;
 
-    /// Consts to match the password types in cryptfs.h
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int CRYPT_TYPE_PASSWORD = IVold.PASSWORD_TYPE_PASSWORD;
+    public static final int CRYPT_TYPE_PASSWORD = 0;
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int CRYPT_TYPE_DEFAULT = IVold.PASSWORD_TYPE_DEFAULT;
-    /** @hide */
-    public static final int CRYPT_TYPE_PATTERN = IVold.PASSWORD_TYPE_PATTERN;
-    /** @hide */
-    public static final int CRYPT_TYPE_PIN = IVold.PASSWORD_TYPE_PIN;
+    public static final int CRYPT_TYPE_DEFAULT = 1;
 }
diff --git a/core/java/android/speech/RecognitionSupport.java b/core/java/android/speech/RecognitionSupport.java
index 43c8e1b..60c3b63 100644
--- a/core/java/android/speech/RecognitionSupport.java
+++ b/core/java/android/speech/RecognitionSupport.java
@@ -17,6 +17,8 @@
 package android.speech;
 
 import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
 import android.os.Parcelable;
 
 import com.android.internal.util.DataClass;
@@ -34,19 +36,63 @@
 )
 public final class RecognitionSupport implements Parcelable {
 
+    /**
+     * Support for this request is ready for use on this device for the returned languages.
+     *
+     * @deprecated See {@link #getInstalledOnDeviceLanguages()}.
+     */
+    @NonNull
+    @Deprecated
+    @DataClass.PluralOf("installedLanguage")
+    private List<String> mInstalledLanguages = List.of();
+
+    /**
+     * Support for this request is scheduled for download for the returned languages.
+     *
+     * @deprecated See {@link #getPendingOnDeviceLanguages()}.
+     */
+    @NonNull
+    @Deprecated
+    @DataClass.PluralOf("pendingLanguage")
+    private List<String> mPendingLanguages = List.of();
+
+    /**
+     * These languages are supported but need to be downloaded before use. See {@link
+     * SpeechRecognizer#triggerModelDownload(Intent)}.
+     *
+     * @deprecated See {@link #getSupportedOnDeviceLanguages()}.
+     */
+    @NonNull
+    @Deprecated
+    @DataClass.PluralOf("supportedLanguage")
+    private List<String> mSupportedLanguages = List.of();
+
     /** Support for this request is ready for use on this device for the returned languages. */
     @NonNull
-    @DataClass.PluralOf("installedLanguage")
-    private List<String> mInstalledLanguages = null;
+    @DataClass.PluralOf("installedOnDeviceLanguage")
+    private List<String> mInstalledOnDeviceLanguages = List.of();
 
     /** Support for this request is scheduled for download for the returned languages. */
-    @DataClass.PluralOf("pendingLanguage")
-    @NonNull private List<String> mPendingLanguages = null;
-
-    /** These languages are supported but need to be downloaded before use. */
     @NonNull
-    @DataClass.PluralOf("supportedLanguage")
-    private List<String> mSupportedLanguages = null;
+    @DataClass.PluralOf("pendingOnDeviceLanguage")
+    private List<String> mPendingOnDeviceLanguages = List.of();
+
+    /**
+     * These languages are supported but need to be downloaded before use. See {@link
+     * SpeechRecognizer#triggerModelDownload(Intent)}.
+     */
+    @NonNull
+    @DataClass.PluralOf("supportedOnDeviceLanguage")
+    private List<String> mSupportedOnDeviceLanguages = List.of();
+
+    /**
+     * Support for this request is available via a remote implementation. {@link SpeechRecognizer}
+     * created via {@link SpeechRecognizer#createOnDeviceSpeechRecognizer(Context)} are expected to
+     * return an empty list.
+     */
+    @NonNull
+    @DataClass.PluralOf("onlineLanguage")
+    private List<String> mOnlineLanguages = List.of();
 
 
 
@@ -65,44 +111,108 @@
 
     @DataClass.Generated.Member
     /* package-private */ RecognitionSupport(
-            @NonNull List<String> installedLanguages,
-            @NonNull List<String> pendingLanguages,
-            @NonNull List<String> supportedLanguages) {
+            @NonNull @Deprecated List<String> installedLanguages,
+            @NonNull @Deprecated List<String> pendingLanguages,
+            @NonNull @Deprecated List<String> supportedLanguages,
+            @NonNull List<String> installedOnDeviceLanguages,
+            @NonNull List<String> pendingOnDeviceLanguages,
+            @NonNull List<String> supportedOnDeviceLanguages,
+            @NonNull List<String> onlineLanguages) {
         this.mInstalledLanguages = installedLanguages;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mInstalledLanguages);
+        com.android.internal.util.AnnotationValidations.validate(
+                Deprecated.class, null, mInstalledLanguages);
         this.mPendingLanguages = pendingLanguages;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mPendingLanguages);
+        com.android.internal.util.AnnotationValidations.validate(
+                Deprecated.class, null, mPendingLanguages);
         this.mSupportedLanguages = supportedLanguages;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mSupportedLanguages);
+        com.android.internal.util.AnnotationValidations.validate(
+                Deprecated.class, null, mSupportedLanguages);
+        this.mInstalledOnDeviceLanguages = installedOnDeviceLanguages;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mInstalledOnDeviceLanguages);
+        this.mPendingOnDeviceLanguages = pendingOnDeviceLanguages;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mPendingOnDeviceLanguages);
+        this.mSupportedOnDeviceLanguages = supportedOnDeviceLanguages;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSupportedOnDeviceLanguages);
+        this.mOnlineLanguages = onlineLanguages;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mOnlineLanguages);
 
         // onConstructed(); // You can define this method to get a callback
     }
 
     /**
      * Support for this request is ready for use on this device for the returned languages.
+     *
+     * @deprecated See {@link #getInstalledOnDeviceLanguages()}.
      */
     @DataClass.Generated.Member
-    public @NonNull List<String> getInstalledLanguages() {
+    @NonNull @Deprecated public List<String> getInstalledLanguages() {
         return mInstalledLanguages;
     }
 
     /**
      * Support for this request is scheduled for download for the returned languages.
+     *
+     * @deprecated See {@link #getPendingOnDeviceLanguages()}.
+     */
+    @DataClass.Generated.Member
+    @NonNull @Deprecated public List<String> getPendingLanguages() {
+        return mPendingLanguages;
+    }
+
+    /**
+     * These languages are supported but need to be downloaded before use. See {@link
+     * SpeechRecognizer#triggerModelDownload(Intent)}.
+     *
+     * @deprecated See {@link #getSupportedOnDeviceLanguages()}.
+     */
+    @DataClass.Generated.Member
+    @NonNull @Deprecated public List<String> getSupportedLanguages() {
+        return mSupportedLanguages;
+    }
+
+    /**
+     * Support for this request is ready for use on this device for the returned languages.
+     */
+    @DataClass.Generated.Member
+    public @NonNull List<String> getInstalledOnDeviceLanguages() {
+        return mInstalledOnDeviceLanguages;
+    }
+
+    /**
+     * Support for this request is scheduled for download for the returned languages.
      */
     @DataClass.Generated.Member
-    public @NonNull List<String> getPendingLanguages() {
-        return mPendingLanguages;
+    public @NonNull List<String> getPendingOnDeviceLanguages() {
+        return mPendingOnDeviceLanguages;
     }
 
     /**
-     * These languages are supported but need to be downloaded before use.
+     * These languages are supported but need to be downloaded before use. See {@link
+     * SpeechRecognizer#triggerModelDownload(Intent)}.
      */
     @DataClass.Generated.Member
-    public @NonNull List<String> getSupportedLanguages() {
-        return mSupportedLanguages;
+    public @NonNull List<String> getSupportedOnDeviceLanguages() {
+        return mSupportedOnDeviceLanguages;
+    }
+
+    /**
+     * Support for this request is available via a remote implementation. {@link SpeechRecognizer}
+     * created via {@link SpeechRecognizer#createOnDeviceSpeechRecognizer(Context)} are expected to
+     * return an empty list.
+     */
+    @DataClass.Generated.Member
+    public @NonNull List<String> getOnlineLanguages() {
+        return mOnlineLanguages;
     }
 
     @Override
@@ -114,7 +224,11 @@
         return "RecognitionSupport { " +
                 "installedLanguages = " + mInstalledLanguages + ", " +
                 "pendingLanguages = " + mPendingLanguages + ", " +
-                "supportedLanguages = " + mSupportedLanguages +
+                "supportedLanguages = " + mSupportedLanguages + ", " +
+                "installedOnDeviceLanguages = " + mInstalledOnDeviceLanguages + ", " +
+                "pendingOnDeviceLanguages = " + mPendingOnDeviceLanguages + ", " +
+                "supportedOnDeviceLanguages = " + mSupportedOnDeviceLanguages + ", " +
+                "onlineLanguages = " + mOnlineLanguages +
         " }";
     }
 
@@ -133,7 +247,11 @@
         return true
                 && java.util.Objects.equals(mInstalledLanguages, that.mInstalledLanguages)
                 && java.util.Objects.equals(mPendingLanguages, that.mPendingLanguages)
-                && java.util.Objects.equals(mSupportedLanguages, that.mSupportedLanguages);
+                && java.util.Objects.equals(mSupportedLanguages, that.mSupportedLanguages)
+                && java.util.Objects.equals(mInstalledOnDeviceLanguages, that.mInstalledOnDeviceLanguages)
+                && java.util.Objects.equals(mPendingOnDeviceLanguages, that.mPendingOnDeviceLanguages)
+                && java.util.Objects.equals(mSupportedOnDeviceLanguages, that.mSupportedOnDeviceLanguages)
+                && java.util.Objects.equals(mOnlineLanguages, that.mOnlineLanguages);
     }
 
     @Override
@@ -146,6 +264,10 @@
         _hash = 31 * _hash + java.util.Objects.hashCode(mInstalledLanguages);
         _hash = 31 * _hash + java.util.Objects.hashCode(mPendingLanguages);
         _hash = 31 * _hash + java.util.Objects.hashCode(mSupportedLanguages);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mInstalledOnDeviceLanguages);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mPendingOnDeviceLanguages);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mSupportedOnDeviceLanguages);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mOnlineLanguages);
         return _hash;
     }
 
@@ -158,6 +280,10 @@
         dest.writeStringList(mInstalledLanguages);
         dest.writeStringList(mPendingLanguages);
         dest.writeStringList(mSupportedLanguages);
+        dest.writeStringList(mInstalledOnDeviceLanguages);
+        dest.writeStringList(mPendingOnDeviceLanguages);
+        dest.writeStringList(mSupportedOnDeviceLanguages);
+        dest.writeStringList(mOnlineLanguages);
     }
 
     @Override
@@ -177,16 +303,42 @@
         in.readStringList(pendingLanguages);
         List<String> supportedLanguages = new java.util.ArrayList<>();
         in.readStringList(supportedLanguages);
+        List<String> installedOnDeviceLanguages = new java.util.ArrayList<>();
+        in.readStringList(installedOnDeviceLanguages);
+        List<String> pendingOnDeviceLanguages = new java.util.ArrayList<>();
+        in.readStringList(pendingOnDeviceLanguages);
+        List<String> supportedOnDeviceLanguages = new java.util.ArrayList<>();
+        in.readStringList(supportedOnDeviceLanguages);
+        List<String> onlineLanguages = new java.util.ArrayList<>();
+        in.readStringList(onlineLanguages);
 
         this.mInstalledLanguages = installedLanguages;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mInstalledLanguages);
+        com.android.internal.util.AnnotationValidations.validate(
+                Deprecated.class, null, mInstalledLanguages);
         this.mPendingLanguages = pendingLanguages;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mPendingLanguages);
+        com.android.internal.util.AnnotationValidations.validate(
+                Deprecated.class, null, mPendingLanguages);
         this.mSupportedLanguages = supportedLanguages;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mSupportedLanguages);
+        com.android.internal.util.AnnotationValidations.validate(
+                Deprecated.class, null, mSupportedLanguages);
+        this.mInstalledOnDeviceLanguages = installedOnDeviceLanguages;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mInstalledOnDeviceLanguages);
+        this.mPendingOnDeviceLanguages = pendingOnDeviceLanguages;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mPendingOnDeviceLanguages);
+        this.mSupportedOnDeviceLanguages = supportedOnDeviceLanguages;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSupportedOnDeviceLanguages);
+        this.mOnlineLanguages = onlineLanguages;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mOnlineLanguages);
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -212,9 +364,13 @@
     @DataClass.Generated.Member
     public static final class Builder {
 
-        private @NonNull List<String> mInstalledLanguages;
-        private @NonNull List<String> mPendingLanguages;
-        private @NonNull List<String> mSupportedLanguages;
+        private @NonNull @Deprecated List<String> mInstalledLanguages;
+        private @NonNull @Deprecated List<String> mPendingLanguages;
+        private @NonNull @Deprecated List<String> mSupportedLanguages;
+        private @NonNull List<String> mInstalledOnDeviceLanguages;
+        private @NonNull List<String> mPendingOnDeviceLanguages;
+        private @NonNull List<String> mSupportedOnDeviceLanguages;
+        private @NonNull List<String> mOnlineLanguages;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -223,9 +379,12 @@
 
         /**
          * Support for this request is ready for use on this device for the returned languages.
+         *
+         * @deprecated See {@link #getInstalledOnDeviceLanguages()}.
          */
         @DataClass.Generated.Member
-        public @NonNull Builder setInstalledLanguages(@NonNull List<String> value) {
+        @Deprecated @NonNull
+        public Builder setInstalledLanguages(@NonNull @Deprecated List<String> value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x1;
             mInstalledLanguages = value;
@@ -234,7 +393,8 @@
 
         /** @see #setInstalledLanguages */
         @DataClass.Generated.Member
-        public @NonNull Builder addInstalledLanguage(@NonNull String value) {
+        @Deprecated @NonNull
+        public Builder addInstalledLanguage(@NonNull String value) {
             if (mInstalledLanguages == null) setInstalledLanguages(new java.util.ArrayList<>());
             mInstalledLanguages.add(value);
             return this;
@@ -242,9 +402,12 @@
 
         /**
          * Support for this request is scheduled for download for the returned languages.
+         *
+         * @deprecated See {@link #getPendingOnDeviceLanguages()}.
          */
         @DataClass.Generated.Member
-        public @NonNull Builder setPendingLanguages(@NonNull List<String> value) {
+        @Deprecated @NonNull
+        public Builder setPendingLanguages(@NonNull @Deprecated List<String> value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x2;
             mPendingLanguages = value;
@@ -253,17 +416,22 @@
 
         /** @see #setPendingLanguages */
         @DataClass.Generated.Member
-        public @NonNull Builder addPendingLanguage(@NonNull String value) {
+        @Deprecated @NonNull
+        public Builder addPendingLanguage(@NonNull String value) {
             if (mPendingLanguages == null) setPendingLanguages(new java.util.ArrayList<>());
             mPendingLanguages.add(value);
             return this;
         }
 
         /**
-         * These languages are supported but need to be downloaded before use.
+         * These languages are supported but need to be downloaded before use. See {@link
+         * SpeechRecognizer#triggerModelDownload(Intent)}.
+         *
+         * @deprecated See {@link #getSupportedOnDeviceLanguages()}.
          */
         @DataClass.Generated.Member
-        public @NonNull Builder setSupportedLanguages(@NonNull List<String> value) {
+        @Deprecated @NonNull
+        public Builder setSupportedLanguages(@NonNull @Deprecated List<String> value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x4;
             mSupportedLanguages = value;
@@ -272,35 +440,131 @@
 
         /** @see #setSupportedLanguages */
         @DataClass.Generated.Member
-        public @NonNull Builder addSupportedLanguage(@NonNull String value) {
+        @Deprecated @NonNull
+        public Builder addSupportedLanguage(@NonNull String value) {
             if (mSupportedLanguages == null) setSupportedLanguages(new java.util.ArrayList<>());
             mSupportedLanguages.add(value);
             return this;
         }
 
+        /**
+         * Support for this request is ready for use on this device for the returned languages.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setInstalledOnDeviceLanguages(@NonNull List<String> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x8;
+            mInstalledOnDeviceLanguages = value;
+            return this;
+        }
+
+        /** @see #setInstalledOnDeviceLanguages */
+        @DataClass.Generated.Member
+        public @NonNull Builder addInstalledOnDeviceLanguage(@NonNull String value) {
+            if (mInstalledOnDeviceLanguages == null) setInstalledOnDeviceLanguages(new java.util.ArrayList<>());
+            mInstalledOnDeviceLanguages.add(value);
+            return this;
+        }
+
+        /**
+         * Support for this request is scheduled for download for the returned languages.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setPendingOnDeviceLanguages(@NonNull List<String> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x10;
+            mPendingOnDeviceLanguages = value;
+            return this;
+        }
+
+        /** @see #setPendingOnDeviceLanguages */
+        @DataClass.Generated.Member
+        public @NonNull Builder addPendingOnDeviceLanguage(@NonNull String value) {
+            if (mPendingOnDeviceLanguages == null) setPendingOnDeviceLanguages(new java.util.ArrayList<>());
+            mPendingOnDeviceLanguages.add(value);
+            return this;
+        }
+
+        /**
+         * These languages are supported but need to be downloaded before use. See {@link
+         * SpeechRecognizer#triggerModelDownload(Intent)}.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setSupportedOnDeviceLanguages(@NonNull List<String> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x20;
+            mSupportedOnDeviceLanguages = value;
+            return this;
+        }
+
+        /** @see #setSupportedOnDeviceLanguages */
+        @DataClass.Generated.Member
+        public @NonNull Builder addSupportedOnDeviceLanguage(@NonNull String value) {
+            if (mSupportedOnDeviceLanguages == null) setSupportedOnDeviceLanguages(new java.util.ArrayList<>());
+            mSupportedOnDeviceLanguages.add(value);
+            return this;
+        }
+
+        /**
+         * Support for this request is available via a remote implementation. {@link SpeechRecognizer}
+         * created via {@link SpeechRecognizer#createOnDeviceSpeechRecognizer(Context)} are expected to
+         * return an empty list.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setOnlineLanguages(@NonNull List<String> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x40;
+            mOnlineLanguages = value;
+            return this;
+        }
+
+        /** @see #setOnlineLanguages */
+        @DataClass.Generated.Member
+        public @NonNull Builder addOnlineLanguage(@NonNull String value) {
+            if (mOnlineLanguages == null) setOnlineLanguages(new java.util.ArrayList<>());
+            mOnlineLanguages.add(value);
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull RecognitionSupport build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x8; // Mark builder used
+            mBuilderFieldsSet |= 0x80; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
-                mInstalledLanguages = null;
+                mInstalledLanguages = List.of();
             }
             if ((mBuilderFieldsSet & 0x2) == 0) {
-                mPendingLanguages = null;
+                mPendingLanguages = List.of();
             }
             if ((mBuilderFieldsSet & 0x4) == 0) {
-                mSupportedLanguages = null;
+                mSupportedLanguages = List.of();
+            }
+            if ((mBuilderFieldsSet & 0x8) == 0) {
+                mInstalledOnDeviceLanguages = List.of();
+            }
+            if ((mBuilderFieldsSet & 0x10) == 0) {
+                mPendingOnDeviceLanguages = List.of();
+            }
+            if ((mBuilderFieldsSet & 0x20) == 0) {
+                mSupportedOnDeviceLanguages = List.of();
+            }
+            if ((mBuilderFieldsSet & 0x40) == 0) {
+                mOnlineLanguages = List.of();
             }
             RecognitionSupport o = new RecognitionSupport(
                     mInstalledLanguages,
                     mPendingLanguages,
-                    mSupportedLanguages);
+                    mSupportedLanguages,
+                    mInstalledOnDeviceLanguages,
+                    mPendingOnDeviceLanguages,
+                    mSupportedOnDeviceLanguages,
+                    mOnlineLanguages);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x8) != 0) {
+            if ((mBuilderFieldsSet & 0x80) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -308,10 +572,10 @@
     }
 
     @DataClass.Generated(
-            time = 1644582623366L,
+            time = 1647890081869L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/speech/RecognitionSupport.java",
-            inputSignatures = "private @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"installedLanguage\") java.util.List<java.lang.String> mInstalledLanguages\nprivate @com.android.internal.util.DataClass.PluralOf(\"pendingLanguage\") @android.annotation.NonNull java.util.List<java.lang.String> mPendingLanguages\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"supportedLanguage\") java.util.List<java.lang.String> mSupportedLanguages\nclass RecognitionSupport 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 = "private @android.annotation.NonNull @java.lang.Deprecated @com.android.internal.util.DataClass.PluralOf(\"installedLanguage\") java.util.List<java.lang.String> mInstalledLanguages\nprivate @android.annotation.NonNull @java.lang.Deprecated @com.android.internal.util.DataClass.PluralOf(\"pendingLanguage\") java.util.List<java.lang.String> mPendingLanguages\nprivate @android.annotation.NonNull @java.lang.Deprecated @com.android.internal.util.DataClass.PluralOf(\"supportedLanguage\") java.util.List<java.lang.String> mSupportedLanguages\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"installedOnDeviceLanguage\") java.util.List<java.lang.String> mInstalledOnDeviceLanguages\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"pendingOnDeviceLanguage\") java.util.List<java.lang.String> mPendingOnDeviceLanguages\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"supportedOnDeviceLanguage\") java.util.List<java.lang.String> mSupportedOnDeviceLanguages\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"onlineLanguage\") java.util.List<java.lang.String> mOnlineLanguages\nclass RecognitionSupport 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/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index fee23f4..9ed57c3 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -103,7 +103,7 @@
      * @param includePad set whether to include extra space beyond font ascent and descent which is
      *                   needed to avoid clipping in some scripts
      * @param ellipsize whether to ellipsize the text if width of the text is longer than the
-     *                  requested width
+     *                  requested width. null if ellipsis is not applied.
      * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is
      *                        {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is
      *                        not used, {@code outerWidth} is used instead
@@ -116,7 +116,7 @@
             @NonNull CharSequence source, @NonNull TextPaint paint,
             @IntRange(from = 0) int outerWidth,
             @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics,
-            boolean includePad, @NonNull TextUtils.TruncateAt ellipsize,
+            boolean includePad, @Nullable TextUtils.TruncateAt ellipsize,
             @IntRange(from = 0) int ellipsizedWidth, boolean useFallbackLineSpacing) {
         return new BoringLayout(source, paint, outerWidth, align, 1f, 0f, metrics, includePad,
                 ellipsize, ellipsizedWidth, useFallbackLineSpacing);
@@ -146,6 +146,7 @@
         mEllipsizedWidth = outerwidth;
         mEllipsizedStart = 0;
         mEllipsizedCount = 0;
+        mUseFallbackLineSpacing = false;
 
         init(source, paint, align, metrics, includePad, true, false /* useFallbackLineSpacing */);
         return this;
@@ -169,7 +170,7 @@
      * @param includePad set whether to include extra space beyond font ascent and descent which is
      *                   needed to avoid clipping in some scripts
      * @param ellipsize whether to ellipsize the text if width of the text is longer than the
-     *                  requested width
+     *                  requested width. null if ellipsis not applied.
      * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is
      *                        {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is
      *                        not used, {@code outerWidth} is used instead
@@ -181,7 +182,7 @@
     public @NonNull BoringLayout replaceOrMake(@NonNull CharSequence source,
             @NonNull TextPaint paint, @IntRange(from = 0) int outerWidth,
             @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics, boolean includePad,
-            @NonNull TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth,
+            @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth,
             boolean useFallbackLineSpacing) {
         boolean trust;
 
@@ -200,6 +201,8 @@
             trust = false;
         }
 
+        mUseFallbackLineSpacing = useFallbackLineSpacing;
+
         init(getText(), paint, align, metrics, includePad, trust,
                 useFallbackLineSpacing);
         return this;
@@ -252,6 +255,7 @@
         mEllipsizedWidth = outerwidth;
         mEllipsizedStart = 0;
         mEllipsizedCount = 0;
+        mUseFallbackLineSpacing = false;
 
         init(source, paint, align, metrics, includePad, true, false /* useFallbackLineSpacing */);
     }
@@ -294,7 +298,7 @@
      * @param includePad set whether to include extra space beyond font ascent and descent which is
      *                   needed to avoid clipping in some scripts
      * @param ellipsize whether to ellipsize the text if width of the text is longer than the
-     *                  requested {@code outerWidth}
+     *                  requested {@code outerWidth}. null if ellipsis is not applied.
      * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is
      *                        {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is
      *                        not used, {@code outerWidth} is used instead
@@ -307,7 +311,7 @@
             @NonNull CharSequence source, @NonNull TextPaint paint,
             @IntRange(from = 0) int outerWidth, @NonNull Alignment align, float spacingMult,
             float spacingAdd, @NonNull BoringLayout.Metrics metrics, boolean includePad,
-            @NonNull TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth,
+            @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth,
             boolean useFallbackLineSpacing) {
         /*
          * It is silly to have to call super() and then replaceWith(),
@@ -331,6 +335,7 @@
             trust = false;
         }
 
+        mUseFallbackLineSpacing = useFallbackLineSpacing;
         init(getText(), paint, align, metrics, includePad, trust, useFallbackLineSpacing);
     }
 
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index c1fa079..60593ca 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -779,7 +779,9 @@
                                 + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
                     }
                     frameTimeNanos = startNanos - lastFrameOffset;
-                    frameData.setFrameTimeNanos(frameTimeNanos);
+                    DisplayEventReceiver.VsyncEventData latestVsyncEventData =
+                            mDisplayEventReceiver.getLatestVsyncEventData();
+                    frameData.updateFrameData(frameTimeNanos, latestVsyncEventData);
                 }
 
                 if (frameTimeNanos < mLastFrameTimeNanos) {
@@ -877,7 +879,9 @@
                     }
                     frameTimeNanos = now - lastFrameOffset;
                     mLastFrameTimeNanos = frameTimeNanos;
-                    frameData.setFrameTimeNanos(frameTimeNanos);
+                    DisplayEventReceiver.VsyncEventData latestVsyncEventData =
+                            mDisplayEventReceiver.getLatestVsyncEventData();
+                    frameData.updateFrameData(frameTimeNanos, latestVsyncEventData);
                 }
             }
         }
@@ -1012,11 +1016,6 @@
             return mVsyncId;
         }
 
-        /** Sets the vsync ID. */
-        void resetVsyncId() {
-            mVsyncId = FrameInfo.INVALID_VSYNC_ID;
-        }
-
         /**
          * The time in {@link System#nanoTime()} timebase which this frame is expected to be
          * presented.
@@ -1061,17 +1060,15 @@
         }
 
         private long mFrameTimeNanos;
-        private final FrameTimeline[] mFrameTimelines;
-        private final FrameTimeline mPreferredFrameTimeline;
+        private FrameTimeline[] mFrameTimelines;
+        private FrameTimeline mPreferredFrameTimeline;
 
-        void setFrameTimeNanos(long frameTimeNanos) {
+        void updateFrameData(long frameTimeNanos,
+                DisplayEventReceiver.VsyncEventData latestVsyncEventData) {
             mFrameTimeNanos = frameTimeNanos;
-            for (FrameTimeline ft : mFrameTimelines) {
-                // The ID is no longer valid because the frame time that was registered with the ID
-                // no longer matches.
-                // TODO(b/205721584): Ask SF for valid vsync information.
-                ft.resetVsyncId();
-            }
+            mFrameTimelines = convertFrameTimelines(latestVsyncEventData);
+            mPreferredFrameTimeline =
+                mFrameTimelines[latestVsyncEventData.preferredFrameTimelineIndex];
         }
 
         /** The time in nanoseconds when the frame started being rendered. */
@@ -1091,6 +1088,19 @@
         public FrameTimeline getPreferredFrameTimeline() {
             return mPreferredFrameTimeline;
         }
+
+        private FrameTimeline[] convertFrameTimelines(
+                DisplayEventReceiver.VsyncEventData vsyncEventData) {
+            FrameTimeline[] frameTimelines =
+                    new FrameTimeline[vsyncEventData.frameTimelines.length];
+            for (int i = 0; i < vsyncEventData.frameTimelines.length; i++) {
+                DisplayEventReceiver.VsyncEventData.FrameTimeline frameTimeline =
+                        vsyncEventData.frameTimelines[i];
+                frameTimelines[i] = new FrameTimeline(frameTimeline.vsyncId,
+                        frameTimeline.expectedPresentTime, frameTimeline.deadline);
+            }
+            return frameTimelines;
+        }
     }
 
     /**
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 774bab4..3a74b2e 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -84,6 +84,7 @@
     private static native void nativeDispose(long receiverPtr);
     @FastNative
     private static native void nativeScheduleVsync(long receiverPtr);
+    private static native VsyncEventData nativeGetLatestVsyncEventData(long receiverPtr);
 
     /**
      * Creates a display event receiver.
@@ -279,6 +280,13 @@
         }
     }
 
+    /**
+     * Gets the latest vsync event data from surface flinger.
+     */
+    VsyncEventData getLatestVsyncEventData() {
+        return nativeGetLatestVsyncEventData(mReceiverPtr);
+    }
+
     // Called from native code.
     @SuppressWarnings("unused")
     private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1b8dc70..febd2e2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -14649,6 +14649,7 @@
         int selectionStart;
         int selectionEnd;
         if (extendSelection && isAccessibilitySelectionExtendable()) {
+            prepareForExtendedAccessibilitySelection();
             selectionStart = getAccessibilitySelectionStart();
             if (selectionStart == ACCESSIBILITY_CURSOR_POSITION_UNDEFINED) {
                 selectionStart = forward ? segmentStart : segmentEnd;
@@ -14688,6 +14689,14 @@
     }
 
     /**
+     * Prepare for extended selection.
+     * @hide
+     */
+    public void prepareForExtendedAccessibilitySelection() {
+        return;
+    }
+
+    /**
      * @hide
      */
     public int getAccessibilitySelectionStart() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 76d360c..172cd03 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -23,8 +23,6 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.InputDevice.SOURCE_CLASS_NONE;
 import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.InsetsState.SIZE;
 import static android.view.View.PFLAG_DRAW_ANIMATION;
 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
@@ -401,10 +399,8 @@
     private static boolean sAlwaysAssignFocus;
 
     /**
-     * This list must only be modified by the main thread, so a lock is only needed when changing
-     * the list or when accessing the list from a non-main thread.
+     * This list must only be modified by the main thread.
      */
-    @GuardedBy("mWindowCallbacks")
     final ArrayList<WindowCallbacks> mWindowCallbacks = new ArrayList<>();
     @UnsupportedAppUsage
     @UiContext
@@ -973,15 +969,11 @@
     }
 
     public void addWindowCallbacks(WindowCallbacks callback) {
-        synchronized (mWindowCallbacks) {
-            mWindowCallbacks.add(callback);
-        }
+        mWindowCallbacks.add(callback);
     }
 
     public void removeWindowCallbacks(WindowCallbacks callback) {
-        synchronized (mWindowCallbacks) {
-            mWindowCallbacks.remove(callback);
-        }
+        mWindowCallbacks.remove(callback);
     }
 
     public void reportDrawFinish() {
@@ -1703,15 +1695,19 @@
         final boolean forceNextWindowRelayout = args.argi1 != 0;
         final int displayId = args.argi3;
         final int resizeMode = args.argi5;
-        final Rect backdropFrame = frames.backdropFrame;
 
-        final boolean frameChanged = !mWinFrame.equals(frames.frame);
-        final boolean backdropFrameChanged = !mPendingBackDropFrame.equals(backdropFrame);
+        final Rect frame = frames.frame;
+        final Rect displayFrame = frames.displayFrame;
+        if (mTranslator != null) {
+            mTranslator.translateRectInScreenToAppWindow(frame);
+            mTranslator.translateRectInScreenToAppWindow(displayFrame);
+        }
+        final boolean frameChanged = !mWinFrame.equals(frame);
         final boolean configChanged = !mLastReportedMergedConfiguration.equals(mergedConfiguration);
         final boolean displayChanged = mDisplay.getDisplayId() != displayId;
         final boolean resizeModeChanged = mResizeMode != resizeMode;
-        if (msg == MSG_RESIZED && !frameChanged && !backdropFrameChanged && !configChanged
-                && !displayChanged && !resizeModeChanged && !forceNextWindowRelayout) {
+        if (msg == MSG_RESIZED && !frameChanged && !configChanged && !displayChanged
+                && !resizeModeChanged && !forceNextWindowRelayout) {
             return;
         }
 
@@ -1727,9 +1723,17 @@
             onMovedToDisplay(displayId, mLastConfigurationFromResources);
         }
 
-        setFrame(frames.frame);
-        mTmpFrames.displayFrame.set(frames.displayFrame);
-        mPendingBackDropFrame.set(backdropFrame);
+        setFrame(frame);
+        mTmpFrames.displayFrame.set(displayFrame);
+
+        if (mDragResizing && mUseMTRenderer) {
+            boolean fullscreen = frame.equals(mPendingBackDropFrame);
+            for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+                mWindowCallbacks.get(i).onWindowSizeIsChanging(mPendingBackDropFrame, fullscreen,
+                        mAttachInfo.mVisibleInsets, mAttachInfo.mStableInsets);
+            }
+        }
+
         mForceNextWindowRelayout = forceNextWindowRelayout;
         mPendingAlwaysConsumeSystemBars = args.argi2 != 0;
         mSyncSeqId = args.argi4;
@@ -2310,11 +2314,12 @@
      */
     void updateCompatSysUiVisibility(@InternalInsetsType int type, boolean visible,
             boolean hasControl) {
-        if (type != ITYPE_STATUS_BAR && type != ITYPE_NAVIGATION_BAR) {
+        @InsetsType final int publicType = InsetsState.toPublicType(type);
+        if (publicType != Type.statusBars() && publicType != Type.navigationBars()) {
             return;
         }
         final SystemUiVisibilityInfo info = mCompatibleVisibilityInfo;
-        final int systemUiFlag = type == ITYPE_STATUS_BAR
+        final int systemUiFlag = publicType == Type.statusBars()
                 ? View.SYSTEM_UI_FLAG_FULLSCREEN
                 : View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
         final boolean wasVisible = (info.globalVisibility & systemUiFlag) == 0;
@@ -5619,8 +5624,6 @@
                         mTmpFrames.frame.top = t;
                         mTmpFrames.frame.bottom = t + h;
                         setFrame(mTmpFrames.frame);
-
-                        mPendingBackDropFrame.set(mWinFrame);
                         maybeHandleWindowMove(mWinFrame);
                     }
                     break;
@@ -8118,7 +8121,6 @@
                     getConfiguration().windowConfiguration.getBounds());
         }
 
-        mPendingBackDropFrame.set(mTmpFrames.backdropFrame);
         if (mSurfaceControl.isValid()) {
             if (!useBLAST()) {
                 mSurface.copyFrom(mSurfaceControl);
@@ -8149,6 +8151,7 @@
 
         if (mTranslator != null) {
             mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
+            mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame);
             mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
             mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
         }
@@ -8193,6 +8196,13 @@
 
     private void setFrame(Rect frame) {
         mWinFrame.set(frame);
+
+        // Surface position is now inherited from parent, and BackdropFrameRenderer uses backdrop
+        // frame to position content. Thus, we just keep the size of backdrop frame, and remove the
+        // offset to avoid double offset from display origin.
+        mPendingBackDropFrame.set(frame);
+        mPendingBackDropFrame.offsetTo(0, 0);
+
         mInsetsController.onFrameChanged(mOverrideInsetsFrame != null ?
             mOverrideInsetsFrame : frame);
     }
@@ -8584,28 +8594,7 @@
     private void dispatchResized(ClientWindowFrames frames, boolean reportDraw,
             MergedConfiguration mergedConfiguration, boolean forceLayout,
             boolean alwaysConsumeSystemBars, int displayId, int seqId, int resizeMode) {
-        final Rect frame = frames.frame;
-        final Rect backDropFrame = frames.backdropFrame;
-        if (DEBUG_LAYOUT) Log.v(mTag, "Resizing " + this + ": frame=" + frame.toShortString()
-                + " reportDraw=" + reportDraw
-                + " backDropFrame=" + backDropFrame);
-
-        // Tell all listeners that we are resizing the window so that the chrome can get
-        // updated as fast as possible on a separate thread,
-        if (mDragResizing && mUseMTRenderer) {
-            boolean fullscreen = frame.equals(backDropFrame);
-            synchronized (mWindowCallbacks) {
-                for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
-                    mWindowCallbacks.get(i).onWindowSizeIsChanging(backDropFrame, fullscreen,
-                            mAttachInfo.mVisibleInsets, mAttachInfo.mStableInsets);
-                }
-            }
-        }
-
         Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED);
-        if (mTranslator != null) {
-            mTranslator.translateRectInScreenToAppWindow(frame);
-        }
         SomeArgs args = SomeArgs.obtain();
         final boolean sameProcessCall = (Binder.getCallingPid() == android.os.Process.myPid());
         args.arg1 = sameProcessCall ? new ClientWindowFrames(frames) : frames;
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index a3b1313..b7b71f1 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -179,6 +179,8 @@
         final boolean hasCompatScale = compatScale != 1f;
         final int pw = outParentFrame.width();
         final int ph = outParentFrame.height();
+        final boolean extendedByCutout =
+                (attrs.privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0;
         int rw = requestedWidth;
         int rh = requestedHeight;
         float x, y;
@@ -186,11 +188,13 @@
 
         // If the view hierarchy hasn't been measured, the requested width and height would be
         // UNSPECIFIED_LENGTH. This can happen in the first layout of a window or in the simulated
-        // layout.
-        if (rw == UNSPECIFIED_LENGTH) {
+        // layout. If extendedByCutout is true, we cannot use the requested lengths. Otherwise,
+        // the window frame might be extended again because the requested lengths may come from the
+        // window frame.
+        if (rw == UNSPECIFIED_LENGTH || extendedByCutout) {
             rw = attrs.width >= 0 ? attrs.width : pw;
         }
-        if (rh == UNSPECIFIED_LENGTH) {
+        if (rh == UNSPECIFIED_LENGTH || extendedByCutout) {
             rh = attrs.height >= 0 ? attrs.height : ph;
         }
 
@@ -262,29 +266,15 @@
             Gravity.applyDisplay(attrs.gravity, outDisplayFrame, outFrame);
         }
 
-        if ((attrs.privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0
-                && !cutout.isEmpty()) {
-            // If the actual frame covering a display cutout, and the window is requesting to extend
-            // it's requested frame, re-do the frame calculation after getting the new requested
-            // size.
+        if (extendedByCutout && !displayCutoutSafe.contains(outFrame)) {
             mTempRect.set(outFrame);
-            // Do nothing if the display cutout and window don't overlap entirely. This may happen
-            // when the cutout is not on the same side with the window.
-            boolean shouldExpand = false;
-            final Rect [] cutoutBounds = cutout.getBoundingRectsAll();
-            for (Rect cutoutBound : cutoutBounds) {
-                if (cutoutBound.isEmpty()) continue;
-                if (mTempRect.contains(cutoutBound) || cutoutBound.contains(mTempRect)) {
-                    shouldExpand = true;
-                    break;
-                }
-            }
-            if (shouldExpand) {
-                // Try to fit move the bar to avoid the display cutout first. Make sure the clip
-                // flags are not set to make the window move.
-                final int clipFlags = DISPLAY_CLIP_VERTICAL | DISPLAY_CLIP_HORIZONTAL;
-                Gravity.applyDisplay(attrs.gravity & ~clipFlags, displayCutoutSafe,
-                        mTempRect);
+
+            // Move the frame into displayCutoutSafe.
+            final int clipFlags = DISPLAY_CLIP_VERTICAL | DISPLAY_CLIP_HORIZONTAL;
+            Gravity.applyDisplay(attrs.gravity & ~clipFlags, displayCutoutSafe,
+                    mTempRect);
+
+            if (mTempRect.intersect(outDisplayFrame)) {
                 outFrame.union(mTempRect);
             }
         }
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index fadbdbb..464414d8 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -646,11 +646,9 @@
      * @param bg The background color.  If 0, no background.  Currently must
      * be black, with any desired alpha level.
      *
-     * @deprecated None of window animations are running with background color.
      */
-    @Deprecated
     public void setBackgroundColor(@ColorInt int bg) {
-        // The background color is not needed any more, do nothing.
+        mBackgroundColor = bg;
     }
 
     /**
@@ -824,13 +822,10 @@
 
     /**
      * Returns the background color behind the animation.
-     *
-     * @deprecated None of window animations are running with background color.
      */
-    @Deprecated
     @ColorInt
     public int getBackgroundColor() {
-        return 0;
+        return mBackgroundColor;
     }
 
     /**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3c8fcb9..6f83a45 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -12363,9 +12363,10 @@
         }
 
         // A view should not be exposed as clickable/long-clickable to a service because of a
-        // LinkMovementMethod.
+        // LinkMovementMethod or because it has selectable and non-editable text.
         if ((info.isClickable() || info.isLongClickable())
-                && mMovement instanceof LinkMovementMethod) {
+                && (mMovement instanceof LinkMovementMethod
+                || (isTextSelectable() && !isTextEditable()))) {
             if (!hasOnClickListeners()) {
                 info.setClickable(false);
                 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
@@ -12597,11 +12598,9 @@
                         return true;
                     }
                     if (start >= 0 && start <= end && end <= text.length()) {
+                        requestFocusOnNonEditableSelectableText();
                         Selection.setSelection((Spannable) text, start, end);
-                        // Make sure selection mode is engaged.
-                        if (mEditor != null) {
-                            mEditor.startSelectionActionModeAsync(false);
-                        }
+                        hideAccessibilitySelectionControllers();
                         return true;
                     }
                 }
@@ -12695,6 +12694,18 @@
         return handled;
     }
 
+    private void requestFocusOnNonEditableSelectableText() {
+        if (!isTextEditable() && isTextSelectable()) {
+            if (!isEnabled()) {
+                return;
+            }
+
+            if (isFocusable() && !isFocused()) {
+                requestFocus();
+            }
+        }
+    }
+
     private boolean hasSpannableText() {
         return mText != null && mText instanceof Spannable;
     }
@@ -12723,9 +12734,7 @@
     /**
      * Returns the text that should be exposed to accessibility services.
      * <p>
-     * This approximates what is displayed visually. If the user has specified
-     * that accessibility services should speak passwords, this method will
-     * bypass any password transformation method and return unobscured text.
+     * This approximates what is displayed visually.
      *
      * @return the text that should be exposed to accessibility services, may
      *         be {@code null} if no text is set
@@ -13706,6 +13715,13 @@
     /**
      * @hide
      */
+    public void prepareForExtendedAccessibilitySelection() {
+        requestFocusOnNonEditableSelectableText();
+    }
+
+    /**
+     * @hide
+     */
     @Override
     public int getAccessibilitySelectionEnd() {
         return getSelectionEnd();
@@ -13727,8 +13743,12 @@
             Selection.removeSelection((Spannable) text);
         }
         // Hide all selection controllers used for adjusting selection
-        // since we are doing so explicitlty by other means and these
+        // since we are doing so explicitly by other means and these
         // controllers interact with how selection behaves.
+        hideAccessibilitySelectionControllers();
+    }
+
+    private void hideAccessibilitySelectionControllers() {
         if (mEditor != null) {
             mEditor.hideCursorAndSpanControllers();
             mEditor.stopTextActionMode();
diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java
index 5b915cc..51f3fe2 100644
--- a/core/java/android/window/ClientWindowFrames.java
+++ b/core/java/android/window/ClientWindowFrames.java
@@ -40,9 +40,6 @@
      */
     public final @NonNull Rect parentFrame = new Rect();
 
-    /** The background area while the window is resizing. */
-    public final @NonNull Rect backdropFrame = new Rect();
-
     public boolean isParentFrameClippedByDisplayCutout;
 
     public ClientWindowFrames() {
@@ -52,7 +49,6 @@
         frame.set(other.frame);
         displayFrame.set(other.displayFrame);
         parentFrame.set(other.parentFrame);
-        backdropFrame.set(other.backdropFrame);
         isParentFrameClippedByDisplayCutout = other.isParentFrameClippedByDisplayCutout;
     }
 
@@ -65,7 +61,6 @@
         frame.readFromParcel(in);
         displayFrame.readFromParcel(in);
         parentFrame.readFromParcel(in);
-        backdropFrame.readFromParcel(in);
         isParentFrameClippedByDisplayCutout = in.readBoolean();
     }
 
@@ -74,7 +69,6 @@
         frame.writeToParcel(dest, flags);
         displayFrame.writeToParcel(dest, flags);
         parentFrame.writeToParcel(dest, flags);
-        backdropFrame.writeToParcel(dest, flags);
         dest.writeBoolean(isParentFrameClippedByDisplayCutout);
     }
 
@@ -84,7 +78,6 @@
         return "ClientWindowFrames{frame=" + frame.toShortString(sb)
                 + " display=" + displayFrame.toShortString(sb)
                 + " parentFrame=" + parentFrame.toShortString(sb)
-                + " backdrop=" + backdropFrame.toShortString(sb)
                 + " parentClippedByDisplayCutout=" + isParentFrameClippedByDisplayCutout + "}";
     }
 
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index 10d7eca..19ee1d0 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -233,14 +233,6 @@
         }
 
         /**
-         * Set the animation duration if icon is animatable.
-         */
-        public Builder setAnimationDurationMillis(long duration) {
-            mIconAnimationDuration = Duration.ofMillis(duration);
-            return this;
-        }
-
-        /**
          * Set the Runnable that can receive the task which should be executed on UI thread.
          * @param uiThreadInitTask
          */
@@ -294,8 +286,7 @@
                 } else {
                     view.mIconView = createSurfaceView(view);
                 }
-                view.initIconAnimation(mIconDrawable,
-                        mIconAnimationDuration != null ? mIconAnimationDuration.toMillis() : 0);
+                view.initIconAnimation(mIconDrawable);
                 view.mIconAnimationStart = mIconAnimationStart;
                 view.mIconAnimationDuration = mIconAnimationDuration;
             } else if (mIconSize != 0) {
@@ -463,6 +454,11 @@
     /**
      * Returns the duration of the icon animation if icon is animatable.
      *
+     * Note the return value can be null or 0 if the
+     * {@link android.R.attr#windowSplashScreenAnimatedIcon} is not
+     * {@link android.graphics.drawable.AnimationDrawable} or
+     * {@link android.graphics.drawable.AnimatedVectorDrawable}.
+     *
      * @see android.R.attr#windowSplashScreenAnimatedIcon
      * @see android.R.attr#windowSplashScreenAnimationDuration
      */
@@ -497,12 +493,12 @@
         mSurfaceView.setChildSurfacePackage(mSurfacePackage);
     }
 
-    void initIconAnimation(Drawable iconDrawable, long duration) {
+    void initIconAnimation(Drawable iconDrawable) {
         if (!(iconDrawable instanceof IconAnimateListener)) {
             return;
         }
         IconAnimateListener aniDrawable = (IconAnimateListener) iconDrawable;
-        aniDrawable.prepareAnimate(duration, this::animationStartCallback);
+        aniDrawable.prepareAnimate(this::animationStartCallback);
         aniDrawable.setAnimationJankMonitoring(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationCancel(Animator animation) {
@@ -524,7 +520,7 @@
 
     private void animationStartCallback(long animDuration) {
         mIconAnimationStart = Instant.now();
-        if (animDuration > 0) {
+        if (animDuration >= 0) {
             mIconAnimationDuration = Duration.ofMillis(animDuration);
         }
     }
@@ -695,10 +691,9 @@
     public interface IconAnimateListener {
         /**
          * Prepare the animation if this drawable also be animatable.
-         * @param duration The animation duration.
          * @param startListener The callback listener used to receive the start of the animation.
          */
-        void prepareAnimate(long duration, LongConsumer startListener);
+        void prepareAnimate(LongConsumer startListener);
 
         /**
          * Stop animation.
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 7718a3b..51da61f 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -344,7 +344,7 @@
             if (parentChg.getMode() != TRANSIT_CHANGE) return false;
 
             // If there are no more parents left, then all the parents, so far, have not been
-            // visibility changes which means this change is indpendent.
+            // visibility changes which means this change is independent.
             if (parentChg.getParent() == null) return true;
 
             parentChg = info.getChange(parentChg.getParent());
diff --git a/core/java/com/android/internal/app/AppLocaleStore.java b/core/java/com/android/internal/app/AppLocaleStore.java
new file mode 100644
index 0000000..76e5898
--- /dev/null
+++ b/core/java/com/android/internal/app/AppLocaleStore.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.app.LocaleConfig;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.LocaleList;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+public class AppLocaleStore {
+    private static final String TAG = AppLocaleStore.class.getSimpleName();
+
+    public static ArrayList<Locale> getAppSupportedLocales(Context context, String packageName) {
+        ArrayList<Locale> appSupportedLocales = new ArrayList<>();
+        LocaleList packageLocaleList = getPackageLocales(context, packageName);
+
+        if (packageLocaleList != null && packageLocaleList.size() > 0) {
+            for (int i = 0; i < packageLocaleList.size(); i++) {
+                appSupportedLocales.add(packageLocaleList.get(i));
+            }
+            Log.d(TAG, "getAppSupportedLocales from LocaleConfig. Size: "
+                    + appSupportedLocales.size());
+        } else {
+            String[] languages = getAssetLocales(context, packageName);
+            for (String language : languages) {
+                appSupportedLocales.add(Locale.forLanguageTag(language));
+            }
+            Log.d(TAG, "getAppSupportedLocales from asset. Size: "
+                    + appSupportedLocales.size());
+        }
+        return appSupportedLocales;
+    }
+
+    private static LocaleList getPackageLocales(Context context, String packageName) {
+        try {
+            LocaleConfig localeConfig =
+                    new LocaleConfig(context.createPackageContext(packageName, 0));
+            if (localeConfig.getStatus() == LocaleConfig.STATUS_SUCCESS) {
+                return localeConfig.getSupportedLocales();
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.w(TAG, "Can not found the package name : " + packageName + " / " + e);
+        }
+        return null;
+    }
+
+    private static String[] getAssetLocales(Context context, String packageName) {
+        try {
+            PackageManager packageManager = context.getPackageManager();
+            String[] locales = packageManager.getResourcesForApplication(
+                    packageManager.getPackageInfo(packageName, PackageManager.MATCH_ALL)
+                            .applicationInfo).getAssets().getNonSystemLocales();
+            if (locales == null) {
+                Log.i(TAG, "[" + packageName + "] locales are null.");
+                return new String[0];
+            } else if (locales.length <= 0) {
+                Log.i(TAG, "[" + packageName + "] locales length is 0.");
+                return new String[0];
+            }
+            return locales;
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.w(TAG, "Can not found the package name : " + packageName + " / " + e);
+        }
+        return new String[0];
+    }
+
+}
diff --git a/core/java/com/android/internal/app/LocaleHelper.java b/core/java/com/android/internal/app/LocaleHelper.java
index 707286e..57bd3f9 100644
--- a/core/java/com/android/internal/app/LocaleHelper.java
+++ b/core/java/com/android/internal/app/LocaleHelper.java
@@ -234,7 +234,11 @@
         public int compare(LocaleStore.LocaleInfo lhs, LocaleStore.LocaleInfo rhs) {
             // We don't care about the various suggestion types, just "suggested" (!= 0)
             // and "all others" (== 0)
-            if (lhs.isSuggested() == rhs.isSuggested()) {
+            if (lhs.isAppCurrentLocale() || rhs.isAppCurrentLocale()) {
+                return lhs.isAppCurrentLocale() ? -1 : 1;
+            } else if (lhs.isSystemLocale() || rhs.isSystemLocale()) {
+                    return lhs.isSystemLocale() ? -1 : 1;
+            } else if (lhs.isSuggested() == rhs.isSuggested()) {
                 // They are in the same "bucket" (suggested / others), so we compare the text
                 return mCollator.compare(
                         removePrefixForCompare(lhs.getLocale(), lhs.getLabel(mCountryMode)),
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index b4ae56f..e7cb43e 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -23,6 +23,7 @@
 import android.os.Bundle;
 import android.os.LocaleList;
 import android.text.TextUtils;
+import android.util.Log;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -32,6 +33,7 @@
 
 import com.android.internal.R;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Locale;
@@ -45,6 +47,7 @@
  * default locale.</p>
  */
 public class LocalePickerWithRegion extends ListFragment implements SearchView.OnQueryTextListener {
+    private static final String TAG = LocalePickerWithRegion.class.getSimpleName();
     private static final String PARENT_FRAGMENT_NAME = "localeListEditor";
 
     private SuggestedLocaleAdapter mAdapter;
@@ -57,6 +60,7 @@
     private boolean mPreviousSearchHadFocus = false;
     private int mFirstVisiblePosition = 0;
     private int mTopDistance = 0;
+    private String mAppPackageName;
 
     /**
      * Other classes can register to be notified when a locale was selected.
@@ -73,17 +77,25 @@
 
     private static LocalePickerWithRegion createCountryPicker(Context context,
             LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
-            boolean translatedOnly) {
+            boolean translatedOnly, String appPackageName) {
         LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
         boolean shouldShowTheList = localePicker.setListener(context, listener, parent,
-                translatedOnly);
+                translatedOnly, appPackageName);
         return shouldShowTheList ? localePicker : null;
     }
 
     public static LocalePickerWithRegion createLanguagePicker(Context context,
             LocaleSelectedListener listener, boolean translatedOnly) {
         LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
-        localePicker.setListener(context, listener, /* parent */ null, translatedOnly);
+        localePicker.setListener(context, listener, /* parent */ null, translatedOnly, null);
+        return localePicker;
+    }
+
+    public static LocalePickerWithRegion createLanguagePicker(Context context,
+            LocaleSelectedListener listener, boolean translatedOnly, String appPackageName) {
+        LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
+        localePicker.setListener(
+                context, listener, /* parent */ null, translatedOnly, appPackageName);
         return localePicker;
     }
 
@@ -101,20 +113,32 @@
      * "pretending" it was selected, and return false.</p>
      */
     private boolean setListener(Context context, LocaleSelectedListener listener,
-            LocaleStore.LocaleInfo parent, boolean translatedOnly) {
+            LocaleStore.LocaleInfo parent, boolean translatedOnly, String appPackageName) {
         this.mParentLocale = parent;
         this.mListener = listener;
         this.mTranslatedOnly = translatedOnly;
+        this.mAppPackageName = appPackageName;
         setRetainInstance(true);
 
         final HashSet<String> langTagsToIgnore = new HashSet<>();
-        if (!translatedOnly) {
+        LocaleStore.LocaleInfo appCurrentLocale =
+                LocaleStore.getAppCurrentLocaleInfo(context, appPackageName);
+        boolean isForCountryMode = parent != null;
+
+        if (!TextUtils.isEmpty(appPackageName) && !isForCountryMode) {
+            if (appCurrentLocale != null) {
+                Log.d(TAG, "appCurrentLocale: " + appCurrentLocale.getLocale().toLanguageTag());
+                langTagsToIgnore.add(appCurrentLocale.getLocale().toLanguageTag());
+            } else {
+                Log.d(TAG, "appCurrentLocale is null");
+            }
+        } else if (!translatedOnly) {
             final LocaleList userLocales = LocalePicker.getLocales();
             final String[] langTags = userLocales.toLanguageTags().split(",");
             Collections.addAll(langTagsToIgnore, langTags);
         }
 
-        if (parent != null) {
+        if (isForCountryMode) {
             mLocaleList = LocaleStore.getLevelLocales(context,
                     langTagsToIgnore, parent, translatedOnly);
             if (mLocaleList.size() <= 1) {
@@ -127,10 +151,39 @@
             mLocaleList = LocaleStore.getLevelLocales(context, langTagsToIgnore,
                     null /* no parent */, translatedOnly);
         }
+        Log.d(TAG, "mLocaleList size:  " + mLocaleList.size());
 
+        // Adding current locale and system default option into suggestion list
+        if(!TextUtils.isEmpty(appPackageName)) {
+            if (appCurrentLocale != null && !isForCountryMode) {
+                mLocaleList.add(appCurrentLocale);
+            }
+            filterTheLanguagesNotSupportedInApp(context, appPackageName);
+
+            if (!isForCountryMode) {
+                mLocaleList.add(LocaleStore.getSystemDefaultLocaleInfo());
+            }
+        }
         return true;
     }
 
+    private void filterTheLanguagesNotSupportedInApp(Context context, String appPackageName) {
+        ArrayList<Locale> supportedLocales =
+                AppLocaleStore.getAppSupportedLocales(context, appPackageName);
+
+        Set<LocaleStore.LocaleInfo> filteredList = new HashSet<>();
+        for(LocaleStore.LocaleInfo li: mLocaleList) {
+            for(Locale l: supportedLocales) {
+                if(LocaleList.matchesLanguageAndScript(li.getLocale(), l)) {
+                    filteredList.add(li);
+                }
+            }
+        }
+        Log.d(TAG, "mLocaleList after app-supported filter:  " + filteredList.size());
+
+        mLocaleList = filteredList;
+    }
+
     private void returnToParentFrame() {
         getFragmentManager().popBackStack(PARENT_FRAGMENT_NAME,
                 FragmentManager.POP_BACK_STACK_INCLUSIVE);
@@ -151,7 +204,7 @@
 
         final boolean countryMode = mParentLocale != null;
         final Locale sortingLocale = countryMode ? mParentLocale.getLocale() : Locale.getDefault();
-        mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode);
+        mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode, mAppPackageName);
         final LocaleHelper.LocaleInfoComparator comp =
                 new LocaleHelper.LocaleInfoComparator(sortingLocale, countryMode);
         mAdapter.sort(comp);
@@ -212,17 +265,22 @@
 
     @Override
     public void onListItemClick(ListView l, View v, int position, long id) {
+        SuggestedLocaleAdapter adapter = (SuggestedLocaleAdapter) getListAdapter();
         final LocaleStore.LocaleInfo locale =
-                (LocaleStore.LocaleInfo) getListAdapter().getItem(position);
+                (LocaleStore.LocaleInfo) adapter.getItem(position);
+        // Special case for resetting the app locale to equal the system locale.
+        boolean isSystemLocale = locale.isSystemLocale();
+        boolean isRegionLocale = locale.getParent() != null;
 
-        if (locale.getParent() != null) {
+        if (isSystemLocale || isRegionLocale) {
             if (mListener != null) {
                 mListener.onLocaleSelected(locale);
             }
             returnToParentFrame();
         } else {
             LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker(
-                    getContext(), mListener, locale, mTranslatedOnly /* translate only */);
+                    getContext(), mListener, locale, mTranslatedOnly /* translate only */,
+                    adapter.getAppPackageName());
             if (selector != null) {
                 getFragmentManager().beginTransaction()
                         .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index 1c5ca59..cea8eaa 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -16,11 +16,13 @@
 
 package com.android.internal.app;
 
+import android.app.LocaleManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.LocaleList;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
+import android.util.Log;
 
 import java.io.Serializable;
 import java.util.HashMap;
@@ -31,12 +33,17 @@
 
 public class LocaleStore {
     private static final HashMap<String, LocaleInfo> sLocaleCache = new HashMap<>();
+    private static final String TAG = LocaleStore.class.getSimpleName();
     private static boolean sFullyInitialized = false;
 
     public static class LocaleInfo implements Serializable {
         private static final int SUGGESTION_TYPE_NONE = 0;
         private static final int SUGGESTION_TYPE_SIM = 1 << 0;
         private static final int SUGGESTION_TYPE_CFG = 1 << 1;
+        // Only for per-app language picker
+        private static final int SUGGESTION_TYPE_CURRENT = 1 << 2;
+        // Only for per-app language picker
+        private static final int SUGGESTION_TYPE_SYSTEM_LANGUAGE = 1 << 3;
 
         private final Locale mLocale;
         private final Locale mParent;
@@ -189,6 +196,14 @@
         public void setChecked(boolean checked) {
             mIsChecked = checked;
         }
+
+        public boolean isAppCurrentLocale() {
+            return (mSuggestionFlags & SUGGESTION_TYPE_CURRENT) > 0;
+        }
+
+        public boolean isSystemLocale() {
+            return (mSuggestionFlags & SUGGESTION_TYPE_SYSTEM_LANGUAGE) > 0;
+        }
     }
 
     private static Set<String> getSimCountries(Context context) {
@@ -239,6 +254,40 @@
         }
     }
 
+    public static LocaleInfo getAppCurrentLocaleInfo(Context context, String appPackageName) {
+        if (appPackageName == null) {
+            return null;
+        }
+
+        LocaleManager localeManager = context.getSystemService(LocaleManager.class);
+        try {
+            LocaleList localeList = (localeManager == null)
+                    ? null : localeManager.getApplicationLocales(appPackageName);
+            Locale locale = localeList == null ? null : localeList.get(0);
+
+            if (locale != null) {
+                LocaleInfo localeInfo = new LocaleInfo(locale);
+                localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_CURRENT;
+                localeInfo.mIsTranslated = true;
+                return localeInfo;
+            }
+        } catch (IllegalArgumentException e) {
+            Log.d(TAG, "IllegalArgumentException ", e);
+        }
+        return null;
+    }
+
+    /**
+     * The "system default" is special case for per-app picker. Intentionally keep the locale
+     * empty to let activity know "system default" been selected.
+     */
+    public static LocaleInfo getSystemDefaultLocaleInfo() {
+        LocaleInfo systemDefaultInfo = new LocaleInfo("");
+        systemDefaultInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SYSTEM_LANGUAGE;
+        systemDefaultInfo.mIsTranslated = true;
+        return systemDefaultInfo;
+    }
+
     /*
      * Show all the languages supported for a country in the suggested list.
      * This is also handy for devices without SIM (tablets).
diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
index 46f47a3..c3e7920 100644
--- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
@@ -53,6 +53,7 @@
     private static final int TYPE_HEADER_SUGGESTED = 0;
     private static final int TYPE_HEADER_ALL_OTHERS = 1;
     private static final int TYPE_LOCALE = 2;
+    private static final int TYPE_SYSTEM_LANGUAGE_FOR_APP_LANGUAGE_PICKER = 3;
     private static final int MIN_REGIONS_FOR_SUGGESTIONS = 6;
 
     private ArrayList<LocaleStore.LocaleInfo> mLocaleOptions;
@@ -64,10 +65,18 @@
     private Locale mDisplayLocale = null;
     // used to potentially cache a modified Context that uses mDisplayLocale
     private Context mContextOverride = null;
+    private String mAppPackageName;
 
     public SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode) {
+        this(localeOptions, countryMode, null);
+    }
+
+    public SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode,
+            String appPackageName) {
         mCountryMode = countryMode;
         mLocaleOptions = new ArrayList<>(localeOptions.size());
+        mAppPackageName = appPackageName;
+
         for (LocaleStore.LocaleInfo li : localeOptions) {
             if (li.isSuggested()) {
                 mSuggestionCount++;
@@ -83,7 +92,8 @@
 
     @Override
     public boolean isEnabled(int position) {
-        return getItemViewType(position) == TYPE_LOCALE;
+        return getItemViewType(position) == TYPE_LOCALE
+                || getItemViewType(position) == TYPE_SYSTEM_LANGUAGE_FOR_APP_LANGUAGE_PICKER;
     }
 
     @Override
@@ -97,13 +107,20 @@
             if (position == mSuggestionCount + 1) {
                 return TYPE_HEADER_ALL_OTHERS;
             }
+
+            LocaleStore.LocaleInfo item = (LocaleStore.LocaleInfo) getItem(position);
+            if (item.isSystemLocale()) {
+                return TYPE_SYSTEM_LANGUAGE_FOR_APP_LANGUAGE_PICKER;
+            }
             return TYPE_LOCALE;
         }
     }
 
     @Override
     public int getViewTypeCount() {
-        if (showHeaders()) {
+        if (!TextUtils.isEmpty(mAppPackageName) && showHeaders()) {
+            return 4; // Two headers and 1 for "System language"
+        } else if (showHeaders()) {
             return 3; // Two headers in addition to the locales
         } else {
             return 1; // Locales items only
@@ -187,6 +204,21 @@
                 textView.setTextLocale(
                         mDisplayLocale != null ? mDisplayLocale : Locale.getDefault());
                 break;
+
+            case TYPE_SYSTEM_LANGUAGE_FOR_APP_LANGUAGE_PICKER:
+                if (!(convertView instanceof ViewGroup)) {
+                    convertView = mInflater.inflate(
+                            R.layout.app_language_picker_system_default, parent, false);
+                }
+
+                Locale defaultLocale = Locale.getDefault();
+                TextView title = convertView.findViewById(R.id.locale);
+                title.setText(R.string.system_locale_title);
+                title.setTextLocale(defaultLocale);
+                TextView subtitle = convertView.findViewById(R.id.system_locale_subtitle);
+                subtitle.setText(defaultLocale.getDisplayName());
+                subtitle.setTextLocale(defaultLocale);
+                break;
             default:
                 // Covers both null, and "reusing" a wrong kind of view
                 if (!(convertView instanceof ViewGroup)) {
@@ -316,4 +348,8 @@
     public Filter getFilter() {
         return new FilterByNativeAndUiNames();
     }
+
+    public String getAppPackageName() {
+        return mAppPackageName;
+    }
 }
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index 74749cc..fd8534d 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -29,6 +29,8 @@
 import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
 import static android.view.WindowManager.TRANSIT_OPEN;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -60,6 +62,7 @@
 import android.view.animation.TranslateAnimation;
 
 import com.android.internal.R;
+import com.android.internal.protolog.common.ProtoLog;
 
 import java.util.List;
 
@@ -98,10 +101,6 @@
 
     private static final String DEFAULT_PACKAGE = "android";
 
-    // TODO (b/215515255): remove once we full migrate to shell transitions
-    private static final boolean SHELL_TRANSITIONS_ENABLED =
-            SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
-
     private final Context mContext;
     private final String mTag;
 
@@ -256,9 +255,6 @@
                 resId = ent.array.getResourceId(animAttr, 0);
             }
         }
-        if (!SHELL_TRANSITIONS_ENABLED) {
-            resId = updateToLegacyIfNeeded(resId);
-        }
         resId = updateToTranslucentAnimIfNeeded(resId, transit);
         if (ResourceId.isValid(resId)) {
             return loadAnimationSafely(context, resId, mTag);
@@ -266,24 +262,6 @@
         return null;
     }
 
-    /**
-     * Replace animations that are not compatible with the legacy transition system with ones that
-     * are compatible with it.
-     * TODO (b/215515255): remove once we full migrate to shell transitions
-     */
-    private int updateToLegacyIfNeeded(int anim) {
-        if (anim == R.anim.activity_open_enter) {
-            return R.anim.activity_open_enter_legacy;
-        } else if (anim == R.anim.activity_open_exit) {
-            return R.anim.activity_open_exit_legacy;
-        } else if (anim == R.anim.activity_close_enter) {
-            return R.anim.activity_close_enter_legacy;
-        } else if (anim == R.anim.activity_close_exit) {
-            return R.anim.activity_close_exit_legacy;
-        }
-        return anim;
-    }
-
     /** Load animation by attribute Id from a specific AnimationStyle resource. */
     @Nullable
     public Animation loadAnimationAttr(String packageName, int animStyleResId, int animAttr,
@@ -320,9 +298,9 @@
     @Nullable
     private AttributeCache.Entry getCachedAnimations(LayoutParams lp) {
         if (mDebug) {
-            Slog.v(mTag, "Loading animations: layout params pkg="
-                    + (lp != null ? lp.packageName : null)
-                    + " resId=0x" + (lp != null ? Integer.toHexString(lp.windowAnimations) : null));
+            ProtoLog.v(WM_DEBUG_ANIM, "Loading animations: layout params pkg=%s resId=0x%x",
+                    lp != null ? lp.packageName : null,
+                    lp != null ? lp.windowAnimations : 0);
         }
         if (lp != null && lp.windowAnimations != 0) {
             // If this is a system resource, don't try to load it from the
@@ -334,7 +312,7 @@
                 packageName = DEFAULT_PACKAGE;
             }
             if (mDebug) {
-                Slog.v(mTag, "Loading animations: picked package=" + packageName);
+                ProtoLog.v(WM_DEBUG_ANIM, "Loading animations: picked package=%s", packageName);
             }
             return AttributeCache.instance().get(packageName, resId,
                     com.android.internal.R.styleable.WindowAnimation);
@@ -345,16 +323,16 @@
     @Nullable
     private AttributeCache.Entry getCachedAnimations(String packageName, int resId) {
         if (mDebug) {
-            Slog.v(mTag, "Loading animations: package="
-                    + packageName + " resId=0x" + Integer.toHexString(resId));
+            ProtoLog.v(WM_DEBUG_ANIM, "Loading animations: package=%s resId=0x%x",
+                    packageName, resId);
         }
         if (packageName != null) {
             if ((resId & 0xFF000000) == 0x01000000) {
                 packageName = DEFAULT_PACKAGE;
             }
             if (mDebug) {
-                Slog.v(mTag, "Loading animations: picked package="
-                        + packageName);
+                ProtoLog.v(WM_DEBUG_ANIM, "Loading animations: picked package=%s",
+                        packageName);
             }
             return AttributeCache.instance().get(packageName, resId,
                     com.android.internal.R.styleable.WindowAnimation);
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index 45c6d5f..7f36c79 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -60,6 +60,7 @@
             Consts.TAG_WM),
     WM_DEBUG_APP_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM),
+    WM_DEBUG_ANIM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
     WM_DEBUG_APP_TRANSITIONS_ANIM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM),
     WM_DEBUG_RECENTS_ANIMATIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
diff --git a/core/java/com/android/internal/widget/CachingIconView.java b/core/java/com/android/internal/widget/CachingIconView.java
index 299cbe1..d115697 100644
--- a/core/java/com/android/internal/widget/CachingIconView.java
+++ b/core/java/com/android/internal/widget/CachingIconView.java
@@ -23,6 +23,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
@@ -35,6 +36,8 @@
 import android.widget.ImageView;
 import android.widget.RemoteViews;
 
+import com.android.internal.R;
+
 import java.util.Objects;
 import java.util.function.Consumer;
 
@@ -55,9 +58,42 @@
     private int mBackgroundColor;
     private boolean mWillBeForceHidden;
 
+    private int mMaxDrawableWidth = -1;
+    private int mMaxDrawableHeight = -1;
+
+    public CachingIconView(Context context) {
+        this(context, null, 0, 0);
+    }
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public CachingIconView(Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
+        this(context, attrs, 0, 0);
+    }
+
+    public CachingIconView(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public CachingIconView(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    private void init(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        if (attrs == null) {
+            return;
+        }
+
+        TypedArray ta = context.obtainStyledAttributes(attrs,
+                R.styleable.CachingIconView, defStyleAttr, defStyleRes);
+        mMaxDrawableWidth = ta.getDimensionPixelSize(R.styleable
+                .CachingIconView_maxDrawableWidth, -1);
+        mMaxDrawableHeight = ta.getDimensionPixelSize(R.styleable
+                .CachingIconView_maxDrawableHeight, -1);
+        ta.recycle();
     }
 
     @Override
@@ -66,15 +102,27 @@
         if (!testAndSetCache(icon)) {
             mInternalSetDrawable = true;
             // This calls back to setImageDrawable, make sure we don't clear the cache there.
-            super.setImageIcon(icon);
+            Drawable drawable = loadSizeRestrictedIcon(icon);
+            if (drawable == null) {
+                super.setImageIcon(icon);
+            } else {
+                super.setImageDrawable(drawable);
+            }
             mInternalSetDrawable = false;
         }
     }
 
+    @Nullable
+    private Drawable loadSizeRestrictedIcon(@Nullable Icon icon) {
+        return LocalImageResolver.resolveImage(icon, getContext(), mMaxDrawableWidth,
+                mMaxDrawableHeight);
+    }
+
     @Override
-    public Runnable setImageIconAsync(@Nullable Icon icon) {
+    public Runnable setImageIconAsync(@Nullable final Icon icon) {
         resetCache();
-        return super.setImageIconAsync(icon);
+        Drawable drawable = loadSizeRestrictedIcon(icon);
+        return () -> setImageDrawable(drawable);
     }
 
     @Override
@@ -83,14 +131,30 @@
         if (!testAndSetCache(resId)) {
             mInternalSetDrawable = true;
             // This calls back to setImageDrawable, make sure we don't clear the cache there.
-            super.setImageResource(resId);
+            Drawable drawable = loadSizeRestrictedDrawable(resId);
+            if (drawable == null) {
+                super.setImageResource(resId);
+            } else {
+                super.setImageDrawable(drawable);
+            }
             mInternalSetDrawable = false;
         }
     }
 
+    @Nullable
+    private Drawable loadSizeRestrictedDrawable(@DrawableRes int resId) {
+        return LocalImageResolver.resolveImage(resId, getContext(), mMaxDrawableWidth,
+                mMaxDrawableHeight);
+    }
+
     @Override
     public Runnable setImageResourceAsync(@DrawableRes int resId) {
         resetCache();
+        Drawable drawable = loadSizeRestrictedDrawable(resId);
+        if (drawable != null) {
+            return () -> setImageDrawable(drawable);
+        }
+
         return super.setImageResourceAsync(resId);
     }
 
@@ -98,13 +162,31 @@
     @RemotableViewMethod(asyncImpl="setImageURIAsync")
     public void setImageURI(@Nullable Uri uri) {
         resetCache();
-        super.setImageURI(uri);
+        Drawable drawable = loadSizeRestrictedUri(uri);
+        if (drawable == null) {
+            super.setImageURI(uri);
+        } else {
+            mInternalSetDrawable = true;
+            super.setImageDrawable(drawable);
+            mInternalSetDrawable = false;
+        }
+    }
+
+    @Nullable
+    private Drawable loadSizeRestrictedUri(@Nullable Uri uri) {
+        return LocalImageResolver.resolveImage(uri, getContext(), mMaxDrawableWidth,
+                mMaxDrawableHeight);
     }
 
     @Override
     public Runnable setImageURIAsync(@Nullable Uri uri) {
         resetCache();
-        return super.setImageURIAsync(uri);
+        Drawable drawable = loadSizeRestrictedUri(uri);
+        if (drawable == null) {
+            return super.setImageURIAsync(uri);
+        } else {
+            return () -> setImageDrawable(drawable);
+        }
     }
 
     @Override
@@ -307,4 +389,18 @@
     public void setWillBeForceHidden(boolean forceHidden) {
         mWillBeForceHidden = forceHidden;
     }
+
+    /**
+     * Returns the set maximum width of drawable in pixels. -1 if not set.
+     */
+    public int getMaxDrawableWidth() {
+        return mMaxDrawableWidth;
+    }
+
+    /**
+     * Returns the set maximum height of drawable in pixels. -1 if not set.
+     */
+    public int getMaxDrawableHeight() {
+        return mMaxDrawableHeight;
+    }
 }
diff --git a/core/java/com/android/internal/widget/LocalImageResolver.java b/core/java/com/android/internal/widget/LocalImageResolver.java
index 616b699..ce27b34 100644
--- a/core/java/com/android/internal/widget/LocalImageResolver.java
+++ b/core/java/com/android/internal/widget/LocalImageResolver.java
@@ -16,70 +16,195 @@
 
 package com.android.internal.widget;
 
+import android.annotation.DrawableRes;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
 import android.graphics.ImageDecoder;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
+import android.util.Log;
 import android.util.Size;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.IOException;
 
 /** A class to extract Drawables from a MessagingStyle/ConversationStyle message. */
 public class LocalImageResolver {
-    private static final String TAG = LocalImageResolver.class.getSimpleName();
 
-    private static final int MAX_SAFE_ICON_SIZE_PX = 480;
+    private static final String TAG = "LocalImageResolver";
+
+    @VisibleForTesting
+    static final int DEFAULT_MAX_SAFE_ICON_SIZE_PX = 480;
 
     /**
-     * Resolve an image from the given Uri using {@link ImageDecoder}
+     * Resolve an image from the given Uri using {@link ImageDecoder} if it contains a
+     * bitmap reference.
      */
+    @Nullable
     public static Drawable resolveImage(Uri uri, Context context) throws IOException {
-        final ImageDecoder.Source source =
-                ImageDecoder.createSource(context.getContentResolver(), uri);
-        final Drawable drawable =
-                ImageDecoder.decodeDrawable(source, LocalImageResolver::onHeaderDecoded);
-        return drawable;
+        try {
+            final ImageDecoder.Source source =
+                    ImageDecoder.createSource(context.getContentResolver(), uri);
+            return ImageDecoder.decodeDrawable(source,
+                    (decoder, info, s) -> LocalImageResolver.onHeaderDecoded(decoder, info,
+                            DEFAULT_MAX_SAFE_ICON_SIZE_PX, DEFAULT_MAX_SAFE_ICON_SIZE_PX));
+        } catch (Exception e) {
+            // Invalid drawable resource can actually throw either NullPointerException or
+            // ResourceNotFoundException. This sanitizes to expected output.
+            throw new IOException(e);
+        }
     }
 
     /**
-     * Get the drawable from Icon using {@link ImageDecoder} if it contains a Uri, or
+     * Get the drawable from Icon using {@link ImageDecoder} if it contains a bitmap reference, or
      * using {@link Icon#loadDrawable(Context)} otherwise.  This will correctly apply the Icon's,
      * tint, if present, to the drawable.
+     *
+     * @return drawable or null if loading failed.
      */
-    public static Drawable resolveImage(Icon icon, Context context) throws IOException {
-        Uri uri = getResolvableUri(icon);
-        if (uri != null) {
-            Drawable result = resolveImage(uri, context);
-            if (icon.hasTint()) {
-                result.mutate();
-                result.setTintList(icon.getTintList());
-                result.setTintBlendMode(icon.getTintBlendMode());
-            }
-            return result;
+    @Nullable
+    public static Drawable resolveImage(@Nullable Icon icon, Context context) throws IOException {
+        return resolveImage(icon, context, DEFAULT_MAX_SAFE_ICON_SIZE_PX,
+                DEFAULT_MAX_SAFE_ICON_SIZE_PX);
+    }
+
+    /**
+     * Get the drawable from Icon using {@link ImageDecoder} if it contains a bitmap reference, or
+     * using {@link Icon#loadDrawable(Context)} otherwise.  This will correctly apply the Icon's,
+     * tint, if present, to the drawable.
+     *
+     * @throws IOException if the icon could not be loaded for whichever reason
+     */
+    @Nullable
+    public static Drawable resolveImage(@Nullable Icon icon, Context context, int maxWidth,
+            int maxHeight) {
+        if (icon == null) {
+            return null;
         }
+
+        switch (icon.getType()) {
+            case Icon.TYPE_URI:
+            case Icon.TYPE_URI_ADAPTIVE_BITMAP:
+                Uri uri = getResolvableUri(icon);
+                if (uri != null) {
+                    Drawable result = resolveImage(uri, context, maxWidth, maxHeight);
+                    if (result != null) {
+                        return tintDrawable(icon, result);
+                    }
+                }
+                break;
+            case Icon.TYPE_RESOURCE:
+                Drawable result = resolveImage(icon.getResId(), context, maxWidth, maxHeight);
+                if (result != null) {
+                    return tintDrawable(icon, result);
+                }
+                break;
+            case Icon.TYPE_BITMAP:
+            case Icon.TYPE_ADAPTIVE_BITMAP:
+                return resolveBitmapImage(icon, context, maxWidth, maxHeight);
+            case Icon.TYPE_DATA:    // We can't really improve on raw data images.
+            default:
+                break;
+        }
+
+        // Fallback to straight drawable load if we fail with more efficient approach.
+        try {
+            return icon.loadDrawable(context);
+        } catch (Resources.NotFoundException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Attempts to resolve the resource as a bitmap drawable constrained within max sizes.
+     */
+    @Nullable
+    public static Drawable resolveImage(Uri uri, Context context, int maxWidth, int maxHeight) {
+        final ImageDecoder.Source source =
+                ImageDecoder.createSource(context.getContentResolver(), uri);
+        return resolveImage(source, maxWidth, maxHeight);
+    }
+
+    /**
+     * Attempts to resolve the resource as a bitmap drawable constrained within max sizes.
+     *
+     * @return decoded drawable or null if the passed resource is not a straight bitmap
+     */
+    @Nullable
+    public static Drawable resolveImage(@DrawableRes int resId, Context context, int maxWidth,
+            int maxHeight) {
+        final ImageDecoder.Source source = ImageDecoder.createSource(context.getResources(), resId);
+        return resolveImage(source, maxWidth, maxHeight);
+    }
+
+    @Nullable
+    private static Drawable resolveBitmapImage(Icon icon, Context context, int maxWidth,
+            int maxHeight) {
+        Bitmap bitmap = icon.getBitmap();
+        if (bitmap == null) {
+            return null;
+        }
+
+        if (bitmap.getWidth() > maxWidth || bitmap.getHeight() > maxHeight) {
+            Icon smallerIcon = icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP
+                    ? Icon.createWithAdaptiveBitmap(bitmap) : Icon.createWithBitmap(bitmap);
+            // We don't want to modify the source icon, create a copy.
+            smallerIcon.setTintList(icon.getTintList())
+                    .setTintBlendMode(icon.getTintBlendMode())
+                    .scaleDownIfNecessary(maxWidth, maxHeight);
+            return smallerIcon.loadDrawable(context);
+        }
+
         return icon.loadDrawable(context);
     }
 
-    public static Drawable resolveImage(Uri uri, Context context, int maxWidth, int maxHeight)
-            throws IOException {
-        final ImageDecoder.Source source =
-                ImageDecoder.createSource(context.getContentResolver(), uri);
-        return ImageDecoder.decodeDrawable(source, (decoder, info, unused) -> {
-            final Size size = info.getSize();
-            if (size.getWidth() > size.getHeight()) {
-                if (size.getWidth() > maxWidth) {
-                    final int targetHeight = size.getHeight() * maxWidth / size.getWidth();
-                    decoder.setTargetSize(maxWidth, targetHeight);
+    @Nullable
+    private static Drawable tintDrawable(Icon icon, @Nullable Drawable drawable) {
+        if (drawable == null) {
+            return null;
+        }
+
+        if (icon.hasTint()) {
+            drawable.mutate();
+            drawable.setTintList(icon.getTintList());
+            drawable.setTintBlendMode(icon.getTintBlendMode());
+        }
+
+        return drawable;
+    }
+
+    private static Drawable resolveImage(ImageDecoder.Source source, int maxWidth, int maxHeight) {
+        try {
+            return ImageDecoder.decodeDrawable(source, (decoder, info, unused) -> {
+                if (maxWidth <= 0 || maxHeight <= 0) {
+                    return;
                 }
-            } else {
-                if (size.getHeight() > maxHeight) {
-                    final int targetWidth = size.getWidth() * maxHeight / size.getHeight();
-                    decoder.setTargetSize(targetWidth, maxHeight);
+
+                final Size size = info.getSize();
+                if (size.getWidth() > size.getHeight()) {
+                    if (size.getWidth() > maxWidth) {
+                        final int targetHeight = size.getHeight() * maxWidth / size.getWidth();
+                        decoder.setTargetSize(maxWidth, targetHeight);
+                    }
+                } else {
+                    if (size.getHeight() > maxHeight) {
+                        final int targetWidth = size.getWidth() * maxHeight / size.getHeight();
+                        decoder.setTargetSize(targetWidth, maxHeight);
+                    }
                 }
-            }
-        });
+            });
+
+        // ImageDecoder documentation is misleading a bit - it'll throw NotFoundException
+        // in some cases despite it not saying so. Rethrow it as an IOException to keep
+        // our API contract.
+        } catch (IOException | Resources.NotFoundException e) {
+            Log.e(TAG, "Failed to load image drawable", e);
+            return null;
+        }
     }
 
     private static int getPowerOfTwoForSampleRatio(double ratio) {
@@ -88,11 +213,12 @@
     }
 
     private static void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
-            ImageDecoder.Source source) {
+            int maxWidth, int maxHeight) {
         final Size size = info.getSize();
         final int originalSize = Math.max(size.getHeight(), size.getWidth());
-        final double ratio = (originalSize > MAX_SAFE_ICON_SIZE_PX)
-                ? originalSize * 1f / MAX_SAFE_ICON_SIZE_PX
+        final int maxSize = Math.max(maxWidth, maxHeight);
+        final double ratio = (originalSize > maxSize)
+                ? originalSize * 1f / maxSize
                 : 1.0;
         decoder.setTargetSampleSize(getPowerOfTwoForSampleRatio(ratio));
     }
@@ -101,7 +227,7 @@
      * Gets the Uri for this icon, assuming the icon can be treated as a pure Uri.  Null otherwise.
      */
     @Nullable
-    public static Uri getResolvableUri(@Nullable Icon icon) {
+    private static Uri getResolvableUri(@Nullable Icon icon) {
         if (icon == null || (icon.getType() != Icon.TYPE_URI
                 && icon.getType() != Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
             return null;
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
index 361ba95..1074004 100644
--- a/core/java/com/android/internal/widget/LockscreenCredential.java
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -175,27 +175,6 @@
         return mCredential;
     }
 
-    /**
-     *  Returns the credential type recognized by {@link StorageManager}. Can be one of
-     *  {@link StorageManager#CRYPT_TYPE_DEFAULT}, {@link StorageManager#CRYPT_TYPE_PATTERN},
-     *  {@link StorageManager#CRYPT_TYPE_PIN} or {@link StorageManager#CRYPT_TYPE_PASSWORD}.
-     */
-    public int getStorageCryptType() {
-        if (isNone()) {
-            return StorageManager.CRYPT_TYPE_DEFAULT;
-        }
-        if (isPattern()) {
-            return StorageManager.CRYPT_TYPE_PATTERN;
-        }
-        if (isPin()) {
-            return StorageManager.CRYPT_TYPE_PIN;
-        }
-        if (isPassword()) {
-            return StorageManager.CRYPT_TYPE_PASSWORD;
-        }
-        throw new IllegalStateException("Unhandled credential type");
-    }
-
     /** Returns whether this is an empty credential */
     public boolean isNone() {
         ensureNotZeroized();
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 13ca133..f28e2f6 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -396,6 +396,9 @@
     {
         JNIEnv* env = javavm_to_jnienv(mVM);
 
+        LOG_ALWAYS_FATAL_IF(env == nullptr,
+                            "Binder thread started or Java binder used, but env null. Attach JVM?");
+
         ALOGV("onTransact() on %p calling object %p in env %p vm %p\n", this, mObject, env, mVM);
 
         IPCThreadState* thread_state = IPCThreadState::self();
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 19402f7..0585586 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -108,6 +108,32 @@
     DisplayEventDispatcher::dispose();
 }
 
+static jobject createJavaVsyncEventData(JNIEnv* env, VsyncEventData vsyncEventData) {
+    ScopedLocalRef<jobjectArray>
+            frameTimelineObjs(env,
+                              env->NewObjectArray(VsyncEventData::kFrameTimelinesLength,
+                                                  gDisplayEventReceiverClassInfo
+                                                          .frameTimelineClassInfo.clazz,
+                                                  /*initial element*/ NULL));
+    for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
+        VsyncEventData::FrameTimeline frameTimeline = vsyncEventData.frameTimelines[i];
+        ScopedLocalRef<jobject>
+                frameTimelineObj(env,
+                                 env->NewObject(gDisplayEventReceiverClassInfo
+                                                        .frameTimelineClassInfo.clazz,
+                                                gDisplayEventReceiverClassInfo
+                                                        .frameTimelineClassInfo.init,
+                                                frameTimeline.vsyncId,
+                                                frameTimeline.expectedPresentationTime,
+                                                frameTimeline.deadlineTimestamp));
+        env->SetObjectArrayElement(frameTimelineObjs.get(), i, frameTimelineObj.get());
+    }
+    return env->NewObject(gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
+                          gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.init,
+                          frameTimelineObjs.get(), vsyncEventData.preferredFrameTimelineIndex,
+                          vsyncEventData.frameInterval);
+}
+
 void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId,
                                                uint32_t count, VsyncEventData vsyncEventData) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -116,37 +142,9 @@
     if (receiverObj.get()) {
         ALOGV("receiver %p ~ Invoking vsync handler.", this);
 
-        ScopedLocalRef<jobjectArray>
-                frameTimelineObjs(env,
-                                  env->NewObjectArray(VsyncEventData::kFrameTimelinesLength,
-                                                      gDisplayEventReceiverClassInfo
-                                                              .frameTimelineClassInfo.clazz,
-                                                      /*initial element*/ NULL));
-        for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
-            VsyncEventData::FrameTimeline frameTimeline = vsyncEventData.frameTimelines[i];
-            ScopedLocalRef<jobject>
-                    frameTimelineObj(env,
-                                     env->NewObject(gDisplayEventReceiverClassInfo
-                                                            .frameTimelineClassInfo.clazz,
-                                                    gDisplayEventReceiverClassInfo
-                                                            .frameTimelineClassInfo.init,
-                                                    frameTimeline.vsyncId,
-                                                    frameTimeline.expectedPresentationTime,
-                                                    frameTimeline.deadlineTimestamp));
-            env->SetObjectArrayElement(frameTimelineObjs.get(), i, frameTimelineObj.get());
-        }
-        ScopedLocalRef<jobject>
-                vsyncEventDataJava(env,
-                                   env->NewObject(gDisplayEventReceiverClassInfo
-                                                          .vsyncEventDataClassInfo.clazz,
-                                                  gDisplayEventReceiverClassInfo
-                                                          .vsyncEventDataClassInfo.init,
-                                                  frameTimelineObjs.get(),
-                                                  vsyncEventData.preferredFrameTimelineIndex,
-                                                  vsyncEventData.frameInterval));
-
+        jobject javaVsyncEventData = createJavaVsyncEventData(env, vsyncEventData);
         env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchVsync,
-                            timestamp, displayId.value, count, vsyncEventDataJava.get());
+                            timestamp, displayId.value, count, javaVsyncEventData);
         ALOGV("receiver %p ~ Returned from vsync handler.", this);
     }
 
@@ -255,19 +253,27 @@
     }
 }
 
+static jobject nativeGetLatestVsyncEventData(JNIEnv* env, jclass clazz, jlong receiverPtr) {
+    sp<NativeDisplayEventReceiver> receiver =
+            reinterpret_cast<NativeDisplayEventReceiver*>(receiverPtr);
+    gui::ParcelableVsyncEventData parcelableVsyncEventData;
+    status_t status = receiver->getLatestVsyncEventData(&parcelableVsyncEventData);
+    if (status) {
+        ALOGW("Failed to get latest vsync event data from surface flinger");
+        return NULL;
+    }
+    return createJavaVsyncEventData(env, parcelableVsyncEventData.vsync);
+}
 
 static const JNINativeMethod gMethods[] = {
-    /* name, signature, funcPtr */
-    { "nativeInit",
-            "(Ljava/lang/ref/WeakReference;Landroid/os/MessageQueue;II)J",
-            (void*)nativeInit },
-    { "nativeDispose",
-            "(J)V",
-            (void*)nativeDispose },
-    // @FastNative
-    { "nativeScheduleVsync", "(J)V",
-            (void*)nativeScheduleVsync }
-};
+        /* name, signature, funcPtr */
+        {"nativeInit", "(Ljava/lang/ref/WeakReference;Landroid/os/MessageQueue;II)J",
+         (void*)nativeInit},
+        {"nativeDispose", "(J)V", (void*)nativeDispose},
+        // @FastNative
+        {"nativeScheduleVsync", "(J)V", (void*)nativeScheduleVsync},
+        {"nativeGetLatestVsyncEventData", "(J)Landroid/view/DisplayEventReceiver$VsyncEventData;",
+         (void*)nativeGetLatestVsyncEventData}};
 
 int register_android_view_DisplayEventReceiver(JNIEnv* env) {
     int res = RegisterMethodsOrDie(env, "android/view/DisplayEventReceiver", gMethods,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 45b6c78..4f95588 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -471,11 +471,9 @@
         android:name="com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION" />
 
     <!-- Defined in RestrictionsManager -->
-    <protected-broadcast
-        android:name="android.intent.action.PERMISSION_RESPONSE_RECEIVED" />
-    <!-- Defined in RestrictionsManager -->
+    <protected-broadcast android:name="android.content.action.PERMISSION_RESPONSE_RECEIVED" />
+    <protected-broadcast android:name="android.content.action.REQUEST_PERMISSION" />
 
-    <protected-broadcast android:name="android.intent.action.REQUEST_PERMISSION" />
     <protected-broadcast android:name="android.nfc.handover.intent.action.HANDOVER_STARTED" />
     <protected-broadcast android:name="android.nfc.handover.intent.action.TRANSFER_DONE" />
     <protected-broadcast android:name="android.nfc.handover.intent.action.TRANSFER_PROGRESS" />
diff --git a/core/res/res/anim/activity_close_enter_legacy.xml b/core/res/res/anim/activity_close_enter_legacy.xml
deleted file mode 100644
index 9fa7c54..0000000
--- a/core/res/res/anim/activity_close_enter_legacy.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2009, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false">
-    <scale
-        android:fromXScale="1.1"
-        android:toXScale="1"
-        android:fromYScale="1.1"
-        android:toYScale="1"
-        android:pivotX="50%"
-        android:pivotY="50%"
-        android:fillEnabled="true"
-        android:fillBefore="true"
-        android:fillAfter="true"
-        android:interpolator="@interpolator/fast_out_extra_slow_in"
-        android:duration="400"/>
-</set>
\ No newline at end of file
diff --git a/core/res/res/anim/activity_close_exit_legacy.xml b/core/res/res/anim/activity_close_exit_legacy.xml
deleted file mode 100644
index 1599ae8..0000000
--- a/core/res/res/anim/activity_close_exit_legacy.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2009, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false"
-    android:zAdjustment="top">
-    <alpha
-        android:fromAlpha="1"
-        android:toAlpha="0.0"
-        android:fillEnabled="true"
-        android:fillBefore="true"
-        android:fillAfter="true"
-        android:interpolator="@interpolator/linear"
-        android:startOffset="33"
-        android:duration="50"/>
-    <scale
-        android:fromXScale="1"
-        android:toXScale="0.9"
-        android:fromYScale="1"
-        android:toYScale="0.9"
-        android:pivotX="50%"
-        android:pivotY="50%"
-        android:fillEnabled="true"
-        android:fillBefore="true"
-        android:fillAfter="true"
-        android:interpolator="@interpolator/fast_out_extra_slow_in"
-        android:duration="400"/>
-</set>
diff --git a/core/res/res/anim/activity_open_enter_legacy.xml b/core/res/res/anim/activity_open_enter_legacy.xml
deleted file mode 100644
index 38d3e8ed..0000000
--- a/core/res/res/anim/activity_open_enter_legacy.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-/*
-** Copyright 2009, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false">
-    <alpha
-        android:fromAlpha="0"
-        android:toAlpha="1.0"
-        android:fillEnabled="true"
-        android:fillBefore="true"
-        android:fillAfter="true"
-        android:interpolator="@interpolator/linear"
-        android:startOffset="50"
-        android:duration="50"/>
-    <scale
-        android:fromXScale="0.85"
-        android:toXScale="1"
-        android:fromYScale="0.85"
-        android:toYScale="1"
-        android:pivotX="50%"
-        android:pivotY="50%"
-        android:fillEnabled="true"
-        android:fillBefore="true"
-        android:fillAfter="true"
-        android:interpolator="@interpolator/fast_out_extra_slow_in"
-        android:duration="400"/>
-</set>
diff --git a/core/res/res/anim/activity_open_exit_legacy.xml b/core/res/res/anim/activity_open_exit_legacy.xml
deleted file mode 100644
index 3865d21..0000000
--- a/core/res/res/anim/activity_open_exit_legacy.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-/*
-** Copyright 2009, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false">
-
-    <!-- Fade out, over a black surface, which simulates a black scrim -->
-    <alpha
-        android:fromAlpha="1"
-        android:toAlpha="0.4"
-        android:fillEnabled="true"
-        android:fillBefore="true"
-        android:fillAfter="true"
-        android:interpolator="@interpolator/linear"
-        android:startOffset="83"
-        android:duration="167"/>
-
-    <scale
-        android:fromXScale="1"
-        android:toXScale="1.05"
-        android:fromYScale="1"
-        android:toYScale="1.05"
-        android:pivotX="50%"
-        android:pivotY="50%"
-        android:fillEnabled="true"
-        android:fillBefore="true"
-        android:fillAfter="true"
-        android:interpolator="@interpolator/fast_out_extra_slow_in"
-        android:duration="400"/>
-</set>
\ No newline at end of file
diff --git a/core/res/res/layout/app_language_picker_system_default.xml b/core/res/res/layout/app_language_picker_system_default.xml
new file mode 100644
index 0000000..d9883bc
--- /dev/null
+++ b/core/res/res/layout/app_language_picker_system_default.xml
@@ -0,0 +1,42 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:orientation="vertical"
+    android:paddingTop="8dp">
+    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:id="@+id/locale"
+              android:gravity="center_vertical"
+              android:textAppearance="?android:attr/textAppearanceListItem"
+              android:textDirection="locale"
+              android:layoutDirection="locale"
+              android:layout_weight="1" />
+
+    <TextView
+        android:id="@+id/system_locale_subtitle"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+        android:textColor="?android:attr/textColorSecondary"
+        android:layout_weight="1" />
+</LinearLayout>
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 81a79c5..a7f2aa7 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -49,6 +49,8 @@
         android:layout_marginStart="@dimen/notification_icon_circle_start"
         android:background="@drawable/notification_icon_circle"
         android:padding="@dimen/notification_icon_circle_padding"
+        android:maxDrawableWidth="@dimen/notification_icon_circle_size"
+        android:maxDrawableHeight="@dimen/notification_icon_circle_size"
         />
 
     <!-- extends ViewGroup -->
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index c6983ae..fd787f6 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -45,6 +45,8 @@
         android:layout_marginStart="@dimen/notification_icon_circle_start"
         android:background="@drawable/notification_icon_circle"
         android:padding="@dimen/notification_icon_circle_padding"
+        android:maxDrawableWidth="@dimen/notification_icon_circle_size"
+        android:maxDrawableHeight="@dimen/notification_icon_circle_size"
         />
 
     <FrameLayout
@@ -136,7 +138,7 @@
 
         </LinearLayout>
 
-        <ImageView
+        <com.android.internal.widget.CachingIconView
             android:id="@+id/right_icon"
             android:layout_width="@dimen/notification_right_icon_size"
             android:layout_height="@dimen/notification_right_icon_size"
@@ -148,6 +150,8 @@
             android:clipToOutline="true"
             android:importantForAccessibility="no"
             android:scaleType="centerCrop"
+            android:maxDrawableWidth="@dimen/notification_right_icon_size"
+            android:maxDrawableHeight="@dimen/notification_right_icon_size"
             />
 
         <FrameLayout
diff --git a/core/res/res/layout/notification_template_right_icon.xml b/core/res/res/layout/notification_template_right_icon.xml
index f163ed5..8b3b795 100644
--- a/core/res/res/layout/notification_template_right_icon.xml
+++ b/core/res/res/layout/notification_template_right_icon.xml
@@ -13,7 +13,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<ImageView
+<com.android.internal.widget.CachingIconView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/right_icon"
     android:layout_width="@dimen/notification_right_icon_size"
@@ -25,4 +25,6 @@
     android:clipToOutline="true"
     android:importantForAccessibility="no"
     android:scaleType="centerCrop"
+    android:maxDrawableWidth="@dimen/notification_right_icon_size"
+    android:maxDrawableHeight="@dimen/notification_right_icon_size"
     />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2107f65..155e903 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2322,7 +2322,9 @@
         <attr name="windowSplashScreenAnimatedIcon" format="reference"/>
         <!-- The duration, in milliseconds, of the window splash screen icon animation duration
              when playing the splash screen starting window. The maximum animation duration should
-             be limited below 1000ms. -->
+             be limited below 1000ms.
+              @deprecated Not used by framework starting from API level 33. The system estimates the
+               duration of the vector animation automatically. -->
         <attr name="windowSplashScreenAnimationDuration" format="integer"/>
 
         <!-- Place an drawable image in the bottom of the starting window, it can be used to
@@ -9807,4 +9809,12 @@
         of the supported locale. {@link android.app.LocaleConfig} -->
         <attr name="name" />
     </declare-styleable>
+
+    <!-- @hide -->
+    <declare-styleable name="CachingIconView">
+        <!-- Maximum width of displayed drawable. Drawables exceeding this size will be downsampled. -->
+        <attr name="maxDrawableWidth" format="dimension"/>
+        <!-- Maximum width of height drawable. Drawables exceeding this size will be downsampled. -->
+        <attr name="maxDrawableHeight" format="dimension"/>
+    </declare-styleable>
     </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3707cb7..94eb45c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2931,8 +2931,8 @@
     <!-- Apps that are authorized to access shared accounts, overridden by product overlays -->
     <string name="config_appsAuthorizedForSharedAccounts" translatable="false">;com.android.settings;</string>
 
-    <!-- Settings intelligence package name -->
-    <string name="config_settingsIntelligencePackageName" translatable="false">
+    <!-- System settings intelligence package name -->
+    <string name="config_systemSettingsIntelligence" translatable="false">
         com.android.settings.intelligence
     </string>
 
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index b985528..ffcadd0 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -147,6 +147,10 @@
     <public name="supportsInlineSuggestionsWithTouchExploration" />
     <public name="lineBreakStyle" />
     <public name="lineBreakWordStyle" />
+    <!-- @hide -->
+    <public name="maxDrawableWidth" />
+    <!-- @hide -->
+    <public name="maxDrawableHeight" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01de0000">
@@ -176,6 +180,8 @@
     <public name="config_defaultAutomotiveNavigation" />
     <!-- @hide @SystemApi -->
     <public name="safety_protection_display_text" />
+    <!-- @hide @SystemApi -->
+    <public name="config_systemSettingsIntelligence" />
   </staging-public-group>
 
   <staging-public-group type="dimen" first-id="0x01db0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6480734..4a2c911 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6293,4 +6293,7 @@
     <!-- Strings for VirtualDeviceManager -->
     <!-- Error message indicating the camera cannot be accessed when running on a virtual device. [CHAR LIMIT=NONE] -->
     <string name="vdm_camera_access_denied">Cannot access camera from this device</string>
+
+    <!-- Title for preference of the system default locale. [CHAR LIMIT=50]-->
+    <string name="system_locale_title">System language</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 37786e4..845de8a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1713,10 +1713,6 @@
   <java-symbol type="anim" name="activity_open_exit" />
   <java-symbol type="anim" name="activity_close_enter" />
   <java-symbol type="anim" name="activity_close_exit" />
-  <java-symbol type="anim" name="activity_open_enter_legacy" />
-  <java-symbol type="anim" name="activity_open_exit_legacy" />
-  <java-symbol type="anim" name="activity_close_enter_legacy" />
-  <java-symbol type="anim" name="activity_close_exit_legacy" />
   <java-symbol type="anim" name="task_fragment_close_enter" />
   <java-symbol type="anim" name="task_fragment_close_exit" />
   <java-symbol type="anim" name="task_fragment_open_enter" />
@@ -4771,4 +4767,7 @@
   <java-symbol type="drawable" name="ic_swap_horiz" />
   <java-symbol type="bool" name="config_notificationForceUserSetOnUpgrade" />
 
+  <java-symbol type="string" name="system_locale_title" />
+  <java-symbol type="layout" name="app_language_picker_system_default" />
+  <java-symbol type="id" name="system_locale_subtitle" />
 </resources>
diff --git a/core/tests/coretests/res/drawable/big_a.png b/core/tests/coretests/res/drawable/big_a.png
new file mode 100644
index 0000000..dc059a3
--- /dev/null
+++ b/core/tests/coretests/res/drawable/big_a.png
Binary files differ
diff --git a/core/tests/coretests/res/layout/caching_icon_view_test_max_size.xml b/core/tests/coretests/res/layout/caching_icon_view_test_max_size.xml
new file mode 100644
index 0000000..9a03446
--- /dev/null
+++ b/core/tests/coretests/res/layout/caching_icon_view_test_max_size.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<com.android.internal.widget.CachingIconView
+          xmlns:android="http://schemas.android.com/apk/res/android"
+          android:id="@+id/caching_icon_view"
+          android:layout_width="120dp"
+          android:layout_height="120dp"
+          android:maxDrawableWidth="80dp"
+          android:maxDrawableHeight="80dp" />
diff --git a/core/tests/coretests/res/layout/caching_icon_view_test_no_max_size.xml b/core/tests/coretests/res/layout/caching_icon_view_test_no_max_size.xml
new file mode 100644
index 0000000..a213a97
--- /dev/null
+++ b/core/tests/coretests/res/layout/caching_icon_view_test_no_max_size.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+
+<com.android.internal.widget.CachingIconView
+          xmlns:android="http://schemas.android.com/apk/res/android"
+          android:id="@+id/caching_icon_view"
+          android:layout_width="120dp"
+          android:layout_height="120dp" />
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryTest.java
index cd61011..863aac35 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryTest.java
@@ -45,7 +45,13 @@
 public class BatteryStatsHistoryTest {
     private static final String TAG = "BatteryStatsHistoryTest";
     private static final int MAX_HISTORY_FILES = 32;
-    private final BatteryStatsImpl mBatteryStatsImpl = new MockBatteryStatsImpl();
+    private static final int MAX_HISTORY_BUFFER_KB = 128;
+
+    // Initializing max history files and buffer to the default values of non-low-ram device
+    // to maintain consistency in the tests
+    private final BatteryStatsImpl mBatteryStatsImpl = new MockBatteryStatsImpl()
+            .setMaxHistoryFiles(MAX_HISTORY_FILES)
+            .setMaxHistoryBuffer(MAX_HISTORY_BUFFER_KB * 1024);
     private final Parcel mHistoryBuffer = Parcel.obtain();
     private File mSystemDir;
     private File mHistoryDir;
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index 1bb41a8..00154a3 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -26,6 +26,7 @@
 import android.os.Handler;
 import android.os.Looper;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
@@ -193,6 +194,18 @@
         return this;
     }
 
+    @GuardedBy("this")
+    public MockBatteryStatsImpl setMaxHistoryFiles(int maxHistoryFiles) {
+        mConstants.MAX_HISTORY_FILES = maxHistoryFiles;
+        return this;
+    }
+
+    @GuardedBy("this")
+    public MockBatteryStatsImpl setMaxHistoryBuffer(int maxHistoryBuffer) {
+        mConstants.MAX_HISTORY_BUFFER = maxHistoryBuffer;
+        return this;
+    }
+
     public int getAndClearExternalStatsSyncFlags() {
         final int flags = mExternalStatsSync.flags;
         mExternalStatsSync.flags = 0;
diff --git a/core/tests/coretests/src/com/android/internal/widget/CachingIconViewTest.java b/core/tests/coretests/src/com/android/internal/widget/CachingIconViewTest.java
new file mode 100644
index 0000000..aa96203
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/CachingIconViewTest.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.widget;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.graphics.drawable.InsetDrawable;
+import android.net.Uri;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class CachingIconViewTest {
+
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    @Test
+    public void invalidIcon_skipsLoadSuccessfully() {
+        CachingIconView view = (CachingIconView) LayoutInflater.from(mContext).inflate(
+                R.layout.caching_icon_view_test_max_size, null);
+        view.setImageIcon(Icon.createWithResource(mContext, 0x85743222));
+        Drawable drawable = view.getDrawable();
+        assertThat(drawable).isNull();
+    }
+
+    @Test
+    public void customDrawable_setImageIcon_skipsResizeSuccessfully() {
+        CachingIconView view = (CachingIconView) LayoutInflater.from(mContext).inflate(
+                R.layout.caching_icon_view_test_max_size, null);
+        view.setImageIcon(Icon.createWithResource(mContext, R.drawable.custom_drawable));
+        Drawable drawable = view.getDrawable();
+        assertThat(drawable).isInstanceOf(InsetDrawable.class);
+    }
+
+    @Test
+    public void customDrawable_setImageIconAsync_skipsResizeSuccessfully() {
+        CachingIconView view = (CachingIconView) LayoutInflater.from(mContext).inflate(
+                R.layout.caching_icon_view_test_max_size, null);
+        view.setImageIconAsync(Icon.createWithResource(mContext, R.drawable.custom_drawable)).run();
+        Drawable drawable = view.getDrawable();
+        assertThat(drawable).isInstanceOf(InsetDrawable.class);
+    }
+
+    @Test
+    public void customDrawable_setImageResource_skipsResizeSuccessfully() {
+        CachingIconView view = (CachingIconView) LayoutInflater.from(mContext).inflate(
+                R.layout.caching_icon_view_test_max_size, null);
+        view.setImageResource(R.drawable.custom_drawable);
+        Drawable drawable = view.getDrawable();
+        assertThat(drawable).isInstanceOf(InsetDrawable.class);
+    }
+
+    @Test
+    public void customDrawable_setImageResourceAsync_skipsResizeSuccessfully() {
+        CachingIconView view = (CachingIconView) LayoutInflater.from(mContext).inflate(
+                R.layout.caching_icon_view_test_max_size, null);
+        view.setImageResourceAsync(R.drawable.custom_drawable).run();
+        Drawable drawable = view.getDrawable();
+        assertThat(drawable).isInstanceOf(InsetDrawable.class);
+    }
+
+    @Test
+    public void customDrawable_setImageUri_skipsResizeSuccessfully() {
+        CachingIconView view = (CachingIconView) LayoutInflater.from(mContext).inflate(
+                R.layout.caching_icon_view_test_max_size, null);
+        view.setImageURI(Uri.parse(
+                "android.resource://com.android.frameworks.coretests/"
+                        + R.drawable.custom_drawable));
+        Drawable drawable = view.getDrawable();
+        assertThat(drawable).isInstanceOf(InsetDrawable.class);
+    }
+
+    @Test
+    public void customDrawable_setImageUriAsync_skipsResizeSuccessfully() {
+        CachingIconView view = (CachingIconView) LayoutInflater.from(mContext).inflate(
+                R.layout.caching_icon_view_test_max_size, null);
+        view.setImageURIAsync(Uri.parse(
+                "android.resource://com.android.frameworks.coretests/"
+                        + R.drawable.custom_drawable)).run();
+        Drawable drawable = view.getDrawable();
+        assertThat(drawable).isInstanceOf(InsetDrawable.class);
+    }
+
+    @Test
+    public void maxDrawableDimensionsSet_setImageIcon_resizesImageIcon() {
+        CachingIconView view = (CachingIconView) LayoutInflater.from(mContext).inflate(
+                R.layout.caching_icon_view_test_max_size, null);
+        view.setImageIcon(Icon.createWithResource(mContext, R.drawable.big_a));
+
+        assertDrawableResized(view);
+    }
+
+    @Test
+    public void maxDrawableWithNoDimensionsSet_setImageIcon_doesNotResizeImageIcon() {
+        CachingIconView view = (CachingIconView) LayoutInflater.from(mContext).inflate(
+                R.layout.caching_icon_view_test_no_max_size, null);
+        view.setImageIcon(Icon.createWithResource(mContext, R.drawable.big_a));
+
+        assertDrawableNotResized(view);
+    }
+
+    @Test
+    public void maxDrawableDimensionsSet_setImageIconAsync_resizesImageIcon() {
+        CachingIconView view = (CachingIconView) LayoutInflater.from(mContext).inflate(
+                R.layout.caching_icon_view_test_max_size, null);
+        view.setImageIconAsync(Icon.createWithResource(mContext, R.drawable.big_a)).run();
+
+        assertDrawableResized(view);
+    }
+
+    @Test
+    public void maxDrawableWithNoDimensionsSet_setImageIconAsync_doesNotResizeImageIcon() {
+        CachingIconView view = (CachingIconView) LayoutInflater.from(mContext).inflate(
+                R.layout.caching_icon_view_test_no_max_size, null);
+        view.setImageIconAsync(Icon.createWithResource(mContext, R.drawable.big_a)).run();
+
+        assertDrawableNotResized(view);
+    }
+
+    @Test
+    public void maxDrawableDimensionsSet_setImageResource_resizesImageIcon() {
+        CachingIconView view = (CachingIconView) LayoutInflater.from(mContext).inflate(
+                R.layout.caching_icon_view_test_max_size, null);
+        view.setImageResource(R.drawable.big_a);
+
+        assertDrawableResized(view);
+    }
+
+    @Test
+    public void maxDrawableWithNoDimensionsSet_setImageResource_doesNotResizeImageIcon() {
+        CachingIconView view = (CachingIconView) LayoutInflater.from(mContext).inflate(
+                R.layout.caching_icon_view_test_no_max_size, null);
+        view.setImageResource(R.drawable.big_a);
+
+        assertDrawableNotResized(view);
+    }
+
+    @Test
+    public void maxDrawableDimensionsSet_setImageResourceAsync_resizesImageIcon() {
+        CachingIconView view = (CachingIconView) LayoutInflater.from(mContext).inflate(
+                R.layout.caching_icon_view_test_max_size, null);
+        view.setImageResourceAsync(R.drawable.big_a).run();
+
+        assertDrawableResized(view);
+    }
+
+    @Test
+    public void maxDrawableWithNoDimensionsSet_setImageResourceAsync_doesNotResizeImageIcon() {
+        CachingIconView view = (CachingIconView) LayoutInflater.from(mContext).inflate(
+                R.layout.caching_icon_view_test_no_max_size, null);
+        view.setImageResourceAsync(R.drawable.big_a).run();
+
+        assertDrawableNotResized(view);
+    }
+
+    @Test
+    public void maxDrawableDimensionsSet_setImageUri_resizesImageIcon() {
+        CachingIconView view = (CachingIconView) LayoutInflater.from(mContext).inflate(
+                R.layout.caching_icon_view_test_max_size, null);
+        view.setImageURI(Uri.parse(
+                "android.resource://com.android.frameworks.coretests/" + R.drawable.big_a));
+
+        assertDrawableResized(view);
+    }
+
+    @Test
+    public void maxDrawableWithNoDimensionsSet_setImageUri_doesNotResizeImageIcon() {
+        CachingIconView view = (CachingIconView) LayoutInflater.from(mContext).inflate(
+                R.layout.caching_icon_view_test_no_max_size, null);
+        view.setImageURI(Uri.parse(
+                "android.resource://com.android.frameworks.coretests/" + R.drawable.big_a));
+
+        assertDrawableNotResized(view);
+    }
+
+    @Test
+    public void maxDrawableDimensionsSet_setImageUriAsync_resizesImageIcon() {
+        CachingIconView view = (CachingIconView) LayoutInflater.from(mContext).inflate(
+                R.layout.caching_icon_view_test_max_size, null);
+        view.setImageURIAsync(Uri.parse(
+                "android.resource://com.android.frameworks.coretests/" + R.drawable.big_a)).run();
+
+        assertDrawableResized(view);
+    }
+
+    @Test
+    public void maxDrawableWithNoDimensionsSet_setImageUriAsync_doesNotResizeImageIcon() {
+        CachingIconView view = (CachingIconView) LayoutInflater.from(mContext).inflate(
+                R.layout.caching_icon_view_test_no_max_size, null);
+        view.setImageURIAsync(Uri.parse(
+                "android.resource://com.android.frameworks.coretests/" + R.drawable.big_a)).run();
+
+        assertDrawableNotResized(view);
+    }
+
+
+    private void assertDrawableResized(@Nullable CachingIconView view) {
+        assertThat(view).isNotNull();
+        int maxSize =
+                (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 80f,
+                        mContext.getResources().getDisplayMetrics());
+        assertThat(view.getMaxDrawableHeight()).isEqualTo(maxSize);
+        assertThat(view.getMaxDrawableWidth()).isEqualTo(maxSize);
+
+        Drawable drawable = view.getDrawable();
+        assertThat(drawable).isInstanceOf(BitmapDrawable.class);
+        BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
+        assertThat(bitmapDrawable.getBitmap().getWidth()).isLessThan(maxSize + 1);
+        assertThat(bitmapDrawable.getBitmap().getHeight()).isLessThan(maxSize + 1);
+    }
+
+    private void assertDrawableNotResized(@Nullable CachingIconView view) {
+        assertThat(view).isNotNull();
+        int maxSize =
+                (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 80f,
+                        mContext.getResources().getDisplayMetrics());
+        assertThat(view.getMaxDrawableHeight()).isEqualTo(-1);
+        assertThat(view.getMaxDrawableWidth()).isEqualTo(-1);
+
+        Drawable drawable = view.getDrawable();
+        assertThat(drawable).isInstanceOf(BitmapDrawable.class);
+        BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
+        assertThat(bitmapDrawable.getBitmap().getWidth()).isGreaterThan(maxSize);
+        assertThat(bitmapDrawable.getBitmap().getHeight()).isGreaterThan(maxSize);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java b/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
new file mode 100644
index 0000000..d8b3780
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.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.internal.widget;
+
+import android.content.Context;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.frameworks.coretests.R;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4ClassRunner.class)
+public class LocalImageResolverTest {
+
+    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+    @Test
+    public void resolveImage_invalidResource_returnsNull() throws IOException {
+        // We promise IOException in case of errors - but ImageDecode will throw NotFoundException
+        // in case of wrong resource. This test verifies that we throw IOException for API users.
+        Icon icon = Icon.createWithResource(mContext, 0x85849454);
+        Drawable d = LocalImageResolver.resolveImage(icon, mContext);
+        assertThat(d).isNull();
+    }
+
+    @Test
+    public void resolveImage_invalidIconUri_returnsNull() throws IOException {
+        // We promise IOException in case of errors - but ImageDecode will throw NotFoundException
+        // in case of wrong resource. This test verifies that we throw IOException for API users.
+        Icon icon = Icon.createWithContentUri(Uri.parse("bogus://uri"));
+        Drawable d = LocalImageResolver.resolveImage(icon, mContext);
+        assertThat(d).isNull();
+    }
+
+    @Test(expected = IOException.class)
+    public void resolveImage_invalidUri_throwsException() throws IOException {
+        Drawable d = LocalImageResolver.resolveImage(Uri.parse("bogus://uri"), mContext);
+        assertThat(d).isNull();
+    }
+
+    @Test
+    public void resolveImage_nonBitmapResourceIcon_fallsBackToNonResizingLoad() throws IOException {
+        Icon icon = Icon.createWithResource(mContext, R.drawable.blue);
+        Drawable d = LocalImageResolver.resolveImage(icon, mContext);
+        assertThat(d).isInstanceOf(ColorDrawable.class);
+    }
+
+    @Test(expected = IOException.class)
+    public void resolveImage_nonBitmapResourceUri_throwsIoException() throws IOException {
+        LocalImageResolver.resolveImage(
+                Uri.parse("android.resource://com.android.frameworks.coretests/" + R.drawable.blue),
+                mContext);
+    }
+
+    @Test
+    public void resolveImageWithResId_nonBitmapResourceIcon_returnsNull() {
+        Drawable d = LocalImageResolver.resolveImage(R.drawable.blue, mContext, 480, 480);
+        assertThat(d).isNull();
+    }
+
+    @Test
+    public void resolveImage_largeBitmapIcon_defaultSize_resizeToDefaultSize() throws
+            IOException {
+        Icon icon = Icon.createWithBitmap(
+                BitmapFactory.decodeResource(mContext.getResources(), R.drawable.big_a));
+        Drawable d = LocalImageResolver.resolveImage(icon, mContext);
+
+        assertThat(d).isInstanceOf(BitmapDrawable.class);
+        BitmapDrawable bd = (BitmapDrawable) d;
+        // No isLessOrEqualThan sadly.
+        assertThat(bd.getBitmap().getWidth()).isLessThan(
+                LocalImageResolver.DEFAULT_MAX_SAFE_ICON_SIZE_PX + 1);
+        assertThat(bd.getBitmap().getHeight()).isLessThan(
+                LocalImageResolver.DEFAULT_MAX_SAFE_ICON_SIZE_PX + 1);
+    }
+
+    @Test
+    public void resolveImage_largeAdaptiveBitmapIcon_defaultSize_resizeToDefaultSize() throws
+            IOException {
+        Icon icon = Icon.createWithAdaptiveBitmap(
+                BitmapFactory.decodeResource(mContext.getResources(), R.drawable.big_a));
+        Drawable d = LocalImageResolver.resolveImage(icon, mContext);
+
+        assertThat(d).isInstanceOf(AdaptiveIconDrawable.class);
+        BitmapDrawable bd = (BitmapDrawable) ((AdaptiveIconDrawable) d).getForeground();
+        // No isLessOrEqualThan sadly.
+        assertThat(bd.getBitmap().getWidth()).isLessThan(
+                LocalImageResolver.DEFAULT_MAX_SAFE_ICON_SIZE_PX + 1);
+        assertThat(bd.getBitmap().getHeight()).isLessThan(
+                LocalImageResolver.DEFAULT_MAX_SAFE_ICON_SIZE_PX + 1);
+    }
+
+    @Test
+    public void resolveImage_largeResourceIcon_defaultSize_resizeToDefaultSize() throws
+            IOException {
+        Icon icon = Icon.createWithResource(mContext, R.drawable.big_a);
+        Drawable d = LocalImageResolver.resolveImage(icon, mContext);
+
+        assertThat(d).isInstanceOf(BitmapDrawable.class);
+        BitmapDrawable bd = (BitmapDrawable) d;
+        // No isLessOrEqualThan sadly.
+        assertThat(bd.getBitmap().getWidth()).isLessThan(
+                LocalImageResolver.DEFAULT_MAX_SAFE_ICON_SIZE_PX + 1);
+        assertThat(bd.getBitmap().getHeight()).isLessThan(
+                LocalImageResolver.DEFAULT_MAX_SAFE_ICON_SIZE_PX + 1);
+    }
+
+    @Test
+    public void resolveImage_largeResourceIcon_passedSize_resizeToDefinedSize() {
+        Icon icon = Icon.createWithResource(mContext, R.drawable.big_a);
+        Drawable d = LocalImageResolver.resolveImage(icon, mContext, 100, 50);
+
+        assertThat(d).isInstanceOf(BitmapDrawable.class);
+        BitmapDrawable bd = (BitmapDrawable) d;
+        assertThat(bd.getBitmap().getWidth()).isLessThan(101);
+        assertThat(bd.getBitmap().getHeight()).isLessThan(51);
+    }
+
+    @Test
+    public void resolveImage_largeBitmapIcon_passedSize_resizeToDefinedSize() {
+        Icon icon = Icon.createWithBitmap(
+                BitmapFactory.decodeResource(mContext.getResources(), R.drawable.big_a));
+        Drawable d = LocalImageResolver.resolveImage(icon, mContext, 100, 50);
+
+        assertThat(d).isInstanceOf(BitmapDrawable.class);
+        BitmapDrawable bd = (BitmapDrawable) d;
+        assertThat(bd.getBitmap().getWidth()).isLessThan(101);
+        assertThat(bd.getBitmap().getHeight()).isLessThan(51);
+    }
+
+    @Test
+    public void resolveImage_largeAdaptiveBitmapIcon_passedSize_resizeToDefinedSize() {
+        Icon icon = Icon.createWithAdaptiveBitmap(
+                BitmapFactory.decodeResource(mContext.getResources(), R.drawable.big_a));
+        Drawable d = LocalImageResolver.resolveImage(icon, mContext, 100, 50);
+
+        assertThat(d).isInstanceOf(AdaptiveIconDrawable.class);
+        BitmapDrawable bd = (BitmapDrawable) ((AdaptiveIconDrawable) d).getForeground();
+        assertThat(bd.getBitmap().getWidth()).isLessThan(101);
+        assertThat(bd.getBitmap().getHeight()).isLessThan(51);
+    }
+
+
+    @Test
+    public void resolveImage_smallResourceIcon_defaultSize_untouched() throws IOException {
+        Icon icon = Icon.createWithResource(mContext, R.drawable.test32x24);
+        Drawable d = LocalImageResolver.resolveImage(icon, mContext);
+
+        assertThat(d).isInstanceOf(BitmapDrawable.class);
+        BitmapDrawable bd = (BitmapDrawable) d;
+        assertThat(bd.getBitmap().getWidth()).isEqualTo(32);
+        assertThat(bd.getBitmap().getHeight()).isEqualTo(24);
+    }
+
+    @Test
+    public void resolveImage_smallBitmapIcon_defaultSize_untouched() throws IOException {
+        Icon icon = Icon.createWithBitmap(
+                BitmapFactory.decodeResource(mContext.getResources(), R.drawable.test32x24));
+        final int originalWidth = icon.getBitmap().getWidth();
+        final int originalHeight = icon.getBitmap().getHeight();
+
+        Drawable d = LocalImageResolver.resolveImage(icon, mContext);
+
+        assertThat(d).isInstanceOf(BitmapDrawable.class);
+        BitmapDrawable bd = (BitmapDrawable) d;
+        assertThat(bd.getBitmap().getWidth()).isEqualTo(originalWidth);
+        assertThat(bd.getBitmap().getHeight()).isEqualTo(originalHeight);
+    }
+
+    @Test
+    public void resolveImage_smallAdaptiveBitmapIcon_defaultSize_untouched() throws IOException {
+        Icon icon = Icon.createWithAdaptiveBitmap(
+                BitmapFactory.decodeResource(mContext.getResources(), R.drawable.test32x24));
+        final int originalWidth = icon.getBitmap().getWidth();
+        final int originalHeight = icon.getBitmap().getHeight();
+
+        Drawable d = LocalImageResolver.resolveImage(icon, mContext);
+        assertThat(d).isInstanceOf(AdaptiveIconDrawable.class);
+        BitmapDrawable bd = (BitmapDrawable) ((AdaptiveIconDrawable) d).getForeground();
+        assertThat(bd.getBitmap().getWidth()).isEqualTo(originalWidth);
+        assertThat(bd.getBitmap().getHeight()).isEqualTo(originalHeight);
+
+    }
+}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 4449c48..99cb40a 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -19,6 +19,12 @@
       "group": "WM_DEBUG_LOCKTASK",
       "at": "com\/android\/server\/wm\/LockTaskController.java"
     },
+    "-2111539867": {
+      "message": "remove IME snapshot, caller=%s",
+      "level": "INFO",
+      "group": "WM_DEBUG_IME",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "-2109936758": {
       "message": "removeAppToken make exiting: %s",
       "level": "VERBOSE",
@@ -61,6 +67,12 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/Task.java"
     },
+    "-2052051397": {
+      "message": "Clear animatingExit: reason=destroySurface win=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "-2049725903": {
       "message": "Task back pressed on root taskId=%d",
       "level": "VERBOSE",
@@ -127,6 +139,12 @@
       "group": "WM_DEBUG_SYNC_ENGINE",
       "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
     },
+    "-1969928125": {
+      "message": "Animation start for %s, anim=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/SurfaceAnimator.java"
+    },
     "-1963461591": {
       "message": "Removing %s from %s",
       "level": "VERBOSE",
@@ -163,6 +181,12 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-1933723759": {
+      "message": "Clear animatingExit: reason=relayoutVisibleWindow win=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "-1924376693": {
       "message": " Setting Ready-group to %b. group=%s from %s",
       "level": "VERBOSE",
@@ -265,6 +289,12 @@
       "group": "WM_DEBUG_RESIZE",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "-1814361639": {
+      "message": "Set IME snapshot position: (%d, %d)",
+      "level": "INFO",
+      "group": "WM_DEBUG_IME",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "-1810446914": {
       "message": "Trying to update display configuration for system\/invalid process.",
       "level": "WARN",
@@ -307,6 +337,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "-1777010776": {
+      "message": "create IME snapshot for %s, buff width=%s, height=%s",
+      "level": "INFO",
+      "group": "WM_DEBUG_IME",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "-1770075711": {
       "message": "Adding window client %s that is dead, aborting.",
       "level": "WARN",
@@ -613,6 +649,12 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-1471518109": {
+      "message": "Set animatingExit: reason=onAppVisibilityChanged win=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "-1468740466": {
       "message": "Moving to PAUSED: %s (starting in paused state)",
       "level": "VERBOSE",
@@ -751,6 +793,12 @@
       "group": "WM_DEBUG_CONTENT_RECORDING",
       "at": "com\/android\/server\/wm\/ContentRecorder.java"
     },
+    "-1318478129": {
+      "message": "applyAnimation: win=%s anim=%d attr=0x%x a=%s transit=%d type=%d isEntrance=%b Callers %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
+    },
     "-1311436264": {
       "message": "Unregister task fragment organizer=%s uid=%d pid=%d",
       "level": "VERBOSE",
@@ -781,12 +829,24 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/ActivityStarter.java"
     },
+    "-1303628829": {
+      "message": "**** STARTING EXIT",
+      "level": "INFO",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/DisplayPolicy.java"
+    },
     "-1292329638": {
       "message": "Added starting %s: startingWindow=%s startingView=%s",
       "level": "VERBOSE",
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-1288007399": {
+      "message": "performShowLocked: mDrawState=HAS_DRAWN in %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "-1270731689": {
       "message": "Attempted to set replacing window on app token with no content %s",
       "level": "WARN",
@@ -835,6 +895,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
     },
+    "-1209252064": {
+      "message": "Clear animatingExit: reason=clearAnimatingFlags win=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "-1207757583": {
       "message": "startAnimation(): Notify animation start: %s",
       "level": "DEBUG",
@@ -1219,6 +1285,12 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-799003045": {
+      "message": "Set animatingExit: reason=remove\/replaceWindow win=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "-784959154": {
       "message": "Attempted to add private presentation window to a non-private display.  Aborting.",
       "level": "WARN",
@@ -1375,6 +1447,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
+    "-658964693": {
+      "message": "onWindowAnimationFinished, wc=%s, type=%s, imeSnapshot=%s, target=%s",
+      "level": "INFO",
+      "group": "WM_DEBUG_IME",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "-655104359": {
       "message": "Frontmost changed immersion: %s",
       "level": "DEBUG",
@@ -1675,6 +1753,12 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
+    "-347866078": {
+      "message": "Setting move animation on %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "-344488673": {
       "message": "Finishing drawing window %s: mDrawState=%s",
       "level": "VERBOSE",
@@ -1699,6 +1783,12 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "-319689203": {
+      "message": "Reparenting to original parent: %s for %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/SurfaceAnimator.java"
+    },
     "-317761482": {
       "message": "Create sleep token: tag=%s, displayId=%d",
       "level": "DEBUG",
@@ -1807,6 +1897,18 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "-208664771": {
+      "message": "Reparenting to leash for %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/SurfaceAnimator.java"
+    },
+    "-203358733": {
+      "message": "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
+    },
     "-198463978": {
       "message": "updateRotationUnchecked: alwaysSendConfiguration=%b forceRelayout=%b",
       "level": "VERBOSE",
@@ -1903,6 +2005,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
+    "-91393839": {
+      "message": "Set animatingExit: reason=remove\/applyAnimation win=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "-90559682": {
       "message": "Config is skipping already pausing %s",
       "level": "VERBOSE",
@@ -1927,6 +2035,12 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/Session.java"
     },
+    "-81121442": {
+      "message": "ImeContainer just became organized but it doesn't have a parent or the parent doesn't have a surface control. mSurfaceControl=%s imeParentSurfaceControl=%s",
+      "level": "ERROR",
+      "group": "WM_DEBUG_IME",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "-80004683": {
       "message": "Resume failed; resetting state to %s: %s",
       "level": "VERBOSE",
@@ -1939,6 +2053,12 @@
       "group": "WM_DEBUG_WINDOW_ORGANIZER",
       "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
     },
+    "-57750640": {
+      "message": "show IME snapshot, ime target=%s, callers=%s",
+      "level": "INFO",
+      "group": "WM_DEBUG_IME",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "-55185509": {
       "message": "setFocusedTask: taskId=%d touchedActivity=%s",
       "level": "DEBUG",
@@ -1957,6 +2077,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
       "at": "com\/android\/server\/wm\/WindowContainer.java"
     },
+    "-32102932": {
+      "message": "Error sending initial configuration change to WindowContainer overlay",
+      "level": "ERROR",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowContainer.java"
+    },
     "-23020844": {
       "message": "Back: Reset surfaces",
       "level": "DEBUG",
@@ -2191,6 +2317,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/Task.java"
     },
+    "215077284": {
+      "message": "Animation start delayed for %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/SurfaceAnimator.java"
+    },
     "221540118": {
       "message": "mUserActivityTimeout set to %d",
       "level": "DEBUG",
@@ -2281,6 +2413,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
       "at": "com\/android\/server\/wm\/AppTransition.java"
     },
+    "283489582": {
+      "message": "Clear animatingExit: reason=exitAnimationDone win=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "288485303": {
       "message": "Attempted to set remove mode to a display that does not exist: %d",
       "level": "WARN",
@@ -2353,6 +2491,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/TaskFragment.java"
     },
+    "341360111": {
+      "message": "selectAnimation in %s: transit=%d",
+      "level": "INFO",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/DisplayPolicy.java"
+    },
     "342460966": {
       "message": "DRAG %s: pos=(%d,%d)",
       "level": "INFO",
@@ -2419,6 +2563,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/TaskFragment.java"
     },
+    "385595355": {
+      "message": "Starting animation on %s: type=%d, anim=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowContainer.java"
+    },
     "397105698": {
       "message": "grantEmbeddedWindowFocus remove request for win=%s dropped since no candidate was found",
       "level": "VERBOSE",
@@ -2431,6 +2581,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "397862437": {
+      "message": "Cancelling animation restarting=%b for %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/SurfaceAnimator.java"
+    },
     "399841913": {
       "message": "SURFACE RECOVER DESTROY: %s",
       "level": "INFO",
@@ -2767,12 +2923,6 @@
       "group": "WM_DEBUG_WALLPAPER",
       "at": "com\/android\/server\/wm\/WallpaperWindowToken.java"
     },
-    "736003885": {
-      "message": "Unable to retrieve the task token to start recording for display %d",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_CONTENT_RECORDING",
-      "at": "com\/android\/server\/wm\/ContentRecorder.java"
-    },
     "736692676": {
       "message": "Config is relaunching %s",
       "level": "VERBOSE",
@@ -2803,6 +2953,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
+    "769218938": {
+      "message": "Loaded animation %s for %s, duration: %d, stack=%s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowContainer.java"
+    },
     "778774915": {
       "message": "Unable to record task since feature is disabled %d",
       "level": "VERBOSE",
@@ -2959,6 +3115,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "975275467": {
+      "message": "Set animatingExit: reason=remove\/isAnimating win=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "979347997": {
       "message": "Launch on display check: disallow activity embedding without permission.",
       "level": "DEBUG",
@@ -3127,6 +3289,12 @@
       "group": "WM_DEBUG_WINDOW_ORGANIZER",
       "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
     },
+    "1164325516": {
+      "message": "onExitAnimationDone in %s: exiting=%b remove=%b selfAnimating=%b anim=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "1166381079": {
       "message": "Execute app transition: %s, displayId: %d Callers=%s",
       "level": "WARN",
@@ -3139,6 +3307,12 @@
       "group": "WM_DEBUG_BACK_PREVIEW",
       "at": "com\/android\/server\/wm\/BackNavigationController.java"
     },
+    "1175495463": {
+      "message": "ImeContainer just became organized. Reparenting under parent. imeParentSurfaceControl=%s",
+      "level": "INFO",
+      "group": "WM_DEBUG_IME",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "1178653181": {
       "message": "Old wallpaper still the target.",
       "level": "VERBOSE",
@@ -3265,6 +3439,12 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/TransitionController.java"
     },
+    "1335791109": {
+      "message": "createSurface %s: mDrawState=DRAW_PENDING",
+      "level": "INFO",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
+    },
     "1337596507": {
       "message": "Sending to proc %s new compat %s",
       "level": "VERBOSE",
@@ -3679,6 +3859,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1810209625": {
+      "message": "Animation done in %s: exiting=%b, reportedVisible=%b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
+    },
     "1822314934": {
       "message": "Expected target rootTask=%s to restored behind rootTask=%s but it is behind rootTask=%s",
       "level": "WARN",
@@ -3757,6 +3943,12 @@
       "group": "WM_DEBUG_WINDOW_ORGANIZER",
       "at": "com\/android\/server\/wm\/DisplayAreaPolicyBuilder.java"
     },
+    "1878927091": {
+      "message": "prepareSurface: No changes in animation for %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
+    },
     "1891501279": {
       "message": "cancelAnimation(): reason=%s",
       "level": "DEBUG",
@@ -3859,6 +4051,12 @@
       "group": "WM_DEBUG_FOCUS_LIGHT",
       "at": "com\/android\/server\/wm\/InputMonitor.java"
     },
+    "2010476671": {
+      "message": "Animation done in %s: reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "2018454757": {
       "message": "WS.removeImmediately: %s Already removed...",
       "level": "VERBOSE",
@@ -3871,6 +4069,12 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "2019765997": {
+      "message": "selectRotationAnimation topFullscreen=%s rotationAnimation=%d forceJumpcut=%b",
+      "level": "INFO",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/DisplayRotation.java"
+    },
     "2022422429": {
       "message": "createAnimationAdapter(): container=%s",
       "level": "DEBUG",
@@ -3925,6 +4129,12 @@
       "group": "WM_DEBUG_WINDOW_INSETS",
       "at": "com\/android\/server\/wm\/InsetsSourceProvider.java"
     },
+    "2075693141": {
+      "message": "Set animatingExit: reason=startExitingAnimation\/%s win=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ANIM",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "2083556954": {
       "message": "Set mOrientationChanging of %s",
       "level": "VERBOSE",
@@ -3984,6 +4194,9 @@
     "WM_DEBUG_ADD_REMOVE": {
       "tag": "WindowManager"
     },
+    "WM_DEBUG_ANIM": {
+      "tag": "WindowManager"
+    },
     "WM_DEBUG_APP_TRANSITIONS": {
       "tag": "WindowManager"
     },
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 13d12b35..677c1c7 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
@@ -2802,7 +2802,14 @@
             mExpandedViewContainer.setVisibility(View.INVISIBLE);
             mExpandedViewContainer.setAlpha(0f);
             mExpandedViewContainer.addView(bev);
-            bev.setManageClickListener((view) -> showManageMenu(!mShowingManage));
+
+            postDelayed(() -> {
+                // Set the Manage button click handler from postDelayed. This appears to resolve
+                // a race condition with adding the BubbleExpandedView view to the expanded view
+                // container. Due to the race condition the click handler sometimes is not set up
+                // correctly and is never called.
+                bev.setManageClickListener((view) -> showManageMenu(true /* show */));
+            }, 0);
 
             if (!mIsExpansionAnimating) {
                 mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
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 daba774..d05a4df 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
@@ -513,10 +513,6 @@
     /** Apply recorded task layout to the {@link WindowContainerTransaction}. */
     public void applyTaskChanges(WindowContainerTransaction wct,
             ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
-        if (mImePositionProcessor.applyTaskLayoutForIme(wct, task1.token, task2.token)) {
-            return;
-        }
-
         if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {
             wct.setBounds(task1.token, mBounds1);
             wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1));
@@ -758,6 +754,7 @@
 
         private final int mDisplayId;
 
+        private boolean mHasImeFocus;
         private boolean mImeShown;
         private int mYOffsetForIme;
         private float mDimValue1;
@@ -780,25 +777,32 @@
         @Override
         public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
                 boolean showing, boolean isFloating, SurfaceControl.Transaction t) {
-            if (displayId != mDisplayId) return 0;
+            if (displayId != mDisplayId || !mInitialized) {
+                return 0;
+            }
+
             final int imeTargetPosition = getImeTargetPosition();
-            if (!mInitialized || imeTargetPosition == SPLIT_POSITION_UNDEFINED) return 0;
+            mHasImeFocus = imeTargetPosition != SPLIT_POSITION_UNDEFINED;
+            if (!mHasImeFocus) {
+                return 0;
+            }
+
             mStartImeTop = showing ? hiddenTop : shownTop;
             mEndImeTop = showing ? shownTop : hiddenTop;
             mImeShown = showing;
 
             // Update target dim values
             mLastDim1 = mDimValue1;
-            mTargetDim1 = imeTargetPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT && showing
+            mTargetDim1 = imeTargetPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT && mImeShown
                     ? ADJUSTED_NONFOCUS_DIM : 0.0f;
             mLastDim2 = mDimValue2;
-            mTargetDim2 = imeTargetPosition == SPLIT_POSITION_TOP_OR_LEFT && showing
+            mTargetDim2 = imeTargetPosition == SPLIT_POSITION_TOP_OR_LEFT && mImeShown
                     ? ADJUSTED_NONFOCUS_DIM : 0.0f;
 
             // Calculate target bounds offset for IME
             mLastYOffset = mYOffsetForIme;
             final boolean needOffset = imeTargetPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
-                    && !isFloating && !isLandscape(mRootBounds) && showing;
+                    && !isFloating && !isLandscape(mRootBounds) && mImeShown;
             mTargetYOffset = needOffset ? getTargetYOffset() : 0;
 
             if (mTargetYOffset != mLastYOffset) {
@@ -817,15 +821,14 @@
             // ImePositionProcessor#onImeVisibilityChanged directly in DividerView is not enough
             // because DividerView won't receive onImeVisibilityChanged callback after it being
             // re-inflated.
-            mSplitWindowManager.setInteractive(
-                    !showing || imeTargetPosition == SPLIT_POSITION_UNDEFINED);
+            mSplitWindowManager.setInteractive(!mImeShown || !mHasImeFocus);
 
             return needOffset ? IME_ANIMATION_NO_ALPHA : 0;
         }
 
         @Override
         public void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) {
-            if (displayId != mDisplayId) return;
+            if (displayId != mDisplayId || !mHasImeFocus) return;
             onProgress(getProgress(imeTop));
             mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
         }
@@ -833,7 +836,7 @@
         @Override
         public void onImeEndPositioning(int displayId, boolean cancel,
                 SurfaceControl.Transaction t) {
-            if (displayId != mDisplayId || cancel) return;
+            if (displayId != mDisplayId || !mHasImeFocus || cancel) return;
             onProgress(1.0f);
             mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
         }
@@ -845,6 +848,7 @@
             if (!controlling && mImeShown) {
                 reset();
                 mSplitWindowManager.setInteractive(true);
+                mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this);
                 mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
             }
         }
@@ -878,6 +882,7 @@
         }
 
         void reset() {
+            mHasImeFocus = false;
             mImeShown = false;
             mYOffsetForIme = mLastYOffset = mTargetYOffset = 0;
             mDimValue1 = mLastDim1 = mTargetDim1 = 0.0f;
@@ -885,26 +890,6 @@
         }
 
         /**
-         * Applies adjusted task layout for showing IME.
-         *
-         * @return {@code false} if there's no need to adjust, otherwise {@code true}
-         */
-        boolean applyTaskLayoutForIme(WindowContainerTransaction wct,
-                WindowContainerToken token1, WindowContainerToken token2) {
-            if (mYOffsetForIme == 0) return false;
-
-            mTempRect.set(mBounds1);
-            mTempRect.offset(0, mYOffsetForIme);
-            wct.setBounds(token1, mTempRect);
-
-            mTempRect.set(mBounds2);
-            mTempRect.offset(0, mYOffsetForIme);
-            wct.setBounds(token2, mTempRect);
-
-            return true;
-        }
-
-        /**
          * Adjusts surface layout while showing IME.
          *
          * @return {@code false} if there's no need to adjust, otherwise {@code true}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
index cc3a3b2..35f1038 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
@@ -51,7 +51,7 @@
 
     /**
      * The name of the {@link SharedPreferences} that holds which user has seen the Letterbox
-     * Education for specific packages and which user has seen the full dialog for any package.
+     * Education dialog.
      */
     @VisibleForTesting
     static final String HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME =
@@ -66,6 +66,13 @@
 
     private final Transitions mTransitions;
 
+    /**
+     * The id of the current user, to associate with a boolean in {@link
+     * #HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME}, indicating whether that user has already seen the
+     * Letterbox Education dialog.
+     */
+    private final int mUserId;
+
     // Remember the last reported state in case visibility changes due to keyguard or IME updates.
     private boolean mEligibleForLetterboxEducation;
 
@@ -98,6 +105,7 @@
         mTransitions = transitions;
         mOnDismissCallback = onDismissCallback;
         mAnimationController = animationController;
+        mUserId = taskInfo.userId;
         mEligibleForLetterboxEducation = taskInfo.topActivityEligibleForLetterboxEducation;
         mSharedPreferences = mContext.getSharedPreferences(HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME,
                 Context.MODE_PRIVATE);
@@ -133,7 +141,6 @@
 
     @Override
     protected View createLayout() {
-        setSeenLetterboxEducation();
         mLayout = inflateLayout();
         updateDialogMargins();
 
@@ -177,6 +184,7 @@
             // Dialog has already been released.
             return;
         }
+        setSeenLetterboxEducation();
         mLayout.setDismissOnClickListener(this::onDismiss);
         // Focus on the dialog title for accessibility.
         mLayout.getDialogTitle().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
@@ -241,7 +249,7 @@
     }
 
     private String getPrefKey() {
-        return String.valueOf(mContext.getUserId());
+        return String.valueOf(mUserId);
     }
 
     @VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index e5a755c..48df28e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -89,6 +89,8 @@
     private final Rect mExitDestinationBounds = new Rect();
     @Nullable
     private IBinder mExitTransition;
+    private IBinder mRequestedEnterTransition;
+    private WindowContainerToken mRequestedEnterTask;
     /** The Task window that is currently in PIP windowing mode. */
     @Nullable
     private WindowContainerToken mCurrentPipTaskToken;
@@ -201,6 +203,9 @@
             }
             mCurrentPipTaskToken = null;
             return true;
+        } else if (transition == mRequestedEnterTransition) {
+            mRequestedEnterTransition = null;
+            mRequestedEnterTask = null;
         }
 
         // The previous PIP Task is no longer in PIP, but this is not an exit transition (This can
@@ -243,6 +248,8 @@
         if (request.getType() == TRANSIT_PIP) {
             WindowContainerTransaction wct = new WindowContainerTransaction();
             if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+                mRequestedEnterTransition = transition;
+                mRequestedEnterTask = request.getTriggerTask().token;
                 wct.setActivityWindowingMode(request.getTriggerTask().token,
                         WINDOWING_MODE_UNDEFINED);
                 final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
@@ -255,6 +262,23 @@
     }
 
     @Override
+    public boolean handleRotateDisplay(int startRotation, int endRotation,
+            WindowContainerTransaction wct) {
+        if (mRequestedEnterTransition != null && mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+            // A fade-in was requested but not-yet started. In this case, just recalculate the
+            // initial state under the new rotation.
+            int rotationDelta = deltaRotation(startRotation, endRotation);
+            if (rotationDelta != Surface.ROTATION_0) {
+                mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), endRotation);
+                final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
+                wct.setBounds(mRequestedEnterTask, destinationBounds);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
     public void onTransitionMerged(@NonNull IBinder transition) {
         if (transition != mExitTransition) {
             return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 02e713d2..24993c62 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -196,6 +196,17 @@
     }
 
     /**
+     * Called when the display is going to rotate.
+     *
+     * @return {@code true} if it was handled, otherwise the existing pip logic
+     *                      will deal with rotation.
+     */
+    public boolean handleRotateDisplay(int startRotation, int endRotation,
+            WindowContainerTransaction wct) {
+        return false;
+    }
+
+    /**
      * Callback interface for PiP transitions (both from and to PiP mode)
      */
     public interface PipTransitionCallback {
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 e86ebaa..9b04764 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
@@ -89,13 +89,14 @@
 
 import java.io.PrintWriter;
 import java.util.Optional;
+import java.util.Set;
 import java.util.function.Consumer;
 
 /**
  * Manages the picture-in-picture (PIP) UI and states for Phones.
  */
 public class PipController implements PipTransitionController.PipTransitionCallback,
-        RemoteCallable<PipController> {
+        RemoteCallable<PipController>, DisplayController.OnDisplaysChangedListener {
     private static final String TAG = "PipController";
 
     private Context mContext;
@@ -143,6 +144,9 @@
      */
     private final DisplayChangeController.OnDisplayChangingListener mRotationController = (
             int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> {
+        if (mPipTransitionController.handleRotateDisplay(fromRotation, toRotation, t)) {
+            return;
+        }
         if (mPipBoundsState.getDisplayLayout().rotation() == toRotation) {
             // The same rotation may have been set by auto PiP-able or fixed rotation. So notify
             // the change with fromRotation=false to apply the rotated destination bounds from
@@ -454,6 +458,14 @@
         return mMainExecutor;
     }
 
+    @Override
+    public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
+            Set<Rect> unrestricted) {
+        if (mPipBoundsState.getDisplayId() == displayId) {
+            mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
+        }
+    }
+
     private void onConfigurationChanged(Configuration newConfig) {
         mPipBoundsAlgorithm.onConfigurationChanged(mContext);
         mTouchHandler.onConfigurationChanged();
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 917eaa06..46b8e60 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
@@ -234,11 +234,12 @@
         }
 
         setState(STATE_PIP_MENU);
+        mTvPipMenuController.showMenu();
         updatePinnedStackBounds();
     }
 
     @Override
-    public void closeMenu() {
+    public void onMenuClosed() {
         if (DEBUG) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: closeMenu(), state before=%s", TAG, stateToName(mState));
@@ -285,6 +286,12 @@
     }
 
     @Override
+    public void enterPipMovementMenu() {
+        setState(STATE_PIP_MENU);
+        mTvPipMenuController.showMovementMenuOnly();
+    }
+
+    @Override
     public void movePip(int keycode) {
         if (mTvPipBoundsAlgorithm.updateGravity(keycode)) {
             mTvPipMenuController.updateGravity(mTvPipBoundsState.getTvPipGravity());
@@ -438,7 +445,7 @@
         }
 
         mPipNotificationController.dismiss();
-        mTvPipMenuController.hideMenu();
+        mTvPipMenuController.closeMenu();
         mTvPipBoundsState.resetTvPipState();
         setState(STATE_NO_PIP);
         mPinnedTaskId = NONEXISTENT_TASK_ID;
@@ -478,16 +485,6 @@
                     TAG, stateToName(state), stateToName(mState));
         }
         mState = state;
-
-        if (mState == STATE_PIP_MENU) {
-            if (DEBUG) {
-                ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                        "%s:  > show menu", TAG);
-            }
-            mTvPipMenuController.showMenu();
-        }
-
-        updatePinnedStackBounds();
     }
 
     private void loadConfigurations() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index b6ae398..35c34ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -65,6 +65,9 @@
 
     // User can actively move the PiP via the DPAD.
     private boolean mInMoveMode;
+    // Used when only showing the move menu since we want to close the menu completely when
+    // exiting the move menu instead of showing the regular button menu.
+    private boolean mCloseAfterExitMoveMenu;
 
     private final List<RemoteAction> mMediaActions = new ArrayList<>();
     private final List<RemoteAction> mAppActions = new ArrayList<>();
@@ -102,7 +105,7 @@
         final BroadcastReceiver closeSystemDialogsBroadcastReceiver = new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                hideMenu();
+                closeMenu();
             }
         };
         context.registerReceiverForAllUsers(closeSystemDialogsBroadcastReceiver,
@@ -155,29 +158,49 @@
                 0, SHELL_ROOT_LAYER_PIP);
     }
 
+    void showMovementMenuOnly() {
+        if (DEBUG) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: showMovementMenuOnly()", TAG);
+        }
+        mInMoveMode = true;
+        mCloseAfterExitMoveMenu = true;
+        showMenuInternal();
+    }
+
     @Override
     public void showMenu() {
         if (DEBUG) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: showMenu()", TAG);
         }
+        mInMoveMode = false;
+        mCloseAfterExitMoveMenu = false;
+        showMenuInternal();
+    }
 
-        if (mPipMenuView != null) {
-            Rect menuBounds = getMenuBounds(mTvPipBoundsState.getBounds());
-            mSystemWindows.updateViewLayout(mPipMenuView, getPipMenuLayoutParams(
-                    MENU_WINDOW_TITLE, menuBounds.width(), menuBounds.height()));
-            maybeUpdateMenuViewActions();
-            updateExpansionState();
+    private void showMenuInternal() {
+        if (mPipMenuView == null) {
+            return;
+        }
+        Rect menuBounds = getMenuBounds(mTvPipBoundsState.getBounds());
+        mSystemWindows.updateViewLayout(mPipMenuView, getPipMenuLayoutParams(
+                MENU_WINDOW_TITLE, menuBounds.width(), menuBounds.height()));
+        maybeUpdateMenuViewActions();
+        updateExpansionState();
 
-            SurfaceControl menuSurfaceControl = getSurfaceControl();
-            if (menuSurfaceControl != null) {
-                SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-                t.setRelativeLayer(mPipMenuView.getWindowSurfaceControl(), mLeash, 1);
-                t.setPosition(menuSurfaceControl, menuBounds.left, menuBounds.top);
-                t.apply();
-            }
-            grantPipMenuFocus(true);
-            mPipMenuView.show(mInMoveMode, mDelegate.getPipGravity());
+        SurfaceControl menuSurfaceControl = getSurfaceControl();
+        if (menuSurfaceControl != null) {
+            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            t.setRelativeLayer(mPipMenuView.getWindowSurfaceControl(), mLeash, 1);
+            t.setPosition(menuSurfaceControl, menuBounds.left, menuBounds.top);
+            t.apply();
+        }
+        grantPipMenuFocus(true);
+        if (mInMoveMode) {
+            mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
+        } else {
+            mPipMenuView.showButtonMenu();
         }
     }
 
@@ -199,25 +222,18 @@
         return menuBounds;
     }
 
-    void hideMenu() {
-        if (!isMenuVisible()) {
-            if (DEBUG) {
-                ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                        "%s: hideMenu() - Menu isn't visible, so don't hide", TAG);
-            }
+    void closeMenu() {
+        if (DEBUG) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: closeMenu()", TAG);
+        }
+        if (mPipMenuView == null) {
             return;
-        } else {
-            if (DEBUG) {
-                ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                        "%s: hideMenu()", TAG);
-            }
         }
 
-        mPipMenuView.hide();
-        if (!mInMoveMode) {
-            grantPipMenuFocus(false);
-            mDelegate.closeMenu();
-        }
+        mPipMenuView.hideAll();
+        grantPipMenuFocus(false);
+        mDelegate.onMenuClosed();
     }
 
     boolean isInMoveMode() {
@@ -228,25 +244,29 @@
     public void onEnterMoveMode() {
         if (DEBUG) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: onEnterMoveMode - %b", TAG, mInMoveMode);
+                    "%s: onEnterMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode,
+                    mCloseAfterExitMoveMenu);
         }
         mInMoveMode = true;
-        mPipMenuView.showMenuButtons(false);
-        mPipMenuView.showMovementHints(mDelegate.getPipGravity());
-        mDelegate.onInMoveModeChanged();
+        mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
     }
 
     @Override
     public boolean onExitMoveMode() {
         if (DEBUG) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: onExitMoveMode - %b", TAG, mInMoveMode);
+                    "%s: onExitMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode,
+                    mCloseAfterExitMoveMenu);
+        }
+        if (mCloseAfterExitMoveMenu) {
+            mInMoveMode = false;
+            mCloseAfterExitMoveMenu = false;
+            closeMenu();
+            return true;
         }
         if (mInMoveMode) {
             mInMoveMode = false;
-            mPipMenuView.showMenuButtons(true);
-            mPipMenuView.hideMovementHints();
-            mDelegate.onInMoveModeChanged();
+            mPipMenuView.showButtonMenu();
             return true;
         }
         return false;
@@ -266,7 +286,7 @@
 
     @Override
     public void detach() {
-        hideMenu();
+        closeMenu();
         detachPipMenuView();
         mLeash = null;
     }
@@ -486,7 +506,7 @@
     @Override
     public void onBackPress() {
         if (!onExitMoveMode()) {
-            hideMenu();
+            closeMenu();
         }
     }
 
@@ -516,7 +536,7 @@
 
         void togglePipExpansion();
 
-        void closeMenu();
+        void onMenuClosed();
 
         void closePip();
     }
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 7fdb9ed..ccd054a 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
@@ -177,29 +177,43 @@
                 expanded ? R.string.pip_collapse : R.string.pip_expand);
     }
 
-    void show(boolean inMoveMode, int gravity) {
+    /**
+     * @param gravity for the arrow hints
+     */
+    void showMoveMenu(int gravity) {
         if (DEBUG) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: show(), inMoveMode: %b", TAG, inMoveMode);
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
         }
-        if (inMoveMode) {
-            showMovementHints(gravity);
-        } else {
-            animateAlphaTo(1, mActionButtonsContainer);
-        }
-        animateAlphaTo(1, mMenuFrameView);
+        showMenuButtons(false);
+        showMovementHints(gravity);
+        showMenuFrame(true);
     }
 
-    void hide() {
+    void showButtonMenu() {
         if (DEBUG) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: hide()", TAG);
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showButtonMenu()", TAG);
         }
-        animateAlphaTo(0, mActionButtonsContainer);
-        animateAlphaTo(0, mMenuFrameView);
+        showMenuButtons(true);
         hideMovementHints();
+        showMenuFrame(true);
+    }
+
+    /**
+     * Hides all menu views, including the menu frame.
+     */
+    void hideAll() {
+        if (DEBUG) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: hideAll()", TAG);
+        }
+        showMenuButtons(false);
+        hideMovementHints();
+        showMenuFrame(false);
     }
 
     private void animateAlphaTo(float alpha, View view) {
+        if (view.getAlpha() == alpha) {
+            return;
+        }
         view.animate()
                 .alpha(alpha)
                 .setInterpolator(alpha == 0f ? TvPipInterpolators.EXIT : TvPipInterpolators.ENTER)
@@ -419,6 +433,10 @@
         animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
     }
 
+    private void showMenuFrame(boolean show) {
+        animateAlphaTo(show ? 1 : 0, mMenuFrameView);
+    }
+
     interface Listener {
 
         void onBackPress();
@@ -426,7 +444,10 @@
         void onEnterMoveMode();
 
         /**
-         * @return whether move mode was exited
+         * Called when a button for exiting move mode was pressed.
+         *
+         * @return true if the event was handled or false if the key event should be handled by the
+         * next receiver.
          */
         boolean onExitMoveMode();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
index 7bd3ce9..4033f03 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
@@ -56,6 +56,10 @@
             "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU";
     private static final String ACTION_CLOSE_PIP =
             "com.android.wm.shell.pip.tv.notification.action.CLOSE_PIP";
+    private static final String ACTION_MOVE_PIP =
+            "com.android.wm.shell.pip.tv.notification.action.MOVE_PIP";
+    private static final String ACTION_TOGGLE_EXPANDED_PIP =
+            "com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP";
 
     private final Context mContext;
     private final PackageManager mPackageManager;
@@ -222,6 +226,8 @@
             mIntentFilter = new IntentFilter();
             mIntentFilter.addAction(ACTION_CLOSE_PIP);
             mIntentFilter.addAction(ACTION_SHOW_PIP_MENU);
+            mIntentFilter.addAction(ACTION_MOVE_PIP);
+            mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP);
         }
         boolean mRegistered = false;
 
@@ -252,6 +258,10 @@
                 mDelegate.showPictureInPictureMenu();
             } else if (ACTION_CLOSE_PIP.equals(action)) {
                 mDelegate.closePip();
+            } else if (ACTION_MOVE_PIP.equals(action)) {
+                mDelegate.enterPipMovementMenu();
+            } else if (ACTION_TOGGLE_EXPANDED_PIP.equals(action)) {
+                mDelegate.togglePipExpansion();
             }
         }
     }
@@ -259,5 +269,7 @@
     interface Delegate {
         void showPictureInPictureMenu();
         void closePip();
+        void enterPipMovementMenu();
+        void togglePipExpansion();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index f0fb69f..b6c8cff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -301,8 +301,6 @@
                 Color.TRANSPARENT);
         attrs.mSplashScreenIcon = safeReturnAttrDefault((def) -> typedArray.getDrawable(
                 R.styleable.Window_windowSplashScreenAnimatedIcon), null);
-        attrs.mAnimationDuration = safeReturnAttrDefault((def) -> typedArray.getInt(
-                R.styleable.Window_windowSplashScreenAnimationDuration, def), 0);
         attrs.mBrandingImage = safeReturnAttrDefault((def) -> typedArray.getDrawable(
                 R.styleable.Window_windowSplashScreenBrandingImage), null);
         attrs.mIconBgColor = safeReturnAttrDefault((def) -> typedArray.getColor(
@@ -310,9 +308,8 @@
                 Color.TRANSPARENT);
         typedArray.recycle();
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
-                "getWindowAttrs: window attributes color: %s, replace icon: %b, avd duration: %d",
-                Integer.toHexString(attrs.mWindowBgColor), attrs.mSplashScreenIcon != null,
-                attrs.mAnimationDuration);
+                "getWindowAttrs: window attributes color: %s, replace icon: %b",
+                Integer.toHexString(attrs.mWindowBgColor), attrs.mSplashScreenIcon != null);
     }
 
     /** Creates the wrapper with system theme to avoid unexpected styles from app. */
@@ -327,7 +324,6 @@
         private Drawable mSplashScreenIcon = null;
         private Drawable mBrandingImage = null;
         private int mIconBgColor = Color.TRANSPARENT;
-        private int mAnimationDuration = 0;
     }
 
     /**
@@ -401,16 +397,13 @@
 
         SplashScreenView build() {
             Drawable iconDrawable;
-            final long animationDuration;
             if (mSuggestType == STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN
                     || mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
                 // empty or legacy splash screen case
-                animationDuration = 0;
                 mFinalIconSize = 0;
             } else if (mTmpAttrs.mSplashScreenIcon != null) {
                 // Using the windowSplashScreenAnimatedIcon attribute
                 iconDrawable = mTmpAttrs.mSplashScreenIcon;
-                animationDuration = mTmpAttrs.mAnimationDuration;
 
                 // There is no background below the icon, so scale the icon up
                 if (mTmpAttrs.mIconBgColor == Color.TRANSPARENT
@@ -440,11 +433,9 @@
                     Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                     createIconDrawable(new BitmapDrawable(bitmap), true);
                 }
-                animationDuration = 0;
             }
 
-            return fillViewWithIcon(mFinalIconSize, mFinalIconDrawables, animationDuration,
-                    mUiThreadInitTask);
+            return fillViewWithIcon(mFinalIconSize, mFinalIconDrawables, mUiThreadInitTask);
         }
 
         private class ShapeIconFactory extends BaseIconFactory {
@@ -460,7 +451,7 @@
             } else {
                 mFinalIconDrawables = SplashscreenIconDrawableFactory.makeIconDrawable(
                         mTmpAttrs.mIconBgColor, mThemeColor, iconDrawable, mDefaultIconSize,
-                        mFinalIconSize, mSplashscreenWorkerHandler, mSplashScreenExecutor);
+                        mFinalIconSize, mSplashscreenWorkerHandler);
             }
         }
 
@@ -520,7 +511,7 @@
         }
 
         private SplashScreenView fillViewWithIcon(int iconSize, @Nullable Drawable[] iconDrawable,
-                long animationDuration, Consumer<Runnable> uiThreadInitTask) {
+                Consumer<Runnable> uiThreadInitTask) {
             Drawable foreground = null;
             Drawable background = null;
             if (iconDrawable != null) {
@@ -536,7 +527,6 @@
                     .setIconSize(iconSize)
                     .setIconBackground(background)
                     .setCenterViewDrawable(foreground)
-                    .setAnimationDurationMillis(animationDuration)
                     .setUiThreadInitConsumer(uiThreadInitTask)
                     .setAllowHandleSolidColor(mAllowHandleSolidColor);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index fdd5a15..5f52071 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -44,7 +44,6 @@
 import android.window.SplashScreenView;
 
 import com.android.internal.R;
-import com.android.wm.shell.common.ShellExecutor;
 
 import java.util.function.LongConsumer;
 
@@ -63,15 +62,14 @@
      */
     static Drawable[] makeIconDrawable(@ColorInt int backgroundColor, @ColorInt int themeColor,
             @NonNull Drawable foregroundDrawable, int srcIconSize, int iconSize,
-            Handler splashscreenWorkerHandler, ShellExecutor splashScreenExecutor) {
+            Handler splashscreenWorkerHandler) {
         Drawable foreground;
         Drawable background = null;
         boolean drawBackground =
                 backgroundColor != Color.TRANSPARENT && backgroundColor != themeColor;
 
         if (foregroundDrawable instanceof Animatable) {
-            foreground = new AnimatableIconAnimateListener(foregroundDrawable,
-                    splashScreenExecutor);
+            foreground = new AnimatableIconAnimateListener(foregroundDrawable);
         } else if (foregroundDrawable instanceof AdaptiveIconDrawable) {
             // If the icon is Adaptive, we already use the icon background.
             drawBackground = false;
@@ -274,12 +272,9 @@
         private boolean mAnimationTriggered;
         private AnimatorListenerAdapter mJankMonitoringListener;
         private boolean mRunning;
-        private long mDuration;
         private LongConsumer mStartListener;
-        private final ShellExecutor mSplashScreenExecutor;
 
-        AnimatableIconAnimateListener(@NonNull Drawable foregroundDrawable,
-                ShellExecutor splashScreenExecutor) {
+        AnimatableIconAnimateListener(@NonNull Drawable foregroundDrawable) {
             super(foregroundDrawable);
             Callback callback = new Callback() {
                 @Override
@@ -299,7 +294,6 @@
                 }
             };
             mForegroundDrawable.setCallback(callback);
-            mSplashScreenExecutor = splashScreenExecutor;
             mAnimatableIcon = (Animatable) mForegroundDrawable;
         }
 
@@ -309,9 +303,8 @@
         }
 
         @Override
-        public void prepareAnimate(long duration, LongConsumer startListener) {
+        public void prepareAnimate(LongConsumer startListener) {
             stopAnimation();
-            mDuration = duration;
             mStartListener = startListener;
         }
 
@@ -328,11 +321,11 @@
                     mJankMonitoringListener.onAnimationCancel(null);
                 }
                 if (mStartListener != null) {
-                    mStartListener.accept(mDuration);
+                    mStartListener.accept(0);
                 }
                 return;
             }
-            long animDuration = mDuration;
+            long animDuration = 0;
             if (mAnimatableIcon instanceof AnimatedVectorDrawable
                     && ((AnimatedVectorDrawable) mAnimatableIcon).getTotalDuration() > 0) {
                 animDuration = ((AnimatedVectorDrawable) mAnimatableIcon).getTotalDuration();
@@ -341,9 +334,8 @@
                 animDuration = ((AnimationDrawable) mAnimatableIcon).getTotalDuration();
             }
             mRunning = true;
-            mSplashScreenExecutor.executeDelayed(this::stopAnimation, animDuration);
             if (mStartListener != null) {
-                mStartListener.accept(Math.max(animDuration, 0));
+                mStartListener.accept(animDuration);
             }
         }
 
@@ -359,7 +351,6 @@
         @Override
         public void stopAnimation() {
             if (mRunning) {
-                mSplashScreenExecutor.removeCallbacks(this::stopAnimation);
                 onAnimationEnd();
                 mJankMonitoringListener = null;
             }
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 2aa63b3..b0e44a1 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
@@ -434,6 +434,10 @@
                         // If available use the background color provided through AnimationOptions
                         backgroundColorForTransition =
                                 info.getAnimationOptions().getBackgroundColor();
+                    } else if (a.getBackgroundColor() != 0) {
+                        // Otherwise fallback on the background color provided through the animation
+                        // definition.
+                        backgroundColorForTransition = a.getBackgroundColor();
                     } else if (change.getBackgroundColor() != 0) {
                         // Otherwise default to the window's background color if provided through
                         // the theme as the background color for the animation - the top most window
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 4607d8a..596100d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -143,9 +143,7 @@
 
         // Verify that the compat controls and letterbox education are updated with new size compat
         // info.
-        clearInvocations(mMockCompatLayout);
-        clearInvocations(mMockLetterboxEduLayout);
-        clearInvocations(mController);
+        clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
         taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
                 CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
         mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
@@ -156,9 +154,7 @@
                 true);
 
         // Verify that compat controls and letterbox education are removed with null task listener.
-        clearInvocations(mMockCompatLayout);
-        clearInvocations(mMockLetterboxEduLayout);
-        clearInvocations(mController);
+        clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
         mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
                 /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN),
                 /* taskListener= */ null);
@@ -181,9 +177,7 @@
                 eq(mMockTaskListener));
 
         // Verify that the layout is created again.
-        clearInvocations(mMockCompatLayout);
-        clearInvocations(mMockLetterboxEduLayout);
-        clearInvocations(mController);
+        clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
         mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
 
         verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
@@ -206,9 +200,7 @@
         verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
                 eq(mMockTaskListener));
 
-        clearInvocations(mMockCompatLayout);
-        clearInvocations(mMockLetterboxEduLayout);
-        clearInvocations(mController);
+        clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
         mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
 
         verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
@@ -217,9 +209,7 @@
                 true);
 
         // Verify that the layout is created again.
-        clearInvocations(mMockCompatLayout);
-        clearInvocations(mMockLetterboxEduLayout);
-        clearInvocations(mController);
+        clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
         mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
 
         verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
@@ -294,8 +284,7 @@
         verify(mMockLetterboxEduLayout).updateDisplayLayout(mMockDisplayLayout);
 
         // No update if the insets state is the same.
-        clearInvocations(mMockCompatLayout);
-        clearInvocations(mMockLetterboxEduLayout);
+        clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout);
         mOnInsetsChangedListenerCaptor.getValue().insetsChanged(new InsetsState(insetsState));
         verify(mMockCompatLayout, never()).updateDisplayLayout(mMockDisplayLayout);
         verify(mMockLetterboxEduLayout, never()).updateDisplayLayout(mMockDisplayLayout);
@@ -368,8 +357,7 @@
         verify(mMockCompatLayout, times(2)).updateVisibility(false);
         verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
 
-        clearInvocations(mMockCompatLayout);
-        clearInvocations(mMockLetterboxEduLayout);
+        clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout);
 
         // Verify button remains hidden after keyguard becomes not showing since IME is showing.
         mController.onKeyguardShowingChanged(false);
@@ -395,8 +383,7 @@
         verify(mMockCompatLayout, times(2)).updateVisibility(false);
         verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
 
-        clearInvocations(mMockCompatLayout);
-        clearInvocations(mMockLetterboxEduLayout);
+        clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout);
 
         // Verify button remains hidden after IME is hidden since keyguard is showing.
         mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
index 7d51b52..f3a8cf4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
@@ -26,6 +26,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -75,6 +76,12 @@
 @SmallTest
 public class LetterboxEduWindowManagerTest extends ShellTestCase {
 
+    private static final int USER_ID_1 = 1;
+    private static final int USER_ID_2 = 2;
+
+    private static final String PREF_KEY_1 = String.valueOf(USER_ID_1);
+    private static final String PREF_KEY_2 = String.valueOf(USER_ID_2);
+
     private static final int TASK_ID = 1;
 
     private static final int TASK_WIDTH = 200;
@@ -98,9 +105,10 @@
     @Mock private Runnable mOnDismissCallback;
 
     private SharedPreferences mSharedPreferences;
-    private String mPrefKey;
     @Nullable
-    private Boolean mInitialPrefValue = null;
+    private Boolean mInitialPrefValue1 = null;
+    @Nullable
+    private Boolean mInitialPrefValue2 = null;
 
     @Before
     public void setUp() {
@@ -109,20 +117,28 @@
         mSharedPreferences = mContext.getSharedPreferences(
                 LetterboxEduWindowManager.HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME,
                 Context.MODE_PRIVATE);
-        mPrefKey = String.valueOf(mContext.getUserId());
-        if (mSharedPreferences.contains(mPrefKey)) {
-            mInitialPrefValue = mSharedPreferences.getBoolean(mPrefKey, /* default= */ false);
-            mSharedPreferences.edit().remove(mPrefKey).apply();
+        if (mSharedPreferences.contains(PREF_KEY_1)) {
+            mInitialPrefValue1 = mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false);
+            mSharedPreferences.edit().remove(PREF_KEY_1).apply();
+        }
+        if (mSharedPreferences.contains(PREF_KEY_2)) {
+            mInitialPrefValue2 = mSharedPreferences.getBoolean(PREF_KEY_2, /* default= */ false);
+            mSharedPreferences.edit().remove(PREF_KEY_2).apply();
         }
     }
 
     @After
     public void tearDown() {
         SharedPreferences.Editor editor = mSharedPreferences.edit();
-        if (mInitialPrefValue == null) {
-            editor.remove(mPrefKey);
+        if (mInitialPrefValue1 == null) {
+            editor.remove(PREF_KEY_1);
         } else {
-            editor.putBoolean(mPrefKey, mInitialPrefValue);
+            editor.putBoolean(PREF_KEY_1, mInitialPrefValue1);
+        }
+        if (mInitialPrefValue2 == null) {
+            editor.remove(PREF_KEY_2);
+        } else {
+            editor.putBoolean(PREF_KEY_2, mInitialPrefValue2);
         }
         editor.apply();
     }
@@ -137,19 +153,9 @@
     }
 
     @Test
-    public void testCreateLayout_alreadyShownToUser_doesNotCreateLayout() {
-        LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
-        mSharedPreferences.edit().putBoolean(mPrefKey, true).apply();
-
-        assertFalse(windowManager.createLayout(/* canShow= */ true));
-
-        assertNull(windowManager.mLayout);
-    }
-
-    @Test
     public void testCreateLayout_taskBarEducationIsShowing_doesNotCreateLayout() {
         LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */
-                true, /* isTaskbarEduShowing= */ true);
+                true, USER_ID_1, /* isTaskbarEduShowing= */ true);
 
         assertFalse(windowManager.createLayout(/* canShow= */ true));
 
@@ -162,7 +168,7 @@
 
         assertTrue(windowManager.createLayout(/* canShow= */ false));
 
-        assertFalse(mSharedPreferences.getBoolean(mPrefKey, /* default= */ false));
+        assertFalse(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
         assertNull(windowManager.mLayout);
     }
 
@@ -172,7 +178,6 @@
 
         assertTrue(windowManager.createLayout(/* canShow= */ true));
 
-        assertTrue(mSharedPreferences.getBoolean(mPrefKey, /* default= */ false));
         LetterboxEduDialogLayout layout = windowManager.mLayout;
         assertNotNull(layout);
         verify(mViewHost).setView(eq(layout), mWindowAttrsCaptor.capture());
@@ -183,6 +188,8 @@
         assertNotNull(dialogTitle);
         spyOn(dialogTitle);
 
+        // The education shouldn't be marked as seen until enter animation is done.
+        assertFalse(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
         // Clicking the layout does nothing until enter animation is done.
         layout.performClick();
         verify(mAnimationController, never()).startExitAnimation(any(), any());
@@ -191,6 +198,7 @@
 
         verifyAndFinishEnterAnimation(layout);
 
+        assertTrue(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
         verify(dialogTitle).sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
         // Exit animation should start following a click on the layout.
         layout.performClick();
@@ -208,12 +216,41 @@
     }
 
     @Test
+    public void testCreateLayout_alreadyShownToUser_createsLayoutForOtherUserOnly() {
+        LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true,
+                USER_ID_1, /* isTaskbarEduShowing= */ false);
+
+        assertTrue(windowManager.createLayout(/* canShow= */ true));
+
+        assertNotNull(windowManager.mLayout);
+        verifyAndFinishEnterAnimation(windowManager.mLayout);
+        assertTrue(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+
+        windowManager.release();
+        windowManager = createWindowManager(/* eligible= */ true,
+                USER_ID_1, /* isTaskbarEduShowing= */ false);
+
+        assertFalse(windowManager.createLayout(/* canShow= */ true));
+        assertNull(windowManager.mLayout);
+
+        clearInvocations(mTransitions, mAnimationController);
+
+        windowManager = createWindowManager(/* eligible= */ true,
+                USER_ID_2, /* isTaskbarEduShowing= */ false);
+
+        assertTrue(windowManager.createLayout(/* canShow= */ true));
+
+        assertNotNull(windowManager.mLayout);
+        verifyAndFinishEnterAnimation(windowManager.mLayout);
+        assertTrue(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
+    }
+
+    @Test
     public void testCreateLayout_windowManagerReleasedBeforeTransitionsIsIdle_doesNotStartAnim() {
         LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
 
         assertTrue(windowManager.createLayout(/* canShow= */ true));
-
-        assertTrue(mSharedPreferences.getBoolean(mPrefKey, /* default= */ false));
+        assertNotNull(windowManager.mLayout);
 
         verify(mTransitions).runOnIdle(mRunOnIdleCaptor.capture());
 
@@ -222,6 +259,7 @@
         mRunOnIdleCaptor.getValue().run();
 
         verify(mAnimationController, never()).startEnterAnimation(any(), any());
+        assertFalse(mSharedPreferences.getBoolean(PREF_KEY_1, /* default= */ false));
     }
 
     @Test
@@ -233,7 +271,7 @@
         assertNotNull(layout);
 
         assertTrue(windowManager.updateCompatInfo(
-                createTaskInfo(/* eligible= */ true, new Rect(50, 25, 150, 75)),
+                createTaskInfo(/* eligible= */ true, USER_ID_1, new Rect(50, 25, 150, 75)),
                 mTaskListener, /* canShow= */ true));
 
         verifyLayout(layout, layout.getLayoutParams(), /* expectedWidth= */ 100,
@@ -341,13 +379,13 @@
     }
 
     private LetterboxEduWindowManager createWindowManager(boolean eligible) {
-        return createWindowManager(eligible, /* isTaskbarEduShowing= */ false);
+        return createWindowManager(eligible, USER_ID_1, /* isTaskbarEduShowing= */ false);
     }
 
     private LetterboxEduWindowManager createWindowManager(boolean eligible,
-            boolean isTaskbarEduShowing) {
+            int userId, boolean isTaskbarEduShowing) {
         LetterboxEduWindowManager windowManager = new LetterboxEduWindowManager(mContext,
-                createTaskInfo(eligible), mSyncTransactionQueue, mTaskListener,
+                createTaskInfo(eligible, userId), mSyncTransactionQueue, mTaskListener,
                 createDisplayLayout(), mTransitions, mOnDismissCallback,
                 mAnimationController);
 
@@ -375,11 +413,16 @@
     }
 
     private static TaskInfo createTaskInfo(boolean eligible) {
-        return createTaskInfo(eligible, new Rect(0, 0, TASK_WIDTH, TASK_HEIGHT));
+        return createTaskInfo(eligible, USER_ID_1);
     }
 
-    private static TaskInfo createTaskInfo(boolean eligible, Rect bounds) {
+    private static TaskInfo createTaskInfo(boolean eligible, int userId) {
+        return createTaskInfo(eligible, userId, new Rect(0, 0, TASK_WIDTH, TASK_HEIGHT));
+    }
+
+    private static TaskInfo createTaskInfo(boolean eligible, int userId, Rect bounds) {
         ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+        taskInfo.userId = userId;
         taskInfo.taskId = TASK_ID;
         taskInfo.topActivityEligibleForLetterboxEducation = eligible;
         taskInfo.configuration.windowConfiguration.setBounds(bounds);
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 935f669..af6e5d4 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
@@ -59,6 +59,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.Optional;
+import java.util.Set;
 
 /**
  * Unit tests for {@link PipController}
@@ -209,4 +210,15 @@
 
         verify(mMockPipMotionHelper, never()).movePip(any(Rect.class));
     }
+
+    @Test
+    public void onKeepClearAreasChanged_updatesPipBoundsState() {
+        final int displayId = 1;
+        final Rect keepClearArea = new Rect(0, 0, 10, 10);
+        when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
+
+        mPipController.onKeepClearAreasChanged(displayId, Set.of(keepClearArea), Set.of());
+
+        verify(mMockPipBoundsState).setKeepClearAreas(Set.of(keepClearArea), Set.of());
+    }
 }
diff --git a/media/java/android/media/tv/DsmccResponse.java b/media/java/android/media/tv/DsmccResponse.java
index e14e8791..657d839 100644
--- a/media/java/android/media/tv/DsmccResponse.java
+++ b/media/java/android/media/tv/DsmccResponse.java
@@ -122,11 +122,12 @@
         mBiopMessageType = BIOP_MESSAGE_TYPE_STREAM;
         mFileDescriptor = null;
         mChildList = null;
-        mEventIds = eventIds;
-        mEventNames = eventNames;
-        if (mEventIds.length != eventNames.length) {
+        if (!((eventIds != null && eventNames != null && eventIds.length == eventNames.length)
+                || (eventIds == null && eventNames == null))) {
             throw new IllegalStateException("The size of eventIds and eventNames must be equal");
         }
+        mEventIds = eventIds;
+        mEventNames = eventNames;
     }
 
     private DsmccResponse(@NonNull Parcel source) {
@@ -137,10 +138,13 @@
             case BIOP_MESSAGE_TYPE_SERVICE_GATEWAY:
             case BIOP_MESSAGE_TYPE_DIRECTORY:
                 int childNum = source.readInt();
-                mChildList = new ArrayList<>();
-                for (int i = 0; i < childNum; i++) {
-                    mChildList.add(source.readString());
-                }
+                if (childNum > 0) {
+                    mChildList = new ArrayList<>();
+                    for (int i = 0; i < childNum; i++) {
+                        mChildList.add(source.readString());
+                    }
+                } else
+                    mChildList = null;
                 mFileDescriptor = null;
                 mEventIds = null;
                 mEventNames = null;
@@ -153,11 +157,16 @@
                 break;
             case BIOP_MESSAGE_TYPE_STREAM:
                 int eventNum = source.readInt();
-                mEventIds = new int[eventNum];
-                mEventNames = new String[eventNum];
-                for (int i = 0; i < eventNum; i++) {
-                    mEventIds[i] = source.readInt();
-                    mEventNames[i] = source.readString();
+                if (eventNum > 0) {
+                    mEventIds = new int[eventNum];
+                    mEventNames = new String[eventNum];
+                    for (int i = 0; i < eventNum; i++) {
+                        mEventIds[i] = source.readInt();
+                        mEventNames[i] = source.readString();
+                    }
+                } else {
+                    mEventIds = null;
+                    mEventNames = null;
                 }
                 mChildList = null;
                 mFileDescriptor = null;
@@ -196,7 +205,7 @@
                 && !mBiopMessageType.equals(BIOP_MESSAGE_TYPE_SERVICE_GATEWAY)) {
             throw new IllegalStateException("Not directory object");
         }
-        return new ArrayList<String>(mChildList);
+        return mChildList != null ? new ArrayList<String>(mChildList) : new ArrayList<String>();
     }
 
     /**
@@ -207,7 +216,7 @@
         if (!mBiopMessageType.equals(BIOP_MESSAGE_TYPE_STREAM)) {
             throw new IllegalStateException("Not stream event object");
         }
-        return mEventIds;
+        return mEventIds != null ? mEventIds : new int[0];
     }
 
     /**
@@ -218,7 +227,7 @@
         if (!mBiopMessageType.equals(BIOP_MESSAGE_TYPE_STREAM)) {
             throw new IllegalStateException("Not stream event object");
         }
-        return mEventNames;
+        return mEventNames != null ? mEventNames : new String[0];
     }
 
     @Override
@@ -233,20 +242,26 @@
         switch (mBiopMessageType) {
             case BIOP_MESSAGE_TYPE_SERVICE_GATEWAY:
             case BIOP_MESSAGE_TYPE_DIRECTORY:
-                dest.writeInt(mChildList.size());
-                for (String child : mChildList) {
-                    dest.writeString(child);
-                }
+                if (mChildList != null && mChildList.size() > 0) {
+                    dest.writeInt(mChildList.size());
+                    for (String child : mChildList) {
+                        dest.writeString(child);
+                    }
+                } else
+                    dest.writeInt(0);
                 break;
             case BIOP_MESSAGE_TYPE_FILE:
                 dest.writeFileDescriptor(mFileDescriptor.getFileDescriptor());
                 break;
             case BIOP_MESSAGE_TYPE_STREAM:
-                dest.writeInt(mEventIds.length);
-                for (int i = 0; i < mEventIds.length; i++) {
-                    dest.writeInt(mEventIds[i]);
-                    dest.writeString(mEventNames[i]);
-                }
+                if (mEventIds != null && mEventIds.length > 0) {
+                    dest.writeInt(mEventIds.length);
+                    for (int i = 0; i < mEventIds.length; i++) {
+                        dest.writeInt(mEventIds[i]);
+                        dest.writeString(mEventNames[i]);
+                    }
+                } else
+                    dest.writeInt(0);
                 break;
             default:
                 throw new IllegalStateException("unexpected BIOP message type");
diff --git a/media/java/android/media/tv/StreamEventResponse.java b/media/java/android/media/tv/StreamEventResponse.java
index 28dff37..05dc39a 100644
--- a/media/java/android/media/tv/StreamEventResponse.java
+++ b/media/java/android/media/tv/StreamEventResponse.java
@@ -63,8 +63,12 @@
         mEventId = source.readInt();
         mNptMillis = source.readLong();
         int dataLength = source.readInt();
-        mData = new byte[dataLength];
-        source.readByteArray(mData);
+        if (dataLength > 0) {
+            mData = new byte[dataLength];
+            source.readByteArray(mData);
+        } else {
+            mData = null;
+        }
     }
 
     /**
@@ -100,7 +104,11 @@
         super.writeToParcel(dest, flags);
         dest.writeInt(mEventId);
         dest.writeLong(mNptMillis);
-        dest.writeInt(mData.length);
-        dest.writeByteArray(mData);
+        if (mData != null && mData.length > 0) {
+            dest.writeInt(mData.length);
+            dest.writeByteArray(mData);
+        } else {
+            dest.writeInt(0);
+        }
     }
 }
diff --git a/packages/CompanionDeviceManager/res/drawable/helper_ok_button.xml b/packages/CompanionDeviceManager/res/drawable/helper_back_button.xml
similarity index 100%
rename from packages/CompanionDeviceManager/res/drawable/helper_ok_button.xml
rename to packages/CompanionDeviceManager/res/drawable/helper_back_button.xml
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_info.xml b/packages/CompanionDeviceManager/res/drawable/ic_info.xml
new file mode 100644
index 0000000..d9f6a27
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_info.xml
@@ -0,0 +1,26 @@
+<?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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="@android:color/system_accent1_600">
+    <path android:fillColor="@android:color/white"
+          android:pathData="M11,17H13V11H11ZM12,9Q12.425,9 12.713,8.712Q13,8.425 13,8Q13,7.575 12.713,7.287Q12.425,7 12,7Q11.575,7 11.288,7.287Q11,7.575 11,8Q11,8.425 11.288,8.712Q11.575,9 12,9ZM12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22ZM12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12ZM12,20Q15.325,20 17.663,17.663Q20,15.325 20,12Q20,8.675 17.663,6.337Q15.325,4 12,4Q8.675,4 6.338,6.337Q4,8.675 4,12Q4,15.325 6.338,17.663Q8.675,20 12,20Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml b/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml
index a22ca94..a9ace44 100644
--- a/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/helper_confirmation.xml
@@ -59,9 +59,9 @@
         android:gravity="end">
 
         <Button
-            android:id="@+id/btn_ok"
-            style="@style/VendorHelperOkButton"
-            android:text="@string/consent_ok" />
+            android:id="@+id/btn_back"
+            style="@style/VendorHelperBackButton"
+            android:text="@string/consent_back" />
 
     </LinearLayout>
 
diff --git a/packages/CompanionDeviceManager/res/layout/vendor_header.xml b/packages/CompanionDeviceManager/res/layout/vendor_header.xml
index 5f6aa94..c35f59e 100644
--- a/packages/CompanionDeviceManager/res/layout/vendor_header.xml
+++ b/packages/CompanionDeviceManager/res/layout/vendor_header.xml
@@ -43,7 +43,7 @@
 
     <ImageButton
         android:id="@+id/vendor_header_button"
-        style="?android:attr/actionOverflowButtonStyle"
+        android:background="@drawable/ic_info"
         android:layout_width="31dp"
         android:layout_height="32dp"
         android:layout_alignParentRight="true" />
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 67fc6c2..586a022 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -47,20 +47,11 @@
     <!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
     <string name="title_app_streaming">Allow &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone</string>
 
-    <!-- Description of the privileges the application will get if associated with the companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
-    <string name="summary_app_streaming" product="default">Let &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to provide &lt;strong&gt;<xliff:g id="device_name" example="Pixelbook Go">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this phone when connected.</string>
-
-    <!-- Description of the privileges the application will get if associated with the companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
-    <string name="summary_app_streaming" product="tablet">Let &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to provide &lt;strong&gt;<xliff:g id="device_name" example="Pixelbook Go">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this tablet when connected.</string>
-
-    <!-- Description of the privileges the application will get if associated with the companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
-    <string name="summary_app_streaming" product="device">Let &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to provide &lt;strong&gt;<xliff:g id="device_name" example="Pixelbook Go">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this device when connected.</string>
-
     <!-- Title of the helper dialog for APP_STREAMING profile [CHAR LIMIT=30]. -->
     <string name="helper_title_app_streaming">Cross-device services</string>
 
     <!-- Description of the helper dialog for APP_STREAMING profile. [CHAR LIMIT=NONE] -->
-    <string name="helper_summary_app_streaming">This service is used to stream apps between your devices</string>
+    <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="Chromebook">%2$s</xliff:g> to access your phone\u2019s photos, media, and notifications</string>
 
     <!-- ================= DEVICE_PROFILE_AUTOMOTIVE_PROJECTION ================= -->
 
@@ -94,10 +85,7 @@
     <string name="helper_title_computer">Google Play services</string>
 
     <!-- Description of the helper dialog for COMPUTER profile. [CHAR LIMIT=NONE] -->
-    <string name="helper_summary_computer" product="default">This service shares photos, media, and notifications form your phone to other devices</string>
-
-    <!-- Description of the helper dialog for COMPUTER profile. [CHAR LIMIT=NONE] -->
-    <string name="helper_summary_computer" product="tablet">This service shares photos, media, and notifications form your phone to other devices</string>
+    <string name="helper_summary_computer"> <xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="Chromebook">%2$s</xliff:g> to stream apps between your devices</string>
 
     <!-- ================= null profile ================= -->
 
@@ -115,8 +103,8 @@
     <!-- Negative button for the device-app association consent dialog [CHAR LIMIT=30] -->
     <string name="consent_no">Don\u2019t allow</string>
 
-    <!-- Ok button for the helper consent dialog [CHAR LIMIT=30] -->
-    <string name="consent_ok">OK</string>
+    <!-- Back button for the helper consent dialog [CHAR LIMIT=30] -->
+    <string name="consent_back">Back</string>
 
     <!-- ================== System data transfer ==================== -->
     <!-- Title of the permission sync confirmation dialog. [CHAR LIMIT=60] -->
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index a55f30c9..faa3032 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -57,13 +57,14 @@
         <item name="android:textColor">?android:attr/textColorSecondary</item>
     </style>
 
-    <style name="VendorHelperOkButton"
+    <style name="VendorHelperBackButton"
            parent="@android:style/Widget.Material.Button.Borderless.Colored">
-        <item name="android:layout_width">50dp</item>
+        <item name="android:layout_width">60dp</item>
         <item name="android:layout_height">36dp</item>
         <item name="android:layout_marginTop">20dp</item>
+        <item name="android:textAllCaps">false</item>
         <item name="android:textColor">@android:color/system_neutral1_900</item>
-        <item name="android:background">@drawable/helper_ok_button</item>
+        <item name="android:background">@drawable/helper_back_button</item>
     </style>
 
     <style name="PositiveButton"
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 5eeb167..b596816 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -38,6 +38,7 @@
 import static com.android.companiondevicemanager.Utils.getIcon;
 import static com.android.companiondevicemanager.Utils.getVendorHeaderIcon;
 import static com.android.companiondevicemanager.Utils.getVendorHeaderName;
+import static com.android.companiondevicemanager.Utils.hasVendorIcon;
 import static com.android.companiondevicemanager.Utils.prepareResultReceiverForIpc;
 
 import static java.util.Objects.requireNonNull;
@@ -285,7 +286,7 @@
         mVendorHeaderButton.setOnClickListener(this::onShowHelperDialog);
 
         if (mRequest.isSelfManaged()) {
-            initUiForSelfManagedAssociation(appLabel);
+            initUiForSelfManagedAssociation();
         } else if (mRequest.isSingleDevice()) {
             initUiForSingleDevice(appLabel);
         } else {
@@ -396,7 +397,7 @@
         finish();
     }
 
-    private void initUiForSelfManagedAssociation(CharSequence appLabel) {
+    private void initUiForSelfManagedAssociation() {
         if (DEBUG) Log.i(TAG, "initUiFor_SelfManaged_Association()");
 
         final CharSequence deviceName = mRequest.getDisplayName();
@@ -412,6 +413,12 @@
         try {
             vendorIcon = getVendorHeaderIcon(this, packageName, userId);
             vendorName = getVendorHeaderName(this, packageName, userId);
+
+            mVendorHeaderImage.setImageDrawable(vendorIcon);
+            if (hasVendorIcon(this, packageName, userId)) {
+                mVendorHeaderImage.setColorFilter(getResources().getColor(
+                                android.R.color.system_accent1_600, /* Theme= */null));
+            }
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
             setResultAndFinish(null, RESULT_INTERNAL_ERROR);
@@ -446,9 +453,7 @@
         mPermissionListRecyclerView.setLayoutManager(new LinearLayoutManager(this));
 
         mTitle.setText(title);
-        mVendorHeaderImage.setImageDrawable(vendorIcon);
         mVendorHeaderName.setText(vendorName);
-
         mDeviceListRecyclerView.setVisibility(View.GONE);
         mProfileIcon.setVisibility(View.GONE);
         mVendorHeader.setVisibility(View.VISIBLE);
@@ -586,8 +591,7 @@
     private void onShowHelperDialog(View view) {
         FragmentManager fragmentManager = getSupportFragmentManager();
         CompanionVendorHelperDialogFragment fragmentDialog =
-                CompanionVendorHelperDialogFragment.newInstance(mRequest.getPackageName(),
-                        mRequest.getUserId(), mRequest.getDeviceProfile());
+                CompanionVendorHelperDialogFragment.newInstance(mRequest);
 
         mAssociationConfirmationDialog.setVisibility(View.INVISIBLE);
 
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
index 728e5e5..f2f6cb0 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
@@ -23,6 +23,7 @@
 import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
 
 import android.annotation.Nullable;
+import android.companion.AssociationRequest;
 import android.content.DialogInterface;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
@@ -44,10 +45,7 @@
  */
 public class CompanionVendorHelperDialogFragment extends DialogFragment {
     private static final String TAG = CompanionVendorHelperDialogFragment.class.getSimpleName();
-
-    private static final String PACKAGE_NAME_EXTRA = "packageName";
-    private static final String DEVICE_PROFILE_EXTRA = "deviceProfile";
-    private static final String USER_ID_EXTRA = "userId";
+    private static final String ASSOCIATION_REQUEST_EXTRA = "association_request";
 
     private CompanionVendorHelperDialogListener mListener;
     // Only present for selfManaged devices.
@@ -63,15 +61,12 @@
 
     private CompanionVendorHelperDialogFragment() {}
 
-    static CompanionVendorHelperDialogFragment newInstance(String packageName,
-            int userId, String deviceProfile) {
+    static CompanionVendorHelperDialogFragment newInstance(AssociationRequest request) {
         CompanionVendorHelperDialogFragment fragmentDialog =
                 new CompanionVendorHelperDialogFragment();
 
         Bundle bundle = new Bundle();
-        bundle.putString(PACKAGE_NAME_EXTRA, packageName);
-        bundle.putInt(USER_ID_EXTRA, userId);
-        bundle.putString(DEVICE_PROFILE_EXTRA, deviceProfile);
+        bundle.putParcelable(ASSOCIATION_REQUEST_EXTRA, request);
         fragmentDialog.setArguments(bundle);
 
         return fragmentDialog;
@@ -102,9 +97,13 @@
         super.onViewCreated(view, savedInstanceState);
 
         Drawable applicationIcon;
-        String packageName = getArguments().getString(PACKAGE_NAME_EXTRA);
-        String deviceProfile = getArguments().getString(DEVICE_PROFILE_EXTRA);
-        int userId = getArguments().getInt(USER_ID_EXTRA);
+        AssociationRequest request = getArguments().getParcelable(
+                ASSOCIATION_REQUEST_EXTRA, AssociationRequest.class);
+
+        final String deviceProfile = request.getDeviceProfile();
+        final String packageName = request.getPackageName();
+        final CharSequence displayName = request.getDisplayName();
+        final int userId = request.getUserId();
 
         try {
             applicationIcon = getApplicationIcon(getContext(), packageName);
@@ -117,7 +116,7 @@
         mTitle = view.findViewById(R.id.helper_title);
         mSummary = view.findViewById(R.id.helper_summary);
         mAppIcon = view.findViewById(R.id.app_icon);
-        mButton = view.findViewById(R.id.btn_ok);
+        mButton = view.findViewById(R.id.btn_back);
 
         final Spanned title;
         final Spanned summary;
@@ -125,12 +124,14 @@
         switch (deviceProfile) {
             case DEVICE_PROFILE_APP_STREAMING:
                 title = getHtmlFromResources(getContext(), R.string.helper_title_app_streaming);
-                summary = getHtmlFromResources(getContext(), R.string.helper_summary_app_streaming);
+                summary = getHtmlFromResources(
+                        getContext(), R.string.helper_summary_app_streaming, title, displayName);
                 break;
 
             case DEVICE_PROFILE_COMPUTER:
                 title = getHtmlFromResources(getContext(), R.string.helper_title_computer);
-                summary = getHtmlFromResources(getContext(), R.string.helper_summary_computer);
+                summary = getHtmlFromResources(
+                        getContext(), R.string.helper_summary_computer, title, displayName);
                 break;
 
             default:
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
index 1852e82..fceca91 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
@@ -108,6 +108,18 @@
         return appInfo.metaData.getCharSequence(COMPANION_DEVICE_ACTIVITY_VENDOR_NAME, "");
     }
 
+    static boolean hasVendorIcon(@NonNull Context context,
+            @NonNull String packageName, int userId) throws PackageManager.NameNotFoundException {
+        final ApplicationInfo appInfo = getApplicationInfo(context, packageName, userId);
+        final Bundle bundle = appInfo.metaData;
+
+        if (bundle == null) {
+            return false;
+        } else {
+            return bundle.getInt(COMPANION_DEVICE_ACTIVITY_VENDOR_ICON) != 0;
+        }
+    }
+
     /**
      * Getting ApplicationInfo from meta-data.
      */
diff --git a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java
index 512fbce..209f372 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java
@@ -45,6 +45,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * The Network Service Discovery Manager class provides the API to discover services
@@ -285,8 +286,12 @@
     private final Context mContext;
 
     private int mListenerKey = FIRST_LISTENER_KEY;
+    @GuardedBy("mMapLock")
     private final SparseArray mListenerMap = new SparseArray();
+    @GuardedBy("mMapLock")
     private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<>();
+    @GuardedBy("mMapLock")
+    private final SparseArray<Executor> mExecutorMap = new SparseArray<>();
     private final Object mMapLock = new Object();
     // Map of listener key sent by client -> per-network discovery tracker
     @GuardedBy("mPerNetworkDiscoveryMap")
@@ -299,6 +304,7 @@
         final String mServiceType;
         final int mProtocolType;
         final DiscoveryListener mBaseListener;
+        final Executor mBaseExecutor;
         final ArrayMap<Network, DelegatingDiscoveryListener> mPerNetworkListeners =
                 new ArrayMap<>();
 
@@ -308,7 +314,8 @@
                 final DelegatingDiscoveryListener wrappedListener = new DelegatingDiscoveryListener(
                         network, mBaseListener);
                 mPerNetworkListeners.put(network, wrappedListener);
-                discoverServices(mServiceType, mProtocolType, network, wrappedListener);
+                discoverServices(mServiceType, mProtocolType, network, mBaseExecutor,
+                        wrappedListener);
             }
 
             @Override
@@ -355,9 +362,10 @@
         }
 
         private PerNetworkDiscoveryTracker(String serviceType, int protocolType,
-                DiscoveryListener baseListener) {
+                Executor baseExecutor, DiscoveryListener baseListener) {
             mServiceType = serviceType;
             mProtocolType = protocolType;
+            mBaseExecutor = baseExecutor;
             mBaseListener = baseListener;
         }
 
@@ -644,9 +652,11 @@
             final int key = message.arg2;
             final Object listener;
             final NsdServiceInfo ns;
+            final Executor executor;
             synchronized (mMapLock) {
                 listener = mListenerMap.get(key);
                 ns = mServiceMap.get(key);
+                executor = mExecutorMap.get(key);
             }
             if (listener == null) {
                 Log.d(TAG, "Stale key " + message.arg2);
@@ -657,56 +667,64 @@
             }
             switch (what) {
                 case DISCOVER_SERVICES_STARTED:
-                    String s = getNsdServiceInfoType((NsdServiceInfo) message.obj);
-                    ((DiscoveryListener) listener).onDiscoveryStarted(s);
+                    final String s = getNsdServiceInfoType((NsdServiceInfo) message.obj);
+                    executor.execute(() -> ((DiscoveryListener) listener).onDiscoveryStarted(s));
                     break;
                 case DISCOVER_SERVICES_FAILED:
                     removeListener(key);
-                    ((DiscoveryListener) listener).onStartDiscoveryFailed(getNsdServiceInfoType(ns),
-                            message.arg1);
+                    executor.execute(() -> ((DiscoveryListener) listener).onStartDiscoveryFailed(
+                            getNsdServiceInfoType(ns), message.arg1));
                     break;
                 case SERVICE_FOUND:
-                    ((DiscoveryListener) listener).onServiceFound((NsdServiceInfo) message.obj);
+                    executor.execute(() -> ((DiscoveryListener) listener).onServiceFound(
+                            (NsdServiceInfo) message.obj));
                     break;
                 case SERVICE_LOST:
-                    ((DiscoveryListener) listener).onServiceLost((NsdServiceInfo) message.obj);
+                    executor.execute(() -> ((DiscoveryListener) listener).onServiceLost(
+                            (NsdServiceInfo) message.obj));
                     break;
                 case STOP_DISCOVERY_FAILED:
                     // TODO: failure to stop discovery should be internal and retried internally, as
                     // the effect for the client is indistinguishable from STOP_DISCOVERY_SUCCEEDED
                     removeListener(key);
-                    ((DiscoveryListener) listener).onStopDiscoveryFailed(getNsdServiceInfoType(ns),
-                            message.arg1);
+                    executor.execute(() -> ((DiscoveryListener) listener).onStopDiscoveryFailed(
+                            getNsdServiceInfoType(ns), message.arg1));
                     break;
                 case STOP_DISCOVERY_SUCCEEDED:
                     removeListener(key);
-                    ((DiscoveryListener) listener).onDiscoveryStopped(getNsdServiceInfoType(ns));
+                    executor.execute(() -> ((DiscoveryListener) listener).onDiscoveryStopped(
+                            getNsdServiceInfoType(ns)));
                     break;
                 case REGISTER_SERVICE_FAILED:
                     removeListener(key);
-                    ((RegistrationListener) listener).onRegistrationFailed(ns, message.arg1);
+                    executor.execute(() -> ((RegistrationListener) listener).onRegistrationFailed(
+                            ns, message.arg1));
                     break;
                 case REGISTER_SERVICE_SUCCEEDED:
-                    ((RegistrationListener) listener).onServiceRegistered(
-                            (NsdServiceInfo) message.obj);
+                    executor.execute(() -> ((RegistrationListener) listener).onServiceRegistered(
+                            (NsdServiceInfo) message.obj));
                     break;
                 case UNREGISTER_SERVICE_FAILED:
                     removeListener(key);
-                    ((RegistrationListener) listener).onUnregistrationFailed(ns, message.arg1);
+                    executor.execute(() -> ((RegistrationListener) listener).onUnregistrationFailed(
+                            ns, message.arg1));
                     break;
                 case UNREGISTER_SERVICE_SUCCEEDED:
                     // TODO: do not unregister listener until service is unregistered, or provide
                     // alternative way for unregistering ?
                     removeListener(message.arg2);
-                    ((RegistrationListener) listener).onServiceUnregistered(ns);
+                    executor.execute(() -> ((RegistrationListener) listener).onServiceUnregistered(
+                            ns));
                     break;
                 case RESOLVE_SERVICE_FAILED:
                     removeListener(key);
-                    ((ResolveListener) listener).onResolveFailed(ns, message.arg1);
+                    executor.execute(() -> ((ResolveListener) listener).onResolveFailed(
+                            ns, message.arg1));
                     break;
                 case RESOLVE_SERVICE_SUCCEEDED:
                     removeListener(key);
-                    ((ResolveListener) listener).onServiceResolved((NsdServiceInfo) message.obj);
+                    executor.execute(() -> ((ResolveListener) listener).onServiceResolved(
+                            (NsdServiceInfo) message.obj));
                     break;
                 default:
                     Log.d(TAG, "Ignored " + message);
@@ -722,7 +740,7 @@
     }
 
     // Assert that the listener is not in the map, then add it and returns its key
-    private int putListener(Object listener, NsdServiceInfo s) {
+    private int putListener(Object listener, Executor e, NsdServiceInfo s) {
         checkListener(listener);
         final int key;
         synchronized (mMapLock) {
@@ -733,6 +751,7 @@
             key = nextListenerKey();
             mListenerMap.put(key, listener);
             mServiceMap.put(key, s);
+            mExecutorMap.put(key, e);
         }
         return key;
     }
@@ -741,6 +760,7 @@
         synchronized (mMapLock) {
             mListenerMap.remove(key);
             mServiceMap.remove(key);
+            mExecutorMap.remove(key);
         }
     }
 
@@ -779,12 +799,33 @@
      */
     public void registerService(NsdServiceInfo serviceInfo, int protocolType,
             RegistrationListener listener) {
+        registerService(serviceInfo, protocolType, Runnable::run, listener);
+    }
+
+    /**
+     * Register a service to be discovered by other services.
+     *
+     * <p> The function call immediately returns after sending a request to register service
+     * to the framework. The application is notified of a successful registration
+     * through the callback {@link RegistrationListener#onServiceRegistered} or a failure
+     * through {@link RegistrationListener#onRegistrationFailed}.
+     *
+     * <p> The application should call {@link #unregisterService} when the service
+     * registration is no longer required, and/or whenever the application is stopped.
+     * @param serviceInfo The service being registered
+     * @param protocolType The service discovery protocol
+     * @param executor Executor to run listener callbacks with
+     * @param listener The listener notifies of a successful registration and is used to
+     * unregister this service through a call on {@link #unregisterService}. Cannot be null.
+     */
+    public void registerService(@NonNull NsdServiceInfo serviceInfo, int protocolType,
+            @NonNull Executor executor, @NonNull RegistrationListener listener) {
         if (serviceInfo.getPort() <= 0) {
             throw new IllegalArgumentException("Invalid port number");
         }
         checkServiceInfo(serviceInfo);
         checkProtocol(protocolType);
-        int key = putListener(listener, serviceInfo);
+        int key = putListener(listener, executor, serviceInfo);
         try {
             mService.registerService(key, serviceInfo);
         } catch (RemoteException e) {
@@ -815,14 +856,6 @@
     }
 
     /**
-     * Same as {@link #discoverServices(String, int, Network, DiscoveryListener)} with a null
-     * {@link Network}.
-     */
-    public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
-        discoverServices(serviceType, protocolType, (Network) null, listener);
-    }
-
-    /**
      * Initiate service discovery to browse for instances of a service type. Service discovery
      * consumes network bandwidth and will continue until the application calls
      * {@link #stopServiceDiscovery}.
@@ -846,13 +879,45 @@
      * @param serviceType The service type being discovered. Examples include "_http._tcp" for
      * http services or "_ipp._tcp" for printers
      * @param protocolType The service discovery protocol
-     * @param network Network to discover services on, or null to discover on all available networks
      * @param listener  The listener notifies of a successful discovery and is used
      * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
      * Cannot be null. Cannot be in use for an active service discovery.
      */
+    public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
+        discoverServices(serviceType, protocolType, (Network) null, Runnable::run, listener);
+    }
+
+    /**
+     * Initiate service discovery to browse for instances of a service type. Service discovery
+     * consumes network bandwidth and will continue until the application calls
+     * {@link #stopServiceDiscovery}.
+     *
+     * <p> The function call immediately returns after sending a request to start service
+     * discovery to the framework. The application is notified of a success to initiate
+     * discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure
+     * through {@link DiscoveryListener#onStartDiscoveryFailed}.
+     *
+     * <p> Upon successful start, application is notified when a service is found with
+     * {@link DiscoveryListener#onServiceFound} or when a service is lost with
+     * {@link DiscoveryListener#onServiceLost}.
+     *
+     * <p> Upon failure to start, service discovery is not active and application does
+     * not need to invoke {@link #stopServiceDiscovery}
+     *
+     * <p> The application should call {@link #stopServiceDiscovery} when discovery of this
+     * service type is no longer required, and/or whenever the application is paused or
+     * stopped.
+     * @param serviceType The service type being discovered. Examples include "_http._tcp" for
+     * http services or "_ipp._tcp" for printers
+     * @param protocolType The service discovery protocol
+     * @param network Network to discover services on, or null to discover on all available networks
+     * @param executor Executor to run listener callbacks with
+     * @param listener  The listener notifies of a successful discovery and is used
+     * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
+     */
     public void discoverServices(@NonNull String serviceType, int protocolType,
-            @Nullable Network network, @NonNull DiscoveryListener listener) {
+            @Nullable Network network, @NonNull Executor executor,
+            @NonNull DiscoveryListener listener) {
         if (TextUtils.isEmpty(serviceType)) {
             throw new IllegalArgumentException("Service type cannot be empty");
         }
@@ -862,7 +927,7 @@
         s.setServiceType(serviceType);
         s.setNetwork(network);
 
-        int key = putListener(listener, s);
+        int key = putListener(listener, executor, s);
         try {
             mService.discoverServices(key, s);
         } catch (RemoteException e) {
@@ -899,18 +964,18 @@
      * themselves are encouraged to use this method instead of other overloads of
      * {@code discoverServices}, as they will receive proper notifications when a service becomes
      * available or unavailable due to network changes.
-     *
      * @param serviceType The service type being discovered. Examples include "_http._tcp" for
      * http services or "_ipp._tcp" for printers
      * @param protocolType The service discovery protocol
      * @param networkRequest Request specifying networks that should be considered when discovering
+     * @param executor Executor to run listener callbacks with
      * @param listener  The listener notifies of a successful discovery and is used
      * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
-     * Cannot be null. Cannot be in use for an active service discovery.
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public void discoverServices(@NonNull String serviceType, int protocolType,
-            @NonNull NetworkRequest networkRequest, @NonNull DiscoveryListener listener) {
+            @NonNull NetworkRequest networkRequest, @NonNull Executor executor,
+            @NonNull DiscoveryListener listener) {
         if (TextUtils.isEmpty(serviceType)) {
             throw new IllegalArgumentException("Service type cannot be empty");
         }
@@ -920,10 +985,10 @@
         NsdServiceInfo s = new NsdServiceInfo();
         s.setServiceType(serviceType);
 
-        final int baseListenerKey = putListener(listener, s);
+        final int baseListenerKey = putListener(listener, executor, s);
 
         final PerNetworkDiscoveryTracker discoveryInfo = new PerNetworkDiscoveryTracker(
-                serviceType, protocolType, listener);
+                serviceType, protocolType, executor, listener);
 
         synchronized (mPerNetworkDiscoveryMap) {
             mPerNetworkDiscoveryMap.put(baseListenerKey, discoveryInfo);
@@ -974,8 +1039,21 @@
      * Cannot be in use for an active service resolution.
      */
     public void resolveService(NsdServiceInfo serviceInfo, ResolveListener listener) {
+        resolveService(serviceInfo, Runnable::run, listener);
+    }
+
+    /**
+     * Resolve a discovered service. An application can resolve a service right before
+     * establishing a connection to fetch the IP and port details on which to setup
+     * the connection.
+     * @param serviceInfo service to be resolved
+     * @param executor Executor to run listener callbacks with
+     * @param listener to receive callback upon success or failure.
+     */
+    public void resolveService(@NonNull NsdServiceInfo serviceInfo,
+            @NonNull Executor executor, @NonNull ResolveListener listener) {
         checkServiceInfo(serviceInfo);
-        int key = putListener(listener, serviceInfo);
+        int key = putListener(listener, executor, serviceInfo);
         try {
             mService.resolveService(key, serviceInfo);
         } catch (RemoteException e) {
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index b439f84..998aeeab 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -23,9 +23,11 @@
 import android.os.Build;
 import android.os.MemoryFile;
 import android.os.ParcelFileDescriptor;
+import android.os.SystemProperties;
 import android.os.image.DynamicSystemManager;
 import android.service.persistentdata.PersistentDataBlockManager;
 import android.util.Log;
+import android.util.Range;
 import android.webkit.URLUtil;
 
 import org.json.JSONException;
@@ -48,7 +50,12 @@
 
     private static final String TAG = "InstallationAsyncTask";
 
-    private static final int READ_BUFFER_SIZE = 1 << 13;
+    private static final int MIN_SHARED_MEMORY_SIZE = 8 << 10; // 8KiB
+    private static final int MAX_SHARED_MEMORY_SIZE = 1024 << 10; // 1MiB
+    private static final int DEFAULT_SHARED_MEMORY_SIZE = 64 << 10; // 64KiB
+    private static final String SHARED_MEMORY_SIZE_PROP =
+            "dynamic_system.data_transfer.shared_memory.size";
+
     private static final long MIN_PROGRESS_TO_PUBLISH = 1 << 27;
 
     private static final List<String> UNSUPPORTED_PARTITIONS =
@@ -131,6 +138,7 @@
         void onResult(int resultCode, Throwable detail);
     }
 
+    private final int mSharedMemorySize;
     private final String mUrl;
     private final String mDsuSlot;
     private final String mPublicKey;
@@ -164,6 +172,11 @@
             Context context,
             DynamicSystemManager dynSystem,
             ProgressListener listener) {
+        mSharedMemorySize =
+                Range.create(MIN_SHARED_MEMORY_SIZE, MAX_SHARED_MEMORY_SIZE)
+                        .clamp(
+                                SystemProperties.getInt(
+                                        SHARED_MEMORY_SIZE_PROP, DEFAULT_SHARED_MEMORY_SIZE));
         mUrl = url;
         mDsuSlot = dsuSlot;
         mPublicKey = publicKey;
@@ -541,10 +554,10 @@
 
         Log.d(TAG, "Start installing: " + partitionName);
 
-        MemoryFile memoryFile = new MemoryFile("dsu_" + partitionName, READ_BUFFER_SIZE);
+        MemoryFile memoryFile = new MemoryFile("dsu_" + partitionName, mSharedMemorySize);
         ParcelFileDescriptor pfd = new ParcelFileDescriptor(memoryFile.getFileDescriptor());
 
-        mInstallationSession.setAshmem(pfd, READ_BUFFER_SIZE);
+        mInstallationSession.setAshmem(pfd, memoryFile.length());
 
         mPartitionName = partitionName;
         mPartitionSize = partitionSize;
@@ -553,10 +566,10 @@
 
         long prevInstalledSize = 0;
         long installedSize = 0;
-        byte[] bytes = new byte[READ_BUFFER_SIZE];
+        byte[] bytes = new byte[memoryFile.length()];
         int numBytesRead;
 
-        while ((numBytesRead = sis.read(bytes, 0, READ_BUFFER_SIZE)) != -1) {
+        while ((numBytesRead = sis.read(bytes, 0, bytes.length)) != -1) {
             if (isCancelled()) {
                 return;
             }
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java
index 4117d0f..7d23266 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java
@@ -133,36 +133,32 @@
         return mLeft == 0;
     }
 
-    /**
-     * It overrides the InputStream.read(byte[] buf)
-     */
-    public int read(byte[] buf) throws IOException {
+    @Override
+    public int read(byte[] buf, int off, int len) throws IOException {
         if (!mIsSparse) {
-            return mIn.read(buf);
+            return mIn.read(buf, off, len);
         }
         if (prepareChunk()) return -1;
         int n = -1;
         switch (mCur.mChunkType) {
             case SparseChunk.RAW:
-                n = mIn.read(buf, 0, (int) min(mLeft, buf.length));
+                n = mIn.read(buf, off, (int) min(mLeft, len));
                 mLeft -= n;
                 return n;
             case SparseChunk.DONTCARE:
-                n = (int) min(mLeft, buf.length);
-                Arrays.fill(buf, 0, n - 1, (byte) 0);
+                n = (int) min(mLeft, len);
+                Arrays.fill(buf, off, off + n, (byte) 0);
                 mLeft -= n;
                 return n;
             case SparseChunk.FILL:
                 // The FILL type is rarely used, so use a simple implmentation.
-                return super.read(buf);
+                return super.read(buf, off, len);
             default:
                 throw new IOException("Unsupported Chunk:" + mCur.toString());
         }
     }
 
-    /**
-     * It overrides the InputStream.read()
-     */
+    @Override
     public int read() throws IOException {
         if (!mIsSparse) {
             return mIn.read();
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index 543a5a0..77d6583 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -63,8 +63,7 @@
             return;
         }
 
-        mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback());
-        View view = mToolbardelegate.onCreateView(getLayoutInflater(), null);
+        View view = getToolbarDelegate().onCreateView(getLayoutInflater(), null);
         super.setContentView(view);
     }
 
@@ -107,7 +106,7 @@
 
     @Override
     public void setTitle(CharSequence title) {
-        mToolbardelegate.setTitle(title);
+        getToolbarDelegate().setTitle(title);
     }
 
     @Override
@@ -128,7 +127,7 @@
      */
     @Nullable
     public CollapsingToolbarLayout getCollapsingToolbarLayout() {
-        return mToolbardelegate.getCollapsingToolbarLayout();
+        return getToolbarDelegate().getCollapsingToolbarLayout();
     }
 
     /**
@@ -136,6 +135,13 @@
      */
     @Nullable
     public AppBarLayout getAppBarLayout() {
-        return mToolbardelegate.getAppBarLayout();
+        return getToolbarDelegate().getAppBarLayout();
+    }
+
+    private CollapsingToolbarDelegate getToolbarDelegate() {
+        if (mToolbardelegate == null) {
+            mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback());
+        }
+        return mToolbardelegate;
     }
 }
diff --git a/packages/SettingsLib/SettingsSpinner/Android.bp b/packages/SettingsLib/SettingsSpinner/Android.bp
index c5b2fe6..d3cc4d1 100644
--- a/packages/SettingsLib/SettingsSpinner/Android.bp
+++ b/packages/SettingsLib/SettingsSpinner/Android.bp
@@ -15,6 +15,7 @@
 
     static_libs: [
         "androidx.preference_preference",
+        "SettingsLibSettingsTheme",
     ],
 
     sdk_version: "system_current",
diff --git a/packages/SettingsLib/SettingsSpinner/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_text_color_secondary.xml
similarity index 66%
rename from packages/SettingsLib/SettingsSpinner/res/values-v31/colors.xml
rename to packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_text_color_secondary.xml
index 8fda876..98ea3ea 100644
--- a/packages/SettingsLib/SettingsSpinner/res/values-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_text_color_secondary.xml
@@ -14,7 +14,9 @@
      limitations under the License.
 -->
 
-<resources>
-    <color name="settingslib_spinner_title_color">@android:color/system_neutral1_900</color>
-    <color name="settingslib_spinner_dropdown_color">@android:color/system_neutral2_700</color>
-</resources>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+        android:alpha="?android:attr/disabledAlpha"
+        android:color="@color/settingslib_text_color_secondary_device_default"/>
+    <item android:color="@color/settingslib_text_color_secondary_device_default"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
index c4dbc5e..fe47e85 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
@@ -79,4 +79,7 @@
     <color name="settingslib_colorAccentSecondary">@color/settingslib_accent_secondary_device_default</color>
 
     <color name="settingslib_colorSurface">@color/settingslib_surface_light</color>
+
+    <color name="settingslib_spinner_title_color">@android:color/system_neutral1_900</color>
+    <color name="settingslib_spinner_dropdown_color">@android:color/system_neutral2_700</color>
 </resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
index cba1a9c..b12c6d2 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
@@ -24,7 +24,7 @@
 
     <style name="TextAppearance.PreferenceSummary.SettingsLib"
            parent="@android:style/TextAppearance.DeviceDefault.Small">
-        <item name="android:textColor">@color/settingslib_text_color_secondary_device_default</item>
+        <item name="android:textColor">@color/settingslib_text_color_secondary</item>
     </style>
 
     <style name="TextAppearance.CategoryTitle.SettingsLib"
@@ -54,4 +54,15 @@
         <item name="android:layout_marginTop">16dp</item>
         <item name="android:layout_marginBottom">8dp</item>
     </style>
+
+    <style name="SpinnerItem.SettingsLib"
+           parent="@android:style/Widget.DeviceDefault.TextView.SpinnerItem">
+        <item name="android:textColor">@color/settingslib_spinner_dropdown_color</item>
+    </style>
+
+    <style name="SpinnerDropDownItem.SettingsLib"
+           parent="@android:style/Widget.Material.DropDownItem.Spinner">
+        <item name="android:textColor">@color/settingslib_spinner_dropdown_color</item>
+    </style>
+
 </resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
index e9bbcc78..4f426a3 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
@@ -27,6 +27,8 @@
         <item name="android:switchStyle">@style/Switch.SettingsLib</item>
         <item name="android:progressBarStyleHorizontal">@style/HorizontalProgressBar.SettingsLib</item>
         <item name="android:spinnerStyle">@style/Spinner.SettingsLib</item>
+        <item name="android:spinnerItemStyle">@style/SpinnerItem.SettingsLib</item>
+        <item name="android:spinnerDropDownItemStyle">@style/SpinnerDropDownItem.SettingsLib</item>
     </style>
 
     <!-- Using in SubSettings page including injected settings page -->
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index ca064c9..1c1e1ba3 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -229,11 +229,11 @@
     <string name="bluetooth_active_no_battery_level">Active</string>
 
     <!-- Connected device settings. Message when the left-side hearing aid device is active. [CHAR LIMIT=NONE] -->
-    <string name="bluetooth_hearing_aid_left_active">Active, left ear</string>
+    <string name="bluetooth_hearing_aid_left_active">Active, left only</string>
     <!-- Connected device settings. Message when the right-side hearing aid device is active. [CHAR LIMIT=NONE] -->
-    <string name="bluetooth_hearing_aid_right_active">Active, right ear</string>
+    <string name="bluetooth_hearing_aid_right_active">Active, right only</string>
     <!-- Connected device settings. Message when the left-side and right-side hearing aids device are active. [CHAR LIMIT=NONE] -->
-    <string name="bluetooth_hearing_aid_left_and_right_active">Active, left and right ears</string>
+    <string name="bluetooth_hearing_aid_left_and_right_active">Active, left and right</string>
 
     <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the A2DP profile. -->
     <string name="bluetooth_profile_a2dp">Media audio</string>
@@ -398,9 +398,6 @@
     <!-- Title for a work profile. [CHAR LIMIT=25] -->
     <string name="managed_user_title">All work apps</string>
 
-    <!-- Title for Guest user [CHAR LIMIT=35] -->
-    <string name="user_guest">Guest</string>
-
     <!-- Manage apps, individual app screen, substituted for the application's label when the app's label CAN NOT be determined.-->
     <string name="unknown">Unknown</string>
 
@@ -1421,16 +1418,18 @@
     <!-- Title for the preference to enter the nickname of the user to display in the user switcher [CHAR LIMIT=25]-->
     <string name="user_nickname">Nickname</string>
 
+    <!-- Label for adding a new user in the user switcher [CHAR LIMIT=35] -->
+    <string name="user_add_user">Add user</string>
     <!-- Label for adding a new guest in the user switcher [CHAR LIMIT=35] -->
     <string name="guest_new_guest">Add guest</string>
     <!-- Label for exiting and removing the guest session in the user switcher [CHAR LIMIT=35] -->
     <string name="guest_exit_guest">Remove guest</string>
     <!-- Label for resetting guest session in the user switcher, which will remove all data from the current guest session [CHAR LIMIT=35] -->
     <string name="guest_reset_guest">Reset guest</string>
-    <!-- Name for the guest user [CHAR LIMIT=35] -->
-    <string name="guest_nickname">Guest</string>
     <!-- Title of the confirmation dialog to confirm resetting guest. [CHAR LIMIT=NONE] -->
     <string name="guest_reset_guest_dialog_title">Reset guest?</string>
+    <!-- Title of the confirmation dialog to confirm removing guest. [CHAR LIMIT=NONE] -->
+    <string name="guest_remove_guest_dialog_title">Remove guest?</string>
     <!-- Label for button in confirmation dialog when resetting guest user [CHAR LIMIT=35] -->
     <string name="guest_reset_guest_confirm_button">Reset</string>
     <!-- Status message indicating the device is in the process of resetting the guest user. [CHAR_LIMIT=NONE] -->
@@ -1442,6 +1441,15 @@
     <!-- Accessibility message for the photo selector which is a button/popup with the current photo [CHAR LIMIT=50] -->
     <string name="user_image_photo_selector">Select photo</string>
 
+    <!-- Content of the dialog shown when the user has failed to provide the device lock too many times and the device is wiped. [CHAR LIMIT=NONE] -->
+    <string name="failed_attempts_now_wiping_device">Too many incorrect attempts. This device\'s data will be deleted.</string>
+    <!-- Content of the dialog shown when the user has failed to provide the user lock too many times and the user is removed. [CHAR LIMIT=NONE] -->
+    <string name="failed_attempts_now_wiping_user">Too many incorrect attempts. This user will be deleted.</string>
+    <!-- Content of the dialog shown when the user has failed to provide the work lock too many times and the work profile is removed. [CHAR LIMIT=NONE] -->
+    <string name="failed_attempts_now_wiping_profile">Too many incorrect attempts. This work profile and its data will be deleted.</string>
+    <!-- Button label to dismiss the dialog telling the user the work profile has been wiped. [CHAR LIMIT=40] -->
+    <string name="failed_attempts_now_wiping_dialog_dismiss">Dismiss</string>
+
     <!-- List entry in developer settings to choose default device/system behavior for the app freezer [CHAR LIMIT=30]-->
     <string name="cached_apps_freezer_device_default">Device default</string>
     <!-- List entry in developer settings to disable the app freezer in developer settings [CHAR LIMIT=30]-->
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index f639022..aaa0114 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -134,7 +134,7 @@
                     ? getUpdatableManagedUserTitle(context)
                     : context.getString(R.string.managed_user_title);
         } else if (info.isGuest()) {
-            name = context.getString(R.string.user_guest);
+            name = context.getString(com.android.internal.R.string.guest_name);
         }
         if (name == null && info != null) {
             name = Integer.toString(info.id);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
new file mode 100644
index 0000000..3ce7a0e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+public final class BluetoothBroadcastUtils {
+
+    static final String SCHEME_BT_BROADCAST_METADATA = "BT:";
+
+    // BluetoothLeBroadcastMetadata
+    static final String PREFIX_BT_ADDRESS_TYPE = "T:";
+    static final String PREFIX_BT_DEVICE = "D:";
+    static final String PREFIX_BT_ADVERTISING_SID = "AS:";
+    static final String PREFIX_BT_BROADCAST_ID = "B:";
+    static final String PREFIX_BT_SYNC_INTERVAL = "SI:";
+    static final String PREFIX_BT_IS_ENCRYPTED = "E:";
+    static final String PREFIX_BT_BROADCAST_CODE = "C:";
+    static final String PREFIX_BT_PRESENTATION_DELAY = "D:";
+    static final String PREFIX_BT_SUBGROUPS = "G:";
+    static final String PREFIX_BT_ANDROID_VERSION = "V:";
+
+    // BluetoothLeBroadcastSubgroup
+    static final String PREFIX_BTSG_CODEC_ID = "CID:";
+    static final String PREFIX_BTSG_CODEC_CONFIG = "CC:";
+    static final String PREFIX_BTSG_AUDIO_CONTENT = "AC:";
+    static final String PREFIX_BTSG_CHANNEL_PREF = "CP:";
+    static final String PREFIX_BTSG_BROADCAST_CHANNEL = "BC:";
+
+    // BluetoothLeAudioCodecConfigMetadata
+    static final String PREFIX_BTCC_AUDIO_LOCATION = "AL:";
+    static final String PREFIX_BTCC_RAW_METADATA = "CCRM:";
+
+    // BluetoothLeAudioContentMetadata
+    static final String PREFIX_BTAC_PROGRAM_INFO = "PI:";
+    static final String PREFIX_BTAC_LANGUAGE = "L:";
+    static final String PREFIX_BTAC_RAW_METADATA = "ACRM:";
+
+    // BluetoothLeBroadcastChannel
+    static final String PREFIX_BTBC_CHANNEL_INDEX = "CI:";
+    static final String PREFIX_BTBC_CODEC_CONFIG = "BCCM:";
+
+    static final String DELIMITER_QR_CODE = ";";
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeAudioContentMetadata.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeAudioContentMetadata.java
new file mode 100644
index 0000000..9df0f4d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeAudioContentMetadata.java
@@ -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.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothLeAudioContentMetadata;
+
+public class LocalBluetoothLeAudioContentMetadata {
+
+    private static final String TAG = "LocalBluetoothLeAudioContentMetadata";
+    private final BluetoothLeAudioContentMetadata mContentMetadata;
+    private final String mLanguage;
+    private final byte[] mRawMetadata;
+    private String mProgramInfo;
+
+    LocalBluetoothLeAudioContentMetadata(BluetoothLeAudioContentMetadata contentMetadata) {
+        mContentMetadata = contentMetadata;
+        mProgramInfo = contentMetadata.getProgramInfo();
+        mLanguage = contentMetadata.getLanguage();
+        mRawMetadata = contentMetadata.getRawMetadata();
+    }
+
+    public void setProgramInfo(String programInfo) {
+        mProgramInfo = programInfo;
+    }
+
+    public String getProgramInfo() {
+        return mProgramInfo;
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
new file mode 100644
index 0000000..bb47c5e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.bluetooth;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothLeAudioContentMetadata;
+import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * LocalBluetoothLeBroadcast provides an interface between the Settings app
+ * and the functionality of the local {@link BluetoothLeBroadcast}.
+ */
+public class LocalBluetoothLeBroadcast implements BluetoothLeBroadcast.Callback {
+
+    private static final String TAG = "LocalBluetoothLeBroadcast";
+    private static final int UNKNOWN_VALUE_PLACEHOLDER = -1;
+    private static final boolean DEBUG = BluetoothUtils.D;
+
+    private BluetoothLeBroadcast mBluetoothLeBroadcast;
+    private LocalBluetoothProfileManager mProfileManager;
+    private BluetoothLeAudioContentMetadata mBluetoothLeAudioContentMetadata;
+    private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata;
+    private BluetoothLeAudioContentMetadata.Builder mBuilder;
+    private int mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER;
+    private boolean mIsProfileReady;
+
+    private final ServiceListener mServiceListener = new ServiceListener() {
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (profile == BluetoothProfile.LE_AUDIO_BROADCAST) {
+                if (DEBUG) {
+                    Log.d(TAG,"Bluetooth service connected");
+                }
+                mBluetoothLeBroadcast = (BluetoothLeBroadcast) proxy;
+                mProfileManager.callServiceConnectedListeners();
+                mIsProfileReady = true;
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+            if (profile == BluetoothProfile.LE_AUDIO_BROADCAST) {
+                if (DEBUG) {
+                    Log.d(TAG,"Bluetooth service disconnected");
+                }
+                mIsProfileReady = false;
+            }
+        }
+    };
+
+    LocalBluetoothLeBroadcast(Context context, LocalBluetoothProfileManager profileManager) {
+        mProfileManager = profileManager;
+        BluetoothAdapter.getDefaultAdapter().
+                getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST);
+        mBuilder = new BluetoothLeAudioContentMetadata.Builder();
+    }
+
+    public void startBroadcast(byte[] broadcastCode, String language,
+            String programInfo) {
+        if (DEBUG) {
+            if (mBluetoothLeBroadcast == null) {
+                Log.d(TAG, "The BluetoothLeBroadcast is null when starting the broadcast.");
+                return;
+            }
+            Log.d(TAG, "startBroadcast: language = " + language + " ,programInfo = " + programInfo);
+        }
+        buildContentMetadata(language, programInfo);
+        mBluetoothLeBroadcast.startBroadcast(mBluetoothLeAudioContentMetadata, broadcastCode);
+    }
+
+    public void stopBroadcast() {
+        if (DEBUG) {
+            if (mBluetoothLeBroadcast == null) {
+                Log.d(TAG, "The BluetoothLeBroadcast is null when stopping the broadcast.");
+                return;
+            }
+            Log.d(TAG, "stopBroadcast()");
+        }
+        mBluetoothLeBroadcast.stopBroadcast(mBroadcastId);
+    }
+
+    public void updateBroadcast(String language, String programInfo) {
+        if (DEBUG) {
+            if (mBluetoothLeBroadcast == null) {
+                Log.d(TAG, "The BluetoothLeBroadcast is null when updating the broadcast.");
+                return;
+            }
+            Log.d(TAG,
+                    "updateBroadcast: language = " + language + " ,programInfo = " + programInfo);
+        }
+        mBluetoothLeAudioContentMetadata = mBuilder.setProgramInfo(programInfo).build();
+        mBluetoothLeBroadcast.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata);
+    }
+
+    private void buildContentMetadata(String language, String programInfo) {
+        mBluetoothLeAudioContentMetadata = mBuilder.setLanguage(language).setProgramInfo(
+                programInfo).build();
+    }
+
+    public LocalBluetoothLeBroadcastMetadata getLocalBluetoothLeBroadcastMetaData() {
+        return new LocalBluetoothLeBroadcastMetadata(mBluetoothLeBroadcastMetadata);
+    }
+
+    @Override
+    public void onBroadcastStarted(int reason, int broadcastId) {
+        if (DEBUG) {
+            Log.d(TAG,
+                    "onBroadcastStarted(), reason = " + reason + ", broadcastId = " + broadcastId);
+        }
+    }
+
+    @Override
+    public void onBroadcastStartFailed(int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onBroadcastMetadataChanged(int broadcastId,
+            @NonNull BluetoothLeBroadcastMetadata metadata) {
+        if (DEBUG) {
+            Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = " + broadcastId);
+        }
+        mBluetoothLeBroadcastMetadata = metadata;
+    }
+
+    @Override
+    public void onBroadcastStopped(int reason, int broadcastId) {
+        if (DEBUG) {
+            Log.d(TAG,
+                    "onBroadcastStopped(), reason = " + reason + ", broadcastId = " + broadcastId);
+        }
+    }
+
+    @Override
+    public void onBroadcastStopFailed(int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onBroadcastStopFailed(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onBroadcastUpdated(int reason, int broadcastId) {
+        if (DEBUG) {
+            Log.d(TAG,
+                    "onBroadcastUpdated(), reason = " + reason + ", broadcastId = " + broadcastId);
+        }
+    }
+
+    @Override
+    public void onBroadcastUpdateFailed(int reason, int broadcastId) {
+        if (DEBUG) {
+            Log.d(TAG,
+                    "onBroadcastUpdateFailed(), reason = " + reason + ", broadcastId = "
+                            + broadcastId);
+        }
+    }
+
+    @Override
+    public void onPlaybackStarted(int reason, int broadcastId) {
+    }
+
+    @Override
+    public void onPlaybackStopped(int reason, int broadcastId) {
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
new file mode 100644
index 0000000..d904265
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.bluetooth;
+
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.content.Context;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * LocalBluetoothLeBroadcastAssistant provides an interface between the Settings app
+ * and the functionality of the local {@link BluetoothLeBroadcastAssistant}.
+ */
+public class LocalBluetoothLeBroadcastAssistant implements
+        BluetoothLeBroadcastAssistant.Callback {
+
+    private static final String TAG = "LocalBluetoothLeBroadcastAssistant";
+    private static final int UNKNOWN_VALUE_PLACEHOLDER = -1;
+    private static final boolean DEBUG = BluetoothUtils.D;
+
+    private LocalBluetoothProfileManager mProfileManager;
+    private BluetoothLeBroadcastAssistant mBluetoothLeBroadcastAssistant;
+    private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata;
+    private BluetoothLeBroadcastMetadata.Builder mBuilder;
+    private boolean mIsProfileReady;
+
+    private final ServiceListener mServiceListener = new ServiceListener() {
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (profile == BluetoothProfile.LE_AUDIO_BROADCAST) {
+                if (DEBUG) {
+                    Log.d(TAG,"Bluetooth service connected");
+                }
+                mBluetoothLeBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy;
+                mProfileManager.callServiceConnectedListeners();
+                mIsProfileReady = true;
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+            if (profile == BluetoothProfile.LE_AUDIO_BROADCAST) {
+                if (DEBUG) {
+                    Log.d(TAG,"Bluetooth service disconnected");
+                }
+                mIsProfileReady = false;
+            }
+        }
+    };
+
+    LocalBluetoothLeBroadcastAssistant(Context context,
+            LocalBluetoothProfileManager profileManager) {
+        mProfileManager = profileManager;
+        BluetoothAdapter.getDefaultAdapter().
+                getProfileProxy(context, mServiceListener,
+                        BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+        mBuilder = new BluetoothLeBroadcastMetadata.Builder();
+    }
+
+    public void addSource(@NonNull BluetoothDevice sink, int sourceAddressType,
+            int presentationDelayMicros, int sourceAdvertisingSid, int broadcastId,
+            int paSyncInterval, boolean isEncrypted, byte[] broadcastCode,
+            BluetoothDevice sourceDevice, boolean isGroupOp) {
+        if (DEBUG) {
+            Log.d(TAG, "addSource()");
+        }
+        if (mBluetoothLeBroadcastAssistant == null) {
+            Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
+            return ;
+        }
+        buildMetadata(sourceAddressType, presentationDelayMicros, sourceAdvertisingSid, broadcastId,
+                paSyncInterval, isEncrypted, broadcastCode, sourceDevice);
+        mBluetoothLeBroadcastAssistant.addSource(sink, mBluetoothLeBroadcastMetadata, isGroupOp);
+    }
+
+    private void buildMetadata(int sourceAddressType, int presentationDelayMicros,
+            int sourceAdvertisingSid, int broadcastId, int paSyncInterval, boolean isEncrypted,
+            byte[] broadcastCode, BluetoothDevice sourceDevice) {
+        mBluetoothLeBroadcastMetadata =
+                mBuilder.setSourceDevice(sourceDevice, sourceAddressType)
+                        .setSourceAdvertisingSid(sourceAdvertisingSid)
+                        .setBroadcastId(broadcastId)
+                        .setPaSyncInterval(paSyncInterval)
+                        .setEncrypted(isEncrypted)
+                        .setBroadcastCode(broadcastCode)
+                        .setPresentationDelayMicros(presentationDelayMicros)
+                        .build();
+    }
+
+    public void removeSource(@NonNull BluetoothDevice sink, int sourceId) {
+        if (DEBUG) {
+            Log.d(TAG, "removeSource()");
+        }
+        if (mBluetoothLeBroadcastAssistant == null) {
+            Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
+            return ;
+        }
+        mBluetoothLeBroadcastAssistant.removeSource(sink, sourceId);
+    }
+
+    public void startSearchingForSources(@NonNull List<android.bluetooth.le.ScanFilter> filters) {
+        if (DEBUG) {
+            Log.d(TAG, "startSearchingForSources()");
+        }
+        if (mBluetoothLeBroadcastAssistant == null) {
+            Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
+            return ;
+        }
+        mBluetoothLeBroadcastAssistant.startSearchingForSources(filters);
+    }
+
+    @Override
+    public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSourceAdded(), reason = " + reason + " , sourceId = " + sourceId);
+        }
+
+    }
+
+    @Override
+    public void onSourceAddFailed(@NonNull BluetoothDevice sink,
+            @NonNull BluetoothLeBroadcastMetadata source, int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSourceAddFailed(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onSourceRemoved(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSourceRemoved(), reason = " + reason + " , sourceId = " + sourceId);
+        }
+    }
+
+    @Override
+    public void onSourceRemoveFailed(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSourceRemoveFailed(), reason = " + reason + " , sourceId = " + sourceId);
+        }
+    }
+
+    @Override
+    public void onSearchStarted(int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSearchStarted(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onSearchStartFailed(int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSearchStartFailed(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onSearchStopped(int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSearchStopped(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onSearchStopFailed(int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSearchStopFailed(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {
+    }
+
+    @Override
+    public void onSourceModified(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+    }
+
+    @Override
+    public void onSourceModifyFailed(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+    }
+
+    @Override
+    public void onReceiveStateChanged(@NonNull BluetoothDevice sink, int sourceId,
+            @NonNull BluetoothLeBroadcastReceiveState state) {
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
new file mode 100644
index 0000000..cf4ba8b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
+import android.bluetooth.BluetoothLeAudioContentMetadata;
+import android.bluetooth.BluetoothLeBroadcastChannel;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastSubgroup;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class LocalBluetoothLeBroadcastMetadata {
+    private static final boolean DEBUG = BluetoothUtils.D;
+    private static final String TAG = "LocalBluetoothLeBroadcastMetadata";
+    private static final String METADATA_START = "<";
+    private static final String METADATA_END = ">";
+    private static final String PATTERN_REGEX = "<(.*?)>";
+
+    private BluetoothLeBroadcastSubgroup mSubgroup;
+    private List<BluetoothLeBroadcastSubgroup> mSubgroupList;
+
+    // BluetoothLeBroadcastMetadata
+    // Optional: Identity address type
+    private int mSourceAddressType;
+    // Optional: Must use identity address
+    private BluetoothDevice mSourceDevice;
+    private int mSourceAdvertisingSid;
+    private int mBroadcastId;
+    private int mPaSyncInterval;
+    private int mPresentationDelayMicros;
+    private boolean mIsEncrypted;
+    private byte[] mBroadcastCode;
+
+    // BluetoothLeBroadcastSubgroup
+    private long mCodecId;
+    private BluetoothLeAudioContentMetadata mContentMetadata;
+    private BluetoothLeAudioCodecConfigMetadata mConfigMetadata;
+    private BluetoothLeBroadcastChannel mChannel;
+
+    // BluetoothLeAudioCodecConfigMetadata
+    private long mAudioLocation;
+
+    // BluetoothLeAudioContentMetadata
+    private String mLanguage;
+    private String mProgramInfo;
+
+    // BluetoothLeBroadcastChannel
+    private boolean mIsSelected;
+    private int mChannelIndex;
+
+
+    LocalBluetoothLeBroadcastMetadata(BluetoothLeBroadcastMetadata metadata) {
+        mSourceAddressType = metadata.getSourceAddressType();
+        mSourceDevice = metadata.getSourceDevice();
+        mSourceAdvertisingSid = metadata.getSourceAdvertisingSid();
+        mBroadcastId = metadata.getBroadcastId();
+        mPaSyncInterval = metadata.getPaSyncInterval();
+        mIsEncrypted = metadata.isEncrypted();
+        mBroadcastCode = metadata.getBroadcastCode();
+        mPresentationDelayMicros = metadata.getPresentationDelayMicros();
+        mSubgroupList = metadata.getSubgroups();
+    }
+
+    public void setBroadcastCode(byte[] code) {
+        mBroadcastCode = code;
+    }
+
+    public int getBroadcastId() {
+        return mBroadcastId;
+    }
+
+    public String convertToQrCodeString() {
+        return new StringBuilder()
+                .append(BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA)
+                .append(BluetoothBroadcastUtils.PREFIX_BT_ADDRESS_TYPE)
+                .append(METADATA_START).append(mSourceAddressType).append(METADATA_END)
+                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+                .append(BluetoothBroadcastUtils.PREFIX_BT_DEVICE)
+                .append(METADATA_START).append(mSourceDevice).append(METADATA_END)
+                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+                .append(BluetoothBroadcastUtils.PREFIX_BT_ADVERTISING_SID)
+                .append(METADATA_START).append(mSourceAdvertisingSid).append(METADATA_END)
+                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+                .append(BluetoothBroadcastUtils.PREFIX_BT_BROADCAST_ID)
+                .append(METADATA_START).append(mBroadcastId).append(METADATA_END)
+                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+                .append(BluetoothBroadcastUtils.PREFIX_BT_SYNC_INTERVAL)
+                .append(METADATA_START).append(mPaSyncInterval).append(METADATA_END)
+                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+                .append(BluetoothBroadcastUtils.PREFIX_BT_IS_ENCRYPTED)
+                .append(METADATA_START).append(mIsEncrypted).append(METADATA_END)
+                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+                .append(BluetoothBroadcastUtils.PREFIX_BT_BROADCAST_CODE)
+                .append(METADATA_START).append(Arrays.toString(mBroadcastCode)).append(METADATA_END)
+                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+                .append(BluetoothBroadcastUtils.PREFIX_BT_PRESENTATION_DELAY)
+                .append(METADATA_START).append(mPresentationDelayMicros).append(METADATA_END)
+                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+                .append(BluetoothBroadcastUtils.PREFIX_BT_SUBGROUPS)
+                .append(METADATA_START).append(mSubgroupList).append(METADATA_END)
+                .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+                .toString();
+    }
+
+    /**
+     * Example : prefix is with the “BT:”, and end by the Android Version.
+     * BT:T:<1>;D:<00:11:22:AA:BB:CC>;AS:<1>;B:…;V:T;;
+     *
+     * @return BluetoothLeBroadcastMetadata
+     */
+    public BluetoothLeBroadcastMetadata convertToBroadcastMetadata(String qrCodeString) {
+        if (DEBUG) {
+            Log.d(TAG, "Convert " + qrCodeString + "to BluetoothLeBroadcastMetadata");
+        }
+        Pattern pattern = Pattern.compile(PATTERN_REGEX);
+        Matcher match = pattern.matcher(qrCodeString);
+        if (match.find()) {
+            ArrayList<String> resultList = new ArrayList<>();
+            resultList.add(match.group(1));
+            mSourceAddressType = Integer.parseInt(resultList.get(0));
+            mSourceDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
+                    resultList.get(1));
+            mSourceAdvertisingSid = Integer.parseInt(resultList.get(2));
+            mBroadcastId = Integer.parseInt(resultList.get(3));
+            mPaSyncInterval = Integer.parseInt(resultList.get(4));
+            mIsEncrypted = Boolean.valueOf(resultList.get(5));
+            mBroadcastCode = resultList.get(6).getBytes();
+            mPresentationDelayMicros = Integer.parseInt(resultList.get(7));
+            mSubgroup = convertToSubgroup(resultList.get(8));
+
+            if (DEBUG) {
+                Log.d(TAG, "Converted qrCodeString result: " + match.group());
+            }
+
+            return new BluetoothLeBroadcastMetadata.Builder()
+                    .setSourceDevice(mSourceDevice, mSourceAddressType)
+                    .setSourceAdvertisingSid(mSourceAdvertisingSid)
+                    .setBroadcastId(mBroadcastId)
+                    .setPaSyncInterval(mPaSyncInterval)
+                    .setEncrypted(mIsEncrypted)
+                    .setBroadcastCode(mBroadcastCode)
+                    .setPresentationDelayMicros(mPresentationDelayMicros)
+                    .addSubgroup(mSubgroup)
+                    .build();
+        } else {
+            if (DEBUG) {
+                Log.d(TAG,
+                        "The match fail, can not convert it to BluetoothLeBroadcastMetadata.");
+            }
+            return null;
+        }
+    }
+
+    private BluetoothLeBroadcastSubgroup convertToSubgroup(String subgroupString) {
+        if (DEBUG) {
+            Log.d(TAG, "Convert " + subgroupString + "to BluetoothLeBroadcastSubgroup");
+        }
+        Pattern pattern = Pattern.compile(PATTERN_REGEX);
+        Matcher match = pattern.matcher(subgroupString);
+        if (match.find()) {
+            ArrayList<String> resultList = new ArrayList<>();
+            resultList.add(match.group(1));
+            mCodecId = Long.getLong(resultList.get(0));
+            mConfigMetadata = convertToConfigMetadata(resultList.get(1));
+            mContentMetadata = convertToContentMetadata(resultList.get(2));
+            mChannel = convertToChannel(resultList.get(3), mConfigMetadata);
+
+            if (DEBUG) {
+                Log.d(TAG, "Converted subgroupString result: " + match.group());
+            }
+
+            return new BluetoothLeBroadcastSubgroup.Builder()
+                    .setCodecId(mCodecId)
+                    .setCodecSpecificConfig(mConfigMetadata)
+                    .setContentMetadata(mContentMetadata)
+                    .addChannel(mChannel)
+                    .build();
+        } else {
+            if (DEBUG) {
+                Log.d(TAG,
+                        "The match fail, can not convert it to BluetoothLeBroadcastSubgroup.");
+            }
+            return null;
+        }
+    }
+
+    private BluetoothLeAudioCodecConfigMetadata convertToConfigMetadata(
+            String configMetadataString) {
+        if (DEBUG) {
+            Log.d(TAG,
+                    "Convert " + configMetadataString + "to BluetoothLeAudioCodecConfigMetadata");
+        }
+        Pattern pattern = Pattern.compile(PATTERN_REGEX);
+        Matcher match = pattern.matcher(configMetadataString);
+        if (match.find()) {
+            ArrayList<String> resultList = new ArrayList<>();
+            resultList.add(match.group(1));
+            mAudioLocation = Long.getLong(resultList.get(0));
+
+            if (DEBUG) {
+                Log.d(TAG, "Converted configMetadataString result: " + match.group());
+            }
+
+            return new BluetoothLeAudioCodecConfigMetadata.Builder()
+                    .setAudioLocation(mAudioLocation)
+                    .build();
+        } else {
+            if (DEBUG) {
+                Log.d(TAG,
+                        "The match fail, can not convert it to "
+                                + "BluetoothLeAudioCodecConfigMetadata.");
+            }
+            return null;
+        }
+    }
+
+    private BluetoothLeAudioContentMetadata convertToContentMetadata(String contentMetadataString) {
+        if (DEBUG) {
+            Log.d(TAG, "Convert " + contentMetadataString + "to BluetoothLeAudioContentMetadata");
+        }
+        Pattern pattern = Pattern.compile(PATTERN_REGEX);
+        Matcher match = pattern.matcher(contentMetadataString);
+        if (match.find()) {
+            ArrayList<String> resultList = new ArrayList<>();
+            resultList.add(match.group(1));
+            mProgramInfo = resultList.get(0);
+            mLanguage = resultList.get(1);
+
+            if (DEBUG) {
+                Log.d(TAG, "Converted contentMetadataString result: " + match.group());
+            }
+
+            return new BluetoothLeAudioContentMetadata.Builder()
+                    .setProgramInfo(mProgramInfo)
+                    .setLanguage(mLanguage)
+                    .build();
+        } else {
+            if (DEBUG) {
+                Log.d(TAG,
+                        "The match fail, can not convert it to BluetoothLeAudioContentMetadata.");
+            }
+            return null;
+        }
+    }
+
+    private BluetoothLeBroadcastChannel convertToChannel(String channelString,
+            BluetoothLeAudioCodecConfigMetadata configMetadata) {
+        if (DEBUG) {
+            Log.d(TAG, "Convert " + channelString + "to BluetoothLeBroadcastChannel");
+        }
+        Pattern pattern = Pattern.compile(PATTERN_REGEX);
+        Matcher match = pattern.matcher(channelString);
+        if (match.find()) {
+            ArrayList<String> resultList = new ArrayList<>();
+            resultList.add(match.group(1));
+            mIsSelected = Boolean.valueOf(resultList.get(0));
+            mChannelIndex = Integer.parseInt(resultList.get(1));
+
+            if (DEBUG) {
+                Log.d(TAG, "Converted channelString result: " + match.group());
+            }
+
+            return new BluetoothLeBroadcastChannel.Builder()
+                    .setSelected(mIsSelected)
+                    .setChannelIndex(mChannelIndex)
+                    .setCodecMetadata(configMetadata)
+                    .build();
+        } else {
+            if (DEBUG) {
+                Log.d(TAG,
+                        "The match fail, can not convert it to BluetoothLeBroadcastChannel.");
+            }
+            return null;
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 9ca431d..1393365 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -465,6 +465,7 @@
         RoutingSessionInfo routingSessionInfo = getRoutingSessionInfo(packageName);
         if (routingSessionInfo != null) {
             infos.addAll(mRouterManager.getSelectedRoutes(routingSessionInfo));
+            infos.addAll(mRouterManager.getSelectableRoutes(routingSessionInfo));
         }
         final List<MediaRoute2Info> transferableRoutes =
                 mRouterManager.getTransferableRoutes(packageName);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index a50d4ae1..dc166b4 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -82,5 +82,6 @@
         Settings.Global.USER_PREFERRED_REFRESH_RATE,
         Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT,
         Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH,
+        Settings.Global.POWER_BUTTON_LONG_PRESS
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index c7673aa..f949f99 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -146,6 +146,7 @@
         Settings.Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS,
         Settings.Secure.TRUST_AGENTS_EXTEND_UNLOCK,
         Settings.Secure.UI_NIGHT_MODE,
+        Settings.Secure.UI_NIGHT_MODE_CUSTOM_TYPE,
         Settings.Secure.DARK_THEME_CUSTOM_START_TIME,
         Settings.Secure.DARK_THEME_CUSTOM_END_TIME,
         Settings.Secure.LOCK_SCREEN_WHEN_TRUST_LOST,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index cbd71c0..2bdf819 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -253,6 +253,7 @@
         VALIDATORS.put(Secure.ODI_CAPTIONS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.DARK_MODE_DIALOG_SEEN, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.UI_NIGHT_MODE, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.UI_NIGHT_MODE_CUSTOM_TYPE, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.DARK_THEME_CUSTOM_START_TIME, NONE_NEGATIVE_LONG_VALIDATOR);
         VALIDATORS.put(Secure.DARK_THEME_CUSTOM_END_TIME, NONE_NEGATIVE_LONG_VALIDATOR);
         VALIDATORS.put(Secure.GLOBAL_ACTIONS_PANEL_ENABLED, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 6cfcb51..440bb67 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -53,6 +53,13 @@
     private static final String SETTING_ORIGINAL_KEY_SUFFIX = "_original";
     private static final float FLOAT_TOLERANCE = 0.01f;
 
+    /** See frameworks/base/core/res/res/values/config.xml#config_longPressOnPowerBehavior **/
+    private static final int LONG_PRESS_POWER_NOTHING = 0;
+    private static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
+    private static final int LONG_PRESS_POWER_FOR_ASSISTANT = 5;
+    /** See frameworks/base/core/res/res/values/config.xml#config_keyChordPowerVolumeUp **/
+    private static final int KEY_CHORD_POWER_VOLUME_UP_GLOBAL_ACTIONS = 2;
+
     private Context mContext;
     private AudioManager mAudioManager;
     private TelephonyManager mTelephonyManager;
@@ -190,6 +197,9 @@
                         && !displayColorModeVendorModeHintsMatch) {
                     return;
                 }
+            } else if (Settings.Global.POWER_BUTTON_LONG_PRESS.equals(name)) {
+                setLongPressPowerBehavior(cr, value);
+                return;
             }
 
             // Default case: write the restored value to settings
@@ -374,6 +384,67 @@
         }
     }
 
+    /**
+     * Correctly sets long press power button Behavior.
+     *
+     * The issue is that setting for LongPressPower button Behavior is not available on all devices
+     * and actually changes default Behavior of two properties - the long press power button
+     * and volume up + power button combo. OEM can also reconfigure these Behaviors in config.xml,
+     * so we need to be careful that we don't irreversibly change power button Behavior when
+     * restoring. Or switch to a non-default button Behavior.
+     */
+    private void setLongPressPowerBehavior(ContentResolver cr, String value) {
+        // We will not restore the value if the long press power setting option is unavailable.
+        if (!mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_longPressOnPowerForAssistantSettingAvailable)) {
+            return;
+        }
+
+        int longPressOnPowerBehavior;
+        try {
+            longPressOnPowerBehavior = Integer.parseInt(value);
+        } catch (NumberFormatException e) {
+            return;
+        }
+
+        if (longPressOnPowerBehavior < LONG_PRESS_POWER_NOTHING
+                || longPressOnPowerBehavior > LONG_PRESS_POWER_FOR_ASSISTANT) {
+            return;
+        }
+
+        // When user enables long press power for Assistant, we also switch the meaning
+        // of Volume Up + Power key chord to the "Show power menu" option.
+        // If the user disables long press power for Assistant, we switch back to default OEM
+        // Behavior configured in config.xml. If the default Behavior IS "LPP for Assistant",
+        // then we fall back to "Long press for Power Menu" Behavior.
+        if (longPressOnPowerBehavior == LONG_PRESS_POWER_FOR_ASSISTANT) {
+            Settings.Global.putInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS,
+                    LONG_PRESS_POWER_FOR_ASSISTANT);
+            Settings.Global.putInt(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
+                    KEY_CHORD_POWER_VOLUME_UP_GLOBAL_ACTIONS);
+        } else {
+            // We're restoring "LPP for Assistant Disabled" state, prefer OEM config.xml Behavior
+            // if possible.
+            int longPressOnPowerDeviceBehavior = mContext.getResources().getInteger(
+                    com.android.internal.R.integer.config_longPressOnPowerBehavior);
+            if (longPressOnPowerDeviceBehavior == LONG_PRESS_POWER_FOR_ASSISTANT) {
+                // The default on device IS "LPP for Assistant Enabled" so fall back to power menu.
+                Settings.Global.putInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS,
+                        LONG_PRESS_POWER_GLOBAL_ACTIONS);
+            } else {
+                // The default is non-Assistant Behavior, so restore that default.
+                Settings.Global.putInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS,
+                        longPressOnPowerDeviceBehavior);
+            }
+
+            // Clear and restore default power + volume up Behavior as well.
+            int powerVolumeUpDefaultBehavior = mContext.getResources().getInteger(
+                    com.android.internal.R.integer.config_keyChordPowerVolumeUp);
+            Settings.Global.putInt(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
+                    powerVolumeUpDefaultBehavior);
+        }
+    }
+
     /* package */ byte[] getLocaleData() {
         Configuration conf = mContext.getResources().getConfiguration();
         return conf.getLocales().toLanguageTags().getBytes();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index aa6661b..fd7554f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -618,7 +618,7 @@
             return;
         }
         HistoricalOperation operation = new HistoricalOperation(
-                SystemClock.elapsedRealtime(), type,
+                System.currentTimeMillis(), type,
                 setting != null ? new Setting(setting) : null);
         if (mNextHistoricalOpIdx >= mHistoricalOperations.size()) {
             mHistoricalOperations.add(operation);
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
index 7baa226..4f7b494 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
@@ -16,6 +16,8 @@
 
 package com.android.providers.settings;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertEquals;
 
 import static org.mockito.ArgumentMatchers.eq;
@@ -27,13 +29,19 @@
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.res.Resources;
 import android.media.AudioManager;
 import android.net.Uri;
 import android.os.LocaleList;
+import android.provider.Settings;
 import android.telephony.TelephonyManager;
 
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.R;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -52,20 +60,28 @@
     private SettingsHelper mSettingsHelper;
 
     @Mock private Context mContext;
+    @Mock private Resources mResources;
     @Mock private ContentResolver mContentResolver;
     @Mock private AudioManager mAudioManager;
     @Mock private TelephonyManager mTelephonyManager;
 
     @Before
     public void setUp() {
+        clearLongPressPowerValues();
         MockitoAnnotations.initMocks(this);
         when(mContext.getSystemService(eq(Context.AUDIO_SERVICE))).thenReturn(mAudioManager);
         when(mContext.getSystemService(eq(Context.TELEPHONY_SERVICE))).thenReturn(
                 mTelephonyManager);
+        when(mContext.getResources()).thenReturn(mResources);
 
         mSettingsHelper = spy(new SettingsHelper(mContext));
     }
 
+    @After
+    public void tearDown() {
+        clearLongPressPowerValues();
+    }
+
     @Test
     public void testOnBackupValue_settingReplaced_returnsRealValue() {
         when(mSettingsHelper.isReplacedSystemSetting(eq(SETTING_KEY))).thenReturn(true);
@@ -92,6 +108,110 @@
     }
 
     @Test
+    public void testRestoreValue_lppForAssistantEnabled_updatesValue() {
+        ContentResolver cr =
+                InstrumentationRegistry.getInstrumentation().getTargetContext()
+                        .getContentResolver();
+        when(mResources.getBoolean(
+                R.bool.config_longPressOnPowerForAssistantSettingAvailable)).thenReturn(
+                true);
+
+        mSettingsHelper.restoreValue(mContext, cr, new ContentValues(), Uri.EMPTY,
+                Settings.Global.POWER_BUTTON_LONG_PRESS, "5", 0);
+
+        assertThat(
+                Settings.Global.getInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS, -1))
+                    .isEqualTo(5);
+        assertThat(Settings.Global.getInt(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
+                -1)).isEqualTo(2);
+    }
+
+    @Test
+    public void testRestoreValue_lppForAssistantNotEnabled_updatesValueToDefaultConfig() {
+        ContentResolver cr =
+                InstrumentationRegistry.getInstrumentation().getTargetContext()
+                        .getContentResolver();
+        when(mResources.getBoolean(
+                R.bool.config_longPressOnPowerForAssistantSettingAvailable)).thenReturn(
+                true);
+
+        when(mResources.getInteger(
+                R.integer.config_longPressOnPowerBehavior)).thenReturn(
+                1);
+        when(mResources.getInteger(
+                R.integer.config_keyChordPowerVolumeUp)).thenReturn(
+                1);
+
+        mSettingsHelper.restoreValue(mContext, cr, new ContentValues(), Uri.EMPTY,
+                Settings.Global.POWER_BUTTON_LONG_PRESS, "2", 0);
+
+        assertThat(
+                Settings.Global.getInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS, -1))
+                .isEqualTo(1);
+        assertThat(Settings.Global.getInt(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
+                -1)).isEqualTo(1);
+    }
+
+    @Test
+    public void testRestoreValue_lppForAssistantNotEnabledDefaultConfig_updatesValue() {
+        ContentResolver cr =
+                InstrumentationRegistry.getInstrumentation().getTargetContext()
+                        .getContentResolver();
+        when(mResources.getBoolean(
+                R.bool.config_longPressOnPowerForAssistantSettingAvailable)).thenReturn(
+                true);
+
+        when(mResources.getInteger(
+                R.integer.config_longPressOnPowerBehavior)).thenReturn(
+                5);
+        when(mResources.getInteger(
+                R.integer.config_keyChordPowerVolumeUp)).thenReturn(
+                1);
+
+        mSettingsHelper.restoreValue(mContext, cr, new ContentValues(), Uri.EMPTY,
+                Settings.Global.POWER_BUTTON_LONG_PRESS, "2", 0);
+
+        assertThat(
+                Settings.Global.getInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS, -1))
+                    .isEqualTo(1);
+        assertThat(Settings.Global.getInt(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
+                -1)).isEqualTo(1);
+    }
+
+    @Test
+    public void testRestoreValue_lppForAssistantNotAvailable_doesNotRestore() {
+        ContentResolver cr =
+                InstrumentationRegistry.getInstrumentation().getTargetContext()
+                        .getContentResolver();
+        when(mResources.getBoolean(
+                R.bool.config_longPressOnPowerForAssistantSettingAvailable)).thenReturn(
+                false);
+
+        mSettingsHelper.restoreValue(mContext, cr, new ContentValues(), Uri.EMPTY,
+                Settings.Global.POWER_BUTTON_LONG_PRESS, "5", 0);
+
+        assertThat((Settings.Global.getInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS,
+                -1))).isEqualTo(-1);
+    }
+
+
+    @Test
+    public void testRestoreValue_lppForAssistantInvalid_doesNotRestore() {
+        ContentResolver cr =
+                InstrumentationRegistry.getInstrumentation().getTargetContext()
+                        .getContentResolver();
+        when(mResources.getBoolean(
+                R.bool.config_longPressOnPowerForAssistantSettingAvailable)).thenReturn(
+                false);
+
+        mSettingsHelper.restoreValue(mContext, cr, new ContentValues(), Uri.EMPTY,
+                Settings.Global.POWER_BUTTON_LONG_PRESS, "trees", 0);
+
+        assertThat((Settings.Global.getInt(cr, Settings.Global.POWER_BUTTON_LONG_PRESS,
+                -1))).isEqualTo(-1);
+    }
+
+    @Test
     public void testResolveLocales() throws Exception {
         // Empty string from backup server
         assertEquals(LocaleList.forLanguageTags("en-US"),
@@ -184,4 +304,11 @@
                         LocaleList.forLanguageTags("en-US"),  // current
                         new String[] { "he-IL", "id-ID", "yi" }));  // supported
     }
+
+    private void clearLongPressPowerValues() {
+        ContentResolver cr = InstrumentationRegistry.getInstrumentation().getTargetContext()
+                .getContentResolver();
+        Settings.Global.putString(cr, Settings.Global.POWER_BUTTON_LONG_PRESS, null);
+        Settings.Global.putString(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP, null);
+    }
 }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewBoundAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
similarity index 99%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/ViewBoundAnimator.kt
rename to packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 5593fdf..c480197 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewBoundAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -29,7 +29,7 @@
  * A class that allows changes in bounds within a view hierarchy to animate seamlessly between the
  * start and end state.
  */
-class ViewBoundAnimator {
+class ViewHierarchyAnimator {
     // TODO(b/221418522): make this private once it can't be passed as an arg anymore.
     enum class Bound(val label: String, val overrideTag: Int) {
         LEFT("left", R.id.tag_override_left) {
diff --git a/packages/SystemUI/docs/media-controls.md b/packages/SystemUI/docs/media-controls.md
index 579f453..112e216 100644
--- a/packages/SystemUI/docs/media-controls.md
+++ b/packages/SystemUI/docs/media-controls.md
@@ -41,7 +41,7 @@
       * SeekBarViewModel.kt
          * Implements its own `computePosition()` for the seekbar (to avoid continually polling the `PlaybackState`, which involves binder calls)
          * Does some touch falsing (ignore flings, require drags to start near the thumb - otherwise users would often accidentally trigger the seekbar when they meant to move the carousel or shade)
-      * PlayerViewHolder.kt
+      * MediaViewHolder.kt
          * Holds references to the UI elements in the panel
 * Animation support:
    * MediaHierarchyManager.kt
diff --git a/packages/SystemUI/res/drawable/new_fgs_dot.xml b/packages/SystemUI/res/drawable/new_fgs_dot.xml
index 759ddaf..3669e1d 100644
--- a/packages/SystemUI/res/drawable/new_fgs_dot.xml
+++ b/packages/SystemUI/res/drawable/new_fgs_dot.xml
@@ -15,8 +15,9 @@
 ** limitations under the License.
 -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:shape="oval"
     android:width="12dp"
     android:height="12dp">
-    <solid android:color="@*android:color/red" />
+    <solid android:color="?androidprv:attr/colorAccentTertiary" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_media_button_background.xml b/packages/SystemUI/res/drawable/qs_media_outline_button.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/qs_media_button_background.xml
rename to packages/SystemUI/res/drawable/qs_media_outline_button.xml
diff --git a/packages/SystemUI/res/drawable/qs_media_solid_button.xml b/packages/SystemUI/res/drawable/qs_media_solid_button.xml
new file mode 100644
index 0000000..baa4aae
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_media_solid_button.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
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+        android:shape="rectangle">
+        <solid android:color="?androidprv:attr/colorAccentPrimaryVariant" />
+        <corners android:radius="24dp"/>
+        <padding
+            android:left="16dp"
+            android:right="16dp"
+            android:top="8dp"
+            android:bottom="8dp" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index f030f31..6cf3215 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -264,7 +264,7 @@
     <TextView
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/qs_media_padding"
+        android:layout_marginTop="0dp"
         android:layout_marginStart="@dimen/qs_media_padding"
         android:layout_marginEnd="@dimen/qs_media_padding"
         android:id="@+id/remove_text"
@@ -274,64 +274,56 @@
         android:marqueeRepeatLimit="marquee_forever"
         android:text="@string/controls_media_close_session"
         android:gravity="center_horizontal|top"
-        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/settings"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintBottom_toTopOf="@id/cancel" />
 
-    <FrameLayout
+    <ImageButton
         android:id="@+id/settings"
-        android:background="@drawable/qs_media_light_source"
+        android:src="@drawable/ic_settings"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
+        android:layout_marginTop="4dp"
+        android:layout_marginEnd="4dp"
+        android:background="@drawable/qs_media_light_source"
+        android:contentDescription="@string/controls_media_settings_button"
+        android:layout_gravity="top"
+        app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
+        app:layout_constraintHeight_min="@dimen/min_clickable_item_size"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+    </ImageButton>
+
+    <FrameLayout
+        android:id="@+id/dismiss"
+        android:background="@drawable/qs_media_light_source"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         android:layout_marginStart="@dimen/qs_media_padding"
         android:layout_marginEnd="@dimen/qs_media_action_spacing"
         android:layout_marginBottom="@dimen/qs_media_padding"
         app:layout_constrainedWidth="true"
         app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
         app:layout_constraintHeight_min="@dimen/min_clickable_item_size"
-        app:layout_constraintHorizontal_chainStyle="spread_inside"
+        app:layout_constraintHorizontal_chainStyle="packed"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toStartOf="@id/cancel"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintTop_toBottomOf="@id/remove_text">
         <TextView
-            android:id="@+id/settings_text"
+            android:id="@+id/dismiss_text"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_gravity="center|bottom"
-            style="@style/MediaPlayer.OutlineButton"
-            android:text="@string/controls_media_settings_button" />
+            android:layout_gravity="center|top"
+            style="@style/MediaPlayer.SolidButton"
+            android:background="@drawable/qs_media_solid_button"
+            android:text="@string/controls_media_dismiss_button" />
     </FrameLayout>
-
     <FrameLayout
         android:id="@+id/cancel"
         android:background="@drawable/qs_media_light_source"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/qs_media_action_spacing"
-        android:layout_marginEnd="@dimen/qs_media_action_spacing"
-        android:layout_marginBottom="@dimen/qs_media_padding"
-        app:layout_constrainedWidth="true"
-        app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
-        app:layout_constraintHeight_min="@dimen/min_clickable_item_size"
-        app:layout_constraintStart_toEndOf="@id/settings"
-        app:layout_constraintEnd_toStartOf="@id/dismiss"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/remove_text">
-        <TextView
-            android:id="@+id/cancel_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center|bottom"
-            style="@style/MediaPlayer.OutlineButton"
-            android:text="@string/cancel" />
-    </FrameLayout>
-
-    <FrameLayout
-        android:id="@+id/dismiss"
-        android:background="@drawable/qs_media_light_source"
-        android:layout_width="0dp"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginStart="@dimen/qs_media_action_spacing"
         android:layout_marginEnd="@dimen/qs_media_padding"
@@ -339,16 +331,16 @@
         app:layout_constrainedWidth="true"
         app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
         app:layout_constraintHeight_min="@dimen/min_clickable_item_size"
-        app:layout_constraintStart_toEndOf="@id/cancel"
+        app:layout_constraintStart_toEndOf="@id/dismiss"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintTop_toBottomOf="@id/remove_text">
         <TextView
-            android:id="@+id/dismiss_text"
+            android:id="@+id/cancel_text"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_gravity="center|bottom"
+            android:layout_gravity="center|top"
             style="@style/MediaPlayer.OutlineButton"
-            android:text="@string/controls_media_dismiss_button" />
+            android:text="@string/cancel" />
     </FrameLayout>
 </com.android.systemui.util.animation.TransitionLayout>
diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml
deleted file mode 100644
index 9471b9f..0000000
--- a/packages/SystemUI/res/layout/media_view.xml
+++ /dev/null
@@ -1,300 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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
-  -->
-
-<!-- Layout for media controls inside QSPanel carousel -->
-<com.android.systemui.util.animation.TransitionLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/qs_media_controls"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:clipChildren="false"
-    android:clipToPadding="false"
-    android:gravity="center_horizontal|fill_vertical"
-    android:forceHasOverlappingRendering="false"
-    android:background="@drawable/qs_media_background"
-    android:theme="@style/MediaPlayer">
-
-    <androidx.constraintlayout.widget.Guideline
-        android:id="@+id/center_vertical_guideline"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        app:layout_constraintGuide_percent="0.6" />
-
-    <androidx.constraintlayout.widget.Guideline
-        android:id="@+id/center_horizontal_guideline"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        app:layout_constraintGuide_begin="48dp" />
-
-    <!-- As per Material Design on Biderectionality, this is forced to LTR in code -->
-    <FrameLayout
-        android:id="@+id/notification_media_progress_time"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:forceHasOverlappingRendering="false">
-        <!-- width is set to "match_parent" to avoid extra layout calls -->
-        <TextView
-            android:id="@+id/media_elapsed_time"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentStart="true"
-            android:fontFamily="@*android:string/config_bodyFontFamily"
-            android:gravity="start"
-            android:textSize="12sp" />
-
-        <TextView
-            android:id="@+id/media_total_time"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentEnd="true"
-            android:fontFamily="@*android:string/config_bodyFontFamily"
-            android:gravity="end"
-            android:textSize="12sp" />
-    </FrameLayout>
-
-    <!--  Actions must be ordered left-to-right even in RTL layout.  However, they appear in a chain
-    with the artist name, and must as a group appear at the end of that chain.  This is
-    accomplished by having all actions appear in a LTR chain within the parent, and then biasing it
-    to the right side, then this barrier is used to bound the text views.  -->
-    <androidx.constraintlayout.widget.Barrier
-        android:id="@+id/media_action_barrier"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:orientation="vertical"
-        app:layout_constraintTop_toBottomOf="@id/header_title"
-        app:barrierDirection="start"
-        app:constraint_referenced_ids="action0,action1,action2,action3,action4"
-         />
-
-    <ImageButton
-        android:id="@+id/action0"
-        style="@style/MediaPlayer.Action"
-        android:layout_width="48dp"
-        android:layout_height="48dp" />
-
-    <ImageButton
-        android:id="@+id/action1"
-        style="@style/MediaPlayer.Action"
-        android:layout_width="48dp"
-        android:layout_height="48dp" />
-
-    <ImageButton
-        android:id="@+id/action2"
-        style="@style/MediaPlayer.Action"
-        android:layout_width="48dp"
-        android:layout_height="48dp" />
-
-    <ImageButton
-        android:id="@+id/action3"
-        style="@style/MediaPlayer.Action"
-        android:layout_width="48dp"
-        android:layout_height="48dp" />
-
-    <ImageButton
-        android:id="@+id/action4"
-        style="@style/MediaPlayer.Action"
-        android:layout_width="48dp"
-        android:layout_height="48dp" />
-
-    <!-- Album Art -->
-    <ImageView
-        android:id="@+id/album_art"
-        android:layout_width="@dimen/qs_media_album_size"
-        android:layout_height="@dimen/qs_media_album_size"
-        android:layout_gravity="center_vertical"
-        style="@style/MediaPlayer.Album"
-        android:background="@drawable/qs_media_art_background"
-        android:clipToOutline="true" />
-
-    <!-- Seamless Output Switcher -->
-    <LinearLayout
-        android:id="@+id/media_seamless"
-        android:layout_width="0dp"
-        android:layout_height="48dp"
-        android:orientation="horizontal"
-        android:gravity="top|end"
-        android:paddingTop="@dimen/qs_media_padding"
-        android:paddingEnd="@dimen/qs_media_padding"
-        android:background="@drawable/qs_media_light_source"
-        android:forceHasOverlappingRendering="false">
-        <LinearLayout
-            android:id="@+id/media_seamless_button"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:minHeight="@dimen/qs_seamless_height"
-            android:theme="@style/MediaPlayer.SolidButton"
-            android:background="@drawable/qs_media_seamless_background"
-            android:orientation="horizontal"
-            android:contentDescription="@string/quick_settings_media_device_label">
-            <ImageView
-                android:id="@+id/media_seamless_image"
-                android:layout_width="@dimen/qs_seamless_icon_size"
-                android:layout_height="@dimen/qs_seamless_icon_size"
-                android:layout_gravity="center"
-                android:tint="?android:attr/textColorPrimary"
-                android:src="@*android:drawable/ic_media_seamless" />
-            <TextView
-                android:id="@+id/media_seamless_text"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center_vertical"
-                android:layout_marginStart="4dp"
-                android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-                android:singleLine="true"
-                android:text="@*android:string/ext_media_seamless_action"
-                android:textDirection="locale"
-                android:textSize="12sp"
-                android:lineHeight="16sp" />
-        </LinearLayout>
-    </LinearLayout>
-
-    <!-- Seek Bar -->
-    <!-- As per Material Design on Biderectionality, this is forced to LTR in code -->
-    <SeekBar
-        android:id="@+id/media_progress_bar"
-        style="@style/MediaPlayer.ProgressBar"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:maxHeight="@dimen/qs_media_enabled_seekbar_height"
-        android:paddingTop="@dimen/qs_media_enabled_seekbar_vertical_padding"
-        android:layout_marginTop="-22dp"
-        android:paddingBottom="2dp"
-        android:splitTrack="false" />
-
-    <!-- Song name -->
-    <TextView
-        android:id="@+id/header_title"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-        android:singleLine="true"
-        android:textSize="16sp" />
-
-    <!-- Artist name -->
-    <TextView
-        android:id="@+id/header_artist"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:fontFamily="@*android:string/config_headlineFontFamily"
-        android:singleLine="true"
-        style="@style/MediaPlayer.Subtitle"
-        android:textSize="14sp" />
-
-    <com.android.internal.widget.CachingIconView
-        android:id="@+id/icon"
-        style="@style/MediaPlayer.AppIcon"
-        android:layout_width="@dimen/qs_media_icon_size"
-        android:layout_height="@dimen/qs_media_icon_size" />
-
-    <!-- Long press menu -->
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/qs_media_padding"
-        android:layout_marginStart="@dimen/qs_media_padding"
-        android:layout_marginEnd="@dimen/qs_media_padding"
-        android:id="@+id/remove_text"
-        android:fontFamily="@*android:string/config_headlineFontFamily"
-        android:singleLine="true"
-        android:ellipsize="marquee"
-        android:marqueeRepeatLimit="marquee_forever"
-        android:text="@string/controls_media_close_session"
-        android:gravity="center_horizontal|top"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/cancel"/>
-
-    <FrameLayout
-        android:id="@+id/settings"
-        android:background="@drawable/qs_media_light_source"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/qs_media_padding"
-        android:layout_marginEnd="@dimen/qs_media_action_spacing"
-        android:layout_marginBottom="@dimen/qs_media_padding"
-        app:layout_constrainedWidth="true"
-        app:layout_constraintWidth_min="48dp"
-        app:layout_constraintHeight_min="48dp"
-        app:layout_constraintHorizontal_chainStyle="spread_inside"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toStartOf="@id/cancel"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/remove_text">
-
-        <TextView
-            android:id="@+id/settings_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center|bottom"
-            style="@style/MediaPlayer.OutlineButton"
-            android:text="@string/controls_media_settings_button" />
-    </FrameLayout>
-
-    <FrameLayout
-        android:id="@+id/cancel"
-        android:background="@drawable/qs_media_light_source"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/qs_media_action_spacing"
-        android:layout_marginEnd="@dimen/qs_media_action_spacing"
-        android:layout_marginBottom="@dimen/qs_media_padding"
-        app:layout_constrainedWidth="true"
-        app:layout_constraintWidth_min="48dp"
-        app:layout_constraintHeight_min="48dp"
-        app:layout_constraintStart_toEndOf="@id/settings"
-        app:layout_constraintEnd_toStartOf="@id/dismiss"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/remove_text">
-
-        <TextView
-            android:id="@+id/cancel_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center|bottom"
-            style="@style/MediaPlayer.OutlineButton"
-            android:text="@string/cancel" />
-    </FrameLayout>
-
-    <FrameLayout
-        android:id="@+id/dismiss"
-        android:background="@drawable/qs_media_light_source"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/qs_media_action_spacing"
-        android:layout_marginEnd="@dimen/qs_media_padding"
-        android:layout_marginBottom="@dimen/qs_media_padding"
-        app:layout_constrainedWidth="true"
-        app:layout_constraintWidth_min="48dp"
-        app:layout_constraintHeight_min="48dp"
-        app:layout_constraintStart_toEndOf="@id/cancel"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/remove_text">
-
-        <TextView
-            android:id="@+id/dismiss_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center|bottom"
-            style="@style/MediaPlayer.OutlineButton"
-            android:text="@string/controls_media_dismiss_button"
-        />
-    </FrameLayout>
-</com.android.systemui.util.animation.TransitionLayout>
diff --git a/packages/SystemUI/res/layout/rounded_corners_bottom.xml b/packages/SystemUI/res/layout/rounded_corners_bottom.xml
deleted file mode 100644
index bb6d4bd..0000000
--- a/packages/SystemUI/res/layout/rounded_corners_bottom.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-** Copyright 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.
--->
-<com.android.systemui.RegionInterceptingFrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/rounded_corners_bottom"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-    <ImageView
-        android:id="@+id/left"
-        android:layout_width="12dp"
-        android:layout_height="12dp"
-        android:layout_gravity="left|bottom"
-        android:tint="#ff000000"
-        android:visibility="gone"
-        android:src="@drawable/rounded_corner_bottom"/>
-
-    <ImageView
-        android:id="@+id/right"
-        android:layout_width="12dp"
-        android:layout_height="12dp"
-        android:tint="#ff000000"
-        android:visibility="gone"
-        android:layout_gravity="right|bottom"
-        android:src="@drawable/rounded_corner_bottom"/>
-
-</com.android.systemui.RegionInterceptingFrameLayout>
diff --git a/packages/SystemUI/res/layout/rounded_corners_top.xml b/packages/SystemUI/res/layout/rounded_corners_top.xml
deleted file mode 100644
index 46648c8..0000000
--- a/packages/SystemUI/res/layout/rounded_corners_top.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-** Copyright 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.
--->
-<com.android.systemui.RegionInterceptingFrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/rounded_corners_top"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-    <ImageView
-        android:id="@+id/left"
-        android:layout_width="12dp"
-        android:layout_height="12dp"
-        android:layout_gravity="left|top"
-        android:tint="#ff000000"
-        android:visibility="gone"
-        android:src="@drawable/rounded_corner_top"/>
-
-    <ImageView
-        android:id="@+id/right"
-        android:layout_width="12dp"
-        android:layout_height="12dp"
-        android:tint="#ff000000"
-        android:visibility="gone"
-        android:layout_gravity="right|top"
-        android:src="@drawable/rounded_corner_top"/>
-
-</com.android.systemui.RegionInterceptingFrameLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8f4e115..7345726 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -959,10 +959,7 @@
 
     <!-- Size of media cards in the QSPanel carousel -->
     <dimen name="qs_media_padding">16dp</dimen>
-    <dimen name="qs_media_album_size_small">72dp</dimen>
-    <dimen name="qs_media_album_size">84dp</dimen>
     <dimen name="qs_media_album_radius">14dp</dimen>
-    <dimen name="qs_media_album_device_padding">26dp</dimen>
     <dimen name="qs_media_info_margin">12dp</dimen>
     <dimen name="qs_media_info_spacing">8dp</dimen>
     <dimen name="qs_media_icon_size">20dp</dimen>
@@ -974,10 +971,7 @@
     <dimen name="qs_seamless_icon_size">12dp</dimen>
     <dimen name="qs_media_disabled_seekbar_height">1dp</dimen>
     <dimen name="qs_media_enabled_seekbar_height">2dp</dimen>
-    <dimen name="qs_media_enabled_seekbar_vertical_padding">28dp</dimen>
-    <dimen name="qs_media_disabled_seekbar_vertical_padding">29dp</dimen>
 
-    <!-- Sizes for alternate session-based layout -->
     <dimen name="qs_media_session_enabled_seekbar_vertical_padding">15dp</dimen>
     <dimen name="qs_media_session_disabled_seekbar_vertical_padding">16dp</dimen>
     <dimen name="qs_media_session_height_expanded">184dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 926734c..096b9a0 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -167,5 +167,11 @@
     <item type="id" name="action_move_bottom_right"/>
     <item type="id" name="action_move_to_edge_and_hide"/>
     <item type="id" name="action_move_out_edge_and_show"/>
+
+    <!-- rounded corner view id -->
+    <item type="id" name="rounded_corner_top_left"/>
+    <item type="id" name="rounded_corner_top_right"/>
+    <item type="id" name="rounded_corner_bottom_left"/>
+    <item type="id" name="rounded_corner_bottom_right"/>
 </resources>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6dc6214..d3b76d9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -368,15 +368,6 @@
     <!-- Content of a dialog shown when the user only has one attempt left to provide the correct password before the work profile is removed. [CHAR LIMIT=NONE] -->
     <string name="biometric_dialog_last_password_attempt_before_wipe_profile">If you enter an incorrect password on the next attempt, your work profile and its data will be deleted.</string>
 
-    <!-- Content of a dialog shown when the user has failed to provide the device lock too many times and the device is wiped. [CHAR LIMIT=NONE] -->
-    <string name="biometric_dialog_failed_attempts_now_wiping_device">Too many incorrect attempts. This device\u2019s data will be deleted.</string>
-    <!-- Content of a dialog shown when the user has failed to provide the user lock too many times and the user is removed. [CHAR LIMIT=NONE] -->
-    <string name="biometric_dialog_failed_attempts_now_wiping_user">Too many incorrect attempts. This user will be deleted.</string>
-    <!-- Content of a dialog shown when the user has failed to provide the work lock too many times and the work profile is removed. [CHAR LIMIT=NONE] -->
-    <string name="biometric_dialog_failed_attempts_now_wiping_profile">Too many incorrect attempts. This work profile and its data will be deleted.</string>
-    <!-- Button label to dismiss the dialog telling the user the device, user, or work profile has been wiped. [CHAR LIMIT=40] -->
-    <string name="biometric_dialog_now_wiping_dialog_dismiss">Dismiss</string>
-
     <!-- Message shown when the system-provided fingerprint dialog is shown, asking for authentication -->
     <string name="fingerprint_dialog_touch_sensor">Touch the fingerprint sensor</string>
     <!-- Content description of the fingerprint icon when the system-provided fingerprint dialog is showing, for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
@@ -831,15 +822,6 @@
 
     <!-- Accessibility label for the button that opens the quick contact of the user. -->
 
-    <!-- Label for the adding a new user in the user switcher [CHAR LIMIT=35] -->
-    <string name="user_add_user">Add user</string>
-
-    <!-- Name for a freshly added user [CHAR LIMIT=30] -->
-    <string name="user_new_user_name">New user</string>
-
-    <!-- Title of the confirmation dialog when exiting guest session [CHAR LIMIT=NONE] -->
-    <string name="guest_exit_guest_dialog_title">Remove guest?</string>
-
     <!-- Message of the confirmation dialog when exiting guest session [CHAR LIMIT=NONE] -->
     <string name="guest_exit_guest_dialog_message">All apps and data in this session will be deleted.</string>
 
@@ -858,12 +840,6 @@
     <!-- Notification when resuming an existing guest session: Action that continues with the current session [CHAR LIMIT=35] -->
     <string name="guest_wipe_session_dontwipe">Yes, continue</string>
 
-    <!-- Title for add user confirmation dialog [CHAR LIMIT=30] -->
-    <string name="user_add_user_title" msgid="2108112641783146007">Add new user?</string>
-
-    <!-- Message for add user confirmation dialog - short version. [CHAR LIMIT=none] -->
-    <string name="user_add_user_message_short" msgid="1511354412249044381">When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. </string>
-
     <!-- Title for the dialog that lets users know that the maximum allowed number of users on the device has been reached. [CHAR LIMIT=35]-->
     <string name="user_limit_reached_title">User limit reached</string>
 
@@ -2140,11 +2116,11 @@
     <!-- Title for media controls [CHAR_LIMIT=50] -->
     <string name="controls_media_title">Media</string>
     <!-- Explanation for closing controls associated with a specific media session [CHAR_LIMIT=50] -->
-    <string name="controls_media_close_session">Hide this media session?</string>
+    <string name="controls_media_close_session">Hide this media control for <xliff:g id="app_name" example="YouTube Music">%1$s</xliff:g>?</string>
     <!-- Explanation that controls associated with a specific media session are active [CHAR_LIMIT=50] -->
     <string name="controls_media_active_session">The current media session cannot be hidden.</string>
     <!-- Label for a button that will hide media controls [CHAR_LIMIT=30] -->
-    <string name="controls_media_dismiss_button">Dismiss</string>
+    <string name="controls_media_dismiss_button">Hide</string>
     <!-- Label for button to resume media playback [CHAR_LIMIT=NONE] -->
     <string name="controls_media_resume">Resume</string>
     <!-- Label for button to go to media control settings screen [CHAR_LIMIT=30] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index f5c1382..a61eda8 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -630,7 +630,7 @@
     </style>
 
     <style name="MediaPlayer.OutlineButton">
-        <item name="android:background">@drawable/qs_media_button_background</item>
+        <item name="android:background">@drawable/qs_media_outline_button</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:backgroundTint">@color/media_player_outline_button_bg</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
diff --git a/packages/SystemUI/res/xml/media_collapsed.xml b/packages/SystemUI/res/xml/media_collapsed.xml
deleted file mode 100644
index 12e446f..0000000
--- a/packages/SystemUI/res/xml/media_collapsed.xml
+++ /dev/null
@@ -1,176 +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
-  -->
-<ConstraintSet
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
-    <Constraint
-        android:id="@+id/icon"
-        android:layout_width="@dimen/qs_media_icon_size"
-        android:layout_height="@dimen/qs_media_icon_size"
-        android:translationY="@dimen/qs_media_icon_offset"
-        android:translationX="@dimen/qs_media_icon_offset"
-        app:layout_constraintEnd_toEndOf="@id/album_art"
-        app:layout_constraintBottom_toBottomOf="@id/album_art"
-        />
-
-    <Constraint
-        android:id="@+id/media_seamless"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
-        app:layout_constraintBottom_toTopOf="@id/center_horizontal_guideline"
-        app:layout_constraintHorizontal_chainStyle="spread_inside"
-        app:layout_constraintHorizontal_bias="1"
-        app:layout_constrainedWidth="true"
-        app:layout_constraintWidth_min="48dp"
-        app:layout_constraintHeight_min="48dp"
-        android:layout_marginStart="@dimen/qs_center_guideline_padding"
-        />
-
-    <Constraint
-        android:id="@+id/album_art"
-        android:layout_width="@dimen/qs_media_album_size_small"
-        android:layout_height="@dimen/qs_media_album_size_small"
-        android:layout_marginTop="@dimen/qs_media_padding"
-        android:layout_marginStart="@dimen/qs_media_padding"
-        android:layout_marginBottom="@dimen/qs_media_padding"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent"
-        />
-
-    <!-- Song name -->
-    <Constraint
-        android:id="@+id/header_title"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/qs_media_info_margin"
-        android:layout_marginEnd="@dimen/qs_center_guideline_padding"
-        app:layout_constrainedWidth="true"
-        app:layout_constraintBottom_toTopOf="@id/center_horizontal_guideline"
-        app:layout_constraintStart_toEndOf="@id/album_art"
-        app:layout_constraintEnd_toStartOf="@id/center_vertical_guideline"
-        app:layout_constraintHorizontal_bias="0"/>
-
-    <!-- Artist name -->
-    <Constraint
-        android:id="@+id/header_artist"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:layout_constrainedWidth="true"
-        android:layout_marginTop="@dimen/qs_media_info_spacing"
-        app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline"
-        app:layout_constraintStart_toStartOf="@id/header_title"
-        app:layout_constraintEnd_toStartOf="@id/media_action_barrier"
-        app:layout_constraintHorizontal_bias="0"/>
-
-    <!-- Seek Bar -->
-    <Constraint
-        android:id="@+id/media_progress_bar"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:alpha="0.0"
-        app:layout_constraintTop_toBottomOf="@id/album_art"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:visibility="gone"
-        />
-
-    <Constraint
-        android:id="@+id/notification_media_progress_time"
-        android:alpha="0.0"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        app:layout_constraintTop_toBottomOf="@id/album_art"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:visibility="gone"
-        />
-
-    <Constraint
-        android:id="@+id/action0"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:layout_marginStart="@dimen/qs_media_padding"
-        android:layout_marginEnd="@dimen/qs_media_action_spacing"
-        android:visibility="gone"
-        app:layout_constraintHorizontal_chainStyle="packed"
-        app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintRight_toLeftOf="@id/action1"
-        app:layout_constraintHorizontal_bias="1"
-        >
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/action1"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:layout_marginStart="@dimen/qs_media_action_spacing"
-        android:layout_marginEnd="@dimen/qs_media_action_spacing"
-        app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintLeft_toRightOf="@id/action0"
-        app:layout_constraintRight_toLeftOf="@id/action2"
-        >
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/action2"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:layout_marginStart="@dimen/qs_media_action_spacing"
-        android:layout_marginEnd="@dimen/qs_media_action_spacing"
-        app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintLeft_toRightOf="@id/action1"
-        app:layout_constraintRight_toLeftOf="@id/action3"
-        >
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/action3"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:layout_marginStart="@dimen/qs_media_action_spacing"
-        android:layout_marginEnd="@dimen/qs_media_action_spacing"
-        app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintLeft_toRightOf="@id/action2"
-        app:layout_constraintRight_toLeftOf="@id/action4"
-        >
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/action4"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:layout_marginStart="@dimen/qs_media_action_spacing"
-        android:layout_marginEnd="@dimen/qs_media_padding"
-        android:visibility="gone"
-        app:layout_constraintHorizontal_chainStyle="packed"
-        app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintLeft_toRightOf="@id/action3"
-        app:layout_constraintRight_toRightOf="parent"
-        app:layout_constraintHorizontal_bias="0"
-        >
-    </Constraint>
-</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_expanded.xml b/packages/SystemUI/res/xml/media_expanded.xml
deleted file mode 100644
index 6b83aae..0000000
--- a/packages/SystemUI/res/xml/media_expanded.xml
+++ /dev/null
@@ -1,174 +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
-  -->
-<ConstraintSet
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
-    <Constraint
-        android:id="@+id/icon"
-        android:layout_width="@dimen/qs_media_icon_size"
-        android:layout_height="@dimen/qs_media_icon_size"
-        android:translationY="@dimen/qs_media_icon_offset"
-        android:translationX="@dimen/qs_media_icon_offset"
-        app:layout_constraintEnd_toEndOf="@id/album_art"
-        app:layout_constraintBottom_toBottomOf="@id/album_art"
-    />
-
-    <Constraint
-        android:id="@+id/media_seamless"
-        android:layout_width="wrap_content"
-        android:layout_height="48dp"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
-        app:layout_constraintHorizontal_chainStyle="spread_inside"
-        app:layout_constraintHorizontal_bias="1"
-        app:layout_constrainedWidth="true"
-        app:layout_constraintWidth_min="48dp"
-        app:layout_constraintHeight_min="48dp"
-        android:paddingTop="@dimen/qs_media_padding"
-        android:paddingEnd="@dimen/qs_media_padding"
-        android:layout_marginStart="@dimen/qs_center_guideline_padding"
-        android:layout_marginBottom="4dp" />
-
-    <Constraint
-        android:id="@+id/album_art"
-        android:layout_width="@dimen/qs_media_album_size"
-        android:layout_height="@dimen/qs_media_album_size"
-        android:layout_marginTop="@dimen/qs_media_padding"
-        android:layout_marginStart="@dimen/qs_media_padding"
-        android:layout_marginBottom="0dp"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        />
-
-    <!-- Song name -->
-    <Constraint
-        android:id="@+id/header_title"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="26dp"
-        android:layout_marginStart="@dimen/qs_media_info_margin"
-        android:layout_marginEnd="@dimen/qs_media_padding"
-        app:layout_constrainedWidth="true"
-        app:layout_constraintTop_toTopOf="@+id/album_art"
-        app:layout_constraintStart_toEndOf="@id/album_art"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintHorizontal_bias="0"/>
-
-    <!-- Artist name -->
-    <Constraint
-        android:id="@+id/header_artist"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginEnd="@dimen/qs_media_padding"
-        android:layout_marginBottom="@dimen/qs_media_info_margin"
-        app:layout_constrainedWidth="true"
-        android:layout_marginTop="1dp"
-        app:layout_constraintTop_toBottomOf="@id/header_title"
-        app:layout_constraintStart_toStartOf="@id/header_title"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintHorizontal_bias="0"/>
-
-    <!-- Seek Bar -->
-    <Constraint
-        android:id="@+id/media_progress_bar"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="34dp"
-        app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        />
-
-    <Constraint
-        android:id="@+id/notification_media_progress_time"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginEnd="@dimen/qs_media_padding"
-        android:layout_marginStart="@dimen/qs_media_padding"
-        app:layout_constraintTop_toBottomOf="@id/media_progress_bar"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        />
-
-    <Constraint
-        android:id="@+id/action0"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:layout_marginStart="@dimen/qs_media_padding"
-        android:layout_marginEnd="@dimen/qs_media_action_spacing"
-        android:layout_marginBottom="@dimen/qs_media_action_margin"
-        app:layout_constraintHorizontal_chainStyle="packed"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintRight_toLeftOf="@id/action1"
-        app:layout_constraintTop_toBottomOf="@id/media_progress_bar"
-        app:layout_constraintBottom_toBottomOf="parent">
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/action1"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:layout_marginStart="@dimen/qs_media_action_spacing"
-        android:layout_marginEnd="@dimen/qs_media_action_spacing"
-        android:layout_marginBottom="@dimen/qs_media_action_margin"
-        app:layout_constraintLeft_toRightOf="@id/action0"
-        app:layout_constraintRight_toLeftOf="@id/action2"
-        app:layout_constraintTop_toBottomOf="@id/media_progress_bar"
-        app:layout_constraintBottom_toBottomOf="parent">
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/action2"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:layout_marginStart="@dimen/qs_media_action_spacing"
-        android:layout_marginEnd="@dimen/qs_media_action_spacing"
-        android:layout_marginBottom="@dimen/qs_media_action_margin"
-        app:layout_constraintLeft_toRightOf="@id/action1"
-        app:layout_constraintRight_toLeftOf="@id/action3"
-        app:layout_constraintTop_toBottomOf="@id/media_progress_bar"
-        app:layout_constraintBottom_toBottomOf="parent">
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/action3"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:layout_marginStart="@dimen/qs_media_action_spacing"
-        android:layout_marginEnd="@dimen/qs_media_action_spacing"
-        android:layout_marginBottom="@dimen/qs_media_action_margin"
-        app:layout_constraintLeft_toRightOf="@id/action2"
-        app:layout_constraintRight_toLeftOf="@id/action4"
-        app:layout_constraintTop_toBottomOf="@id/media_progress_bar"
-        app:layout_constraintBottom_toBottomOf="parent">
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/action4"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:layout_marginStart="@dimen/qs_media_action_spacing"
-        android:layout_marginEnd="@dimen/qs_media_padding"
-        android:layout_marginBottom="@dimen/qs_media_action_margin"
-        app:layout_constraintHorizontal_chainStyle="packed"
-        app:layout_constraintLeft_toRightOf="@id/action3"
-        app:layout_constraintRight_toRightOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/media_progress_bar"
-        app:layout_constraintBottom_toBottomOf="parent">
-    </Constraint>
-</ConstraintSet>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index c9a659a..9cf482f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -32,6 +32,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.ArrayMap;
+import android.util.IntArray;
 import android.util.SparseArray;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
@@ -269,6 +270,7 @@
             SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
         final ArrayList<RemoteAnimationTargetCompat> out = new ArrayList<>();
         final SparseArray<RemoteAnimationTargetCompat> childTaskTargets = new SparseArray<>();
+        final IntArray excludedParentTaskIds = new IntArray();
         for (int i = 0; i < info.getChanges().size(); i++) {
             final TransitionInfo.Change change = info.getChanges().get(i);
             final boolean changeIsWallpaper =
@@ -282,22 +284,40 @@
             }
             final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
             if (taskInfo != null) {
-                if (taskInfo.parentTaskId != -1) {
-                    // Cache child task targets to override its parent target later and exclude
-                    // child task while wrapping up animate targets. Otherwise the child task might
-                    // get transformed twice with the flow like RecentsView#redrawLiveTile.
-                    childTaskTargets.put(taskInfo.parentTaskId, targetCompat);
+                // Skip wrapping excluded parent task animate target since it will animate its child
+                // tasks instead.
+                if (excludedParentTaskIds.binarySearch(taskInfo.taskId) != -1) {
                     continue;
                 }
 
-                final RemoteAnimationTargetCompat childTaskTarget =
-                        childTaskTargets.get(taskInfo.taskId);
+                // Check if there's a matching child task target in cache.
+                RemoteAnimationTargetCompat childTaskTarget = childTaskTargets.get(taskInfo.taskId);
                 if (childTaskTarget != null) {
-                    // Launcher monitors leaf tasks to perform animation, hence override the parent
-                    // task target with child task info so Launcher can locate and animate root
-                    // surface directly with leaf task information.
+                    // Launcher monitors leaf task ids to perform animation, override the target
+                    // with its child task information so Launcher can animate this parent surface
+                    // directly with leaf task information.
                     targetCompat.taskInfo = childTaskTarget.taskInfo;
                     targetCompat.taskId = childTaskTarget.taskId;
+                    childTaskTargets.remove(taskInfo.taskId);
+                }
+
+                // Check if it has a parent task, cache its information for later use.
+                if (taskInfo.parentTaskId != -1
+                        && excludedParentTaskIds.binarySearch(taskInfo.parentTaskId) == -1) {
+                    if (!childTaskTargets.contains(taskInfo.parentTaskId)) {
+                        // Cache the target amd skip wrapping it info the final animation targets.
+                        // Otherwise, the child task might get transformed multiple-times with the
+                        // flow like RecentsView#redrawLiveTile.
+                        childTaskTargets.put(taskInfo.parentTaskId, targetCompat);
+                        continue;
+                    }
+
+                    // There is another child task target cached with the same parent task id.
+                    // Which means the parent having multiple child tasks in transition. Stop
+                    // propagate child task info.
+                    childTaskTarget = childTaskTargets.removeReturnOld(taskInfo.parentTaskId);
+                    out.add(childTaskTarget);
+                    excludedParentTaskIds.add(taskInfo.parentTaskId);
                 }
             }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 74a88bd..5602f3d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1501,17 +1501,23 @@
 
                 @Override
                 public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
+                    Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationHelp");
                     handleFingerprintHelp(helpMsgId, helpString.toString());
+                    Trace.endSection();
                 }
 
                 @Override
                 public void onAuthenticationError(int errMsgId, CharSequence errString) {
+                    Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationError");
                     handleFingerprintError(errMsgId, errString.toString());
+                    Trace.endSection();
                 }
 
                 @Override
                 public void onAuthenticationAcquired(int acquireInfo) {
+                    Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationAcquired");
                     handleFingerprintAcquired(acquireInfo);
+                    Trace.endSection();
                 }
 
                 @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index e36e984..a4a0105f 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -212,19 +212,14 @@
         updateBurnInOffsets();
         updateVisibility();
 
+        mAccessibilityManager.addTouchExplorationStateChangeListener(
+                mTouchExplorationStateChangeListener);
         updateAccessibility();
     }
 
     private void updateAccessibility() {
         if (mAccessibilityManager.isTouchExplorationEnabled()) {
-            mView.setOnClickListener(
-                    new View.OnClickListener() {
-                        @Override
-                        public void onClick(View v) {
-                            onLongPress();
-                        }
-                    }
-            );
+            mView.setOnClickListener(mA11yClickListener);
         } else {
             mView.setOnClickListener(null);
         }
@@ -242,6 +237,9 @@
             mCancelDelayedUpdateVisibilityRunnable.run();
             mCancelDelayedUpdateVisibilityRunnable = null;
         }
+
+        mAccessibilityManager.removeTouchExplorationStateChangeListener(
+                mTouchExplorationStateChangeListener);
     }
 
     public float getTop() {
@@ -267,7 +265,6 @@
             return;
         }
 
-        boolean wasShowingUnlock = mShowUnlockIcon;
         boolean wasShowingFpIcon = mUdfpsEnrolled && !mShowUnlockIcon && !mShowLockIcon
                 && !mShowAodUnlockedIcon && !mShowAodLockIcon;
         mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen()
@@ -702,6 +699,14 @@
         mView.setAlpha(alpha);
     }
 
+    private void updateUdfpsConfig() {
+        // must be called from the main thread since it may update the views
+        mExecutor.execute(() -> {
+            updateIsUdfpsEnrolled();
+            updateConfiguration();
+        });
+    }
+
     private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
         @Override
         public void onAllAuthenticatorsRegistered() {
@@ -714,11 +719,8 @@
         }
     };
 
-    private void updateUdfpsConfig() {
-        // must be called from the main thread since it may update the views
-        mExecutor.execute(() -> {
-            updateIsUdfpsEnrolled();
-            updateConfiguration();
-        });
-    }
+    private final View.OnClickListener mA11yClickListener = v -> onLongPress();
+
+    private final AccessibilityManager.TouchExplorationStateChangeListener
+            mTouchExplorationStateChangeListener = enabled -> updateAccessibility();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
index 56046d9..ccb5b11 100644
--- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
+++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
@@ -79,6 +79,8 @@
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
         updateCutout()
+        updateProtectionBoundingPath()
+        onUpdate()
     }
 
     fun onDisplayChanged(displayId: Int) {
@@ -93,6 +95,7 @@
         if (displayId == display.displayId) {
             updateCutout()
             updateProtectionBoundingPath()
+            onUpdate()
         }
     }
 
@@ -100,8 +103,13 @@
         displayRotation = rotation
         updateCutout()
         updateProtectionBoundingPath()
+        onUpdate()
     }
 
+    // Called after the cutout and protection bounding path change. Subclasses
+    // should make any changes that need to happen based on the change.
+    open fun onUpdate() = Unit
+
     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
     public override fun onDraw(canvas: Canvas) {
         super.onDraw(canvas)
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index b5b6b13..9a6020f 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -128,7 +128,7 @@
                 UserSwitcherController userSwitcherController,
                 UiEventLogger uiEventLogger,
                 int userId) {
-            super(context);
+            super(context, false /* dismissOnDeviceLock */);
 
             setTitle(context.getString(R.string.guest_wipe_session_title));
             setMessage(context.getString(R.string.guest_wipe_session_message));
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
index 1094622..0118813 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
@@ -114,6 +114,10 @@
         }
     }
 
+    override fun onUpdate() {
+        parent.requestTransparentRegion(this)
+    }
+
     override fun onDraw(canvas: Canvas) {
         // If updating onDraw, also update gatherTransparentRegion
         if (useInvertedAlphaColor) {
@@ -365,10 +369,15 @@
      * Update the rounded corner size.
      */
     fun updateRoundedCornerSize(top: Int, bottom: Int) {
+        if (roundedCornerTopSize == top && roundedCornerBottomSize == bottom) {
+            return
+        }
         roundedCornerTopSize = top
         roundedCornerBottomSize = bottom
         updateRoundedCornerDrawableBounds()
-        invalidate()
+
+        // Use requestLayout() to trigger transparent region recalculated
+        requestLayout()
     }
 
     private fun updateRoundedCornerDrawableBounds() {
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 2f5292c..5de09b1 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -77,6 +77,7 @@
 import com.android.systemui.decor.DecorProviderKt;
 import com.android.systemui.decor.OverlayWindow;
 import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
+import com.android.systemui.decor.RoundedCornerDecorProviderFactory;
 import com.android.systemui.decor.RoundedCornerResDelegate;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.settings.UserTracker;
@@ -138,6 +139,9 @@
     @VisibleForTesting
     protected RoundedCornerResDelegate mRoundedCornerResDelegate;
     @VisibleForTesting
+    protected DecorProviderFactory mRoundedCornerFactory;
+    private int mProviderRefreshToken = 0;
+    @VisibleForTesting
     protected OverlayWindow[] mOverlays = null;
     @Nullable
     private DisplayCutoutView[] mCutoutViews;
@@ -292,11 +296,11 @@
         mDisplayUniqueId = mContext.getDisplay().getUniqueId();
         mRoundedCornerResDelegate = new RoundedCornerResDelegate(mContext.getResources(),
                 mDisplayUniqueId);
+        mRoundedCornerFactory = new RoundedCornerDecorProviderFactory(mRoundedCornerResDelegate);
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mDisplayManager = mContext.getSystemService(DisplayManager.class);
         mHwcScreenDecorationSupport = mContext.getDisplay().getDisplayDecorationSupport();
-        updateRoundedCornerDrawable();
-        updateRoundedCornerRadii();
+        updateHwLayerRoundedCornerDrawable();
         setupDecorations();
         setupCameraListener();
 
@@ -348,7 +352,7 @@
                 final String newUniqueId = mContext.getDisplay().getUniqueId();
                 if (!Objects.equals(newUniqueId, mDisplayUniqueId)) {
                     mDisplayUniqueId = newUniqueId;
-                    mRoundedCornerResDelegate.reloadAll(newUniqueId);
+                    mRoundedCornerResDelegate.updateDisplayUniqueId(newUniqueId, null);
                     final DisplayDecorationSupport newScreenDecorationSupport =
                             mContext.getDisplay().getDisplayDecorationSupport();
                     // When the value of mSupportHwcScreenDecoration is changed, re-setup the whole
@@ -359,12 +363,12 @@
                         setupDecorations();
                         return;
                     }
-                    updateRoundedCornerDrawable();
+                    updateHwLayerRoundedCornerDrawable();
                 }
                 if (mScreenDecorHwcLayer != null) {
                     mScreenDecorHwcLayer.onDisplayChanged(displayId);
                 }
-                updateOrientation();
+                updateView();
             }
         };
 
@@ -406,22 +410,22 @@
     }
 
     private void setupDecorations() {
-        List<DecorProvider> decorProviders = mDotFactory.getProviders();
-
-        if (hasRoundedCorners() || shouldDrawCutout() || !decorProviders.isEmpty()) {
+        if (hasRoundedCorners() || shouldDrawCutout() || isPrivacyDotEnabled()) {
+            List<DecorProvider> decorProviders = new ArrayList<>(mDotFactory.getProviders());
             if (mHwcScreenDecorationSupport != null) {
                 createHwcOverlay();
             } else {
                 removeHwcOverlay();
+                decorProviders.addAll(mRoundedCornerFactory.getProviders());
             }
             final DisplayCutout cutout = getCutout();
             for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
-                if (shouldShowCutout(i, cutout) || shouldShowRoundedCorner(i, cutout)
-                        || shouldShowPrivacyDot(i, cutout)) {
+                if (shouldShowSwLayerCutout(i, cutout) || shouldShowSwLayerRoundedCorner(i, cutout)
+                        || shouldShowSwLayerPrivacyDot(i, cutout)) {
                     Pair<List<DecorProvider>, List<DecorProvider>> pair =
                             DecorProviderKt.partitionAlignedBound(decorProviders, i);
                     decorProviders = pair.getSecond();
-                    createOverlay(i, cutout, pair.getFirst());
+                    createOverlay(i, pair.getFirst());
                 } else {
                     removeOverlay(i);
                 }
@@ -522,7 +526,6 @@
 
     private void createOverlay(
             @BoundsPosition int pos,
-            @Nullable DisplayCutout cutout,
             @NonNull List<DecorProvider> decorProviders) {
         if (mOverlays == null) {
             mOverlays = new OverlayWindow[BOUNDS_POSITION_LENGTH];
@@ -547,7 +550,7 @@
             mCutoutViews[pos] = new DisplayCutoutView(mContext, pos);
             mCutoutViews[pos].setColor(mTintColor);
             overlayView.addView(mCutoutViews[pos]);
-            updateView(pos, cutout);
+            mCutoutViews[pos].updateRotation(mRotation);
         }
 
         mWindowManager.addView(overlayView, getWindowLayoutParams(pos));
@@ -603,7 +606,7 @@
     private OverlayWindow overlayForPosition(
             @BoundsPosition int pos,
             @NonNull List<DecorProvider> decorProviders) {
-        final OverlayWindow currentOverlay = new OverlayWindow(LayoutInflater.from(mContext), pos);
+        final OverlayWindow currentOverlay = new OverlayWindow(mContext);
         decorProviders.forEach(provider -> {
             removeOverlayView(provider.getViewId());
             currentOverlay.addDecorProvider(provider, mRotation);
@@ -617,22 +620,16 @@
         return currentOverlay;
     }
 
-    private void updateView(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
-        if (mOverlays == null || mOverlays[pos] == null || mHwcScreenDecorationSupport != null) {
+    private void updateView() {
+        if (mOverlays == null) {
             return;
         }
-
-        // update rounded corner view rotation
-        updateRoundedCornerView(pos, R.id.left, cutout);
-        updateRoundedCornerView(pos, R.id.right, cutout);
-        updateRoundedCornerSize(
-                mRoundedCornerResDelegate.getTopRoundedSize(),
-                mRoundedCornerResDelegate.getBottomRoundedSize());
-        updateRoundedCornerImageView();
-
-        // update cutout view rotation
-        if (mCutoutViews != null && mCutoutViews[pos] != null) {
-            mCutoutViews[pos].updateRotation(mRotation);
+        ++mProviderRefreshToken;
+        for (final OverlayWindow overlay: mOverlays) {
+            if (overlay == null) {
+                continue;
+            }
+            overlay.onReloadResAndMeasure(null, mProviderRefreshToken, mRotation, mDisplayUniqueId);
         }
     }
 
@@ -806,7 +803,6 @@
             int oldRotation = mRotation;
             mPendingRotationChange = false;
             updateOrientation();
-            updateRoundedCornerRadii();
             if (DEBUG) Log.i(TAG, "onConfigChanged from rot " + oldRotation + " to " + mRotation);
             setupDecorations();
             if (mOverlays != null) {
@@ -866,109 +862,32 @@
             mDotViewController.setNewRotation(newRotation);
         }
 
-        if (mPendingRotationChange) {
-            return;
-        }
-        if (newRotation != mRotation) {
+        if (!mPendingRotationChange && newRotation != mRotation) {
             mRotation = newRotation;
             if (mScreenDecorHwcLayer != null) {
                 mScreenDecorHwcLayer.pendingRotationChange = false;
                 mScreenDecorHwcLayer.updateRotation(mRotation);
+                updateHwLayerRoundedCornerSize();
+                updateHwLayerRoundedCornerDrawable();
             }
-            if (mOverlays != null) {
-                updateLayoutParams();
-                final DisplayCutout cutout = getCutout();
-                for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
-                    if (mOverlays[i] == null) {
+            updateLayoutParams();
+            // update cutout view rotation
+            if (mCutoutViews != null) {
+                for (final DisplayCutoutView cutoutView: mCutoutViews) {
+                    if (cutoutView == null) {
                         continue;
                     }
-                    updateView(i, cutout);
+                    cutoutView.updateRotation(mRotation);
                 }
             }
         }
+
+        // update views
+        updateView();
     }
 
-    private void updateRoundedCornerRadii() {
-        // We should eventually move to just using the intrinsic size of the drawables since
-        // they should be sized to the exact pixels they want to cover. Therefore I'm purposely not
-        // upgrading all of the configs to contain (width, height) pairs. Instead assume that a
-        // device configured using the single integer config value is okay with drawing the corners
-        // as a square
-        final Size oldRoundedDefaultTop = mRoundedCornerResDelegate.getTopRoundedSize();
-        final Size oldRoundedDefaultBottom = mRoundedCornerResDelegate.getBottomRoundedSize();
-        mRoundedCornerResDelegate.reloadAll(mDisplayUniqueId);
-        final Size newRoundedDefaultTop = mRoundedCornerResDelegate.getTopRoundedSize();
-        final Size newRoundedDefaultBottom = mRoundedCornerResDelegate.getBottomRoundedSize();
-
-        if (oldRoundedDefaultTop.getWidth() != newRoundedDefaultTop.getWidth()
-                || oldRoundedDefaultBottom.getWidth() != newRoundedDefaultBottom.getWidth()) {
-            onTuningChanged(SIZE, null);
-        }
-    }
-
-    private void updateRoundedCornerView(@BoundsPosition int pos, int id,
-            @Nullable DisplayCutout cutout) {
-        final View rounded = mOverlays[pos].getRootView().findViewById(id);
-        if (rounded == null) {
-            return;
-        }
-        rounded.setVisibility(View.GONE);
-        if (shouldShowRoundedCorner(pos, cutout)) {
-            final int gravity = getRoundedCornerGravity(pos, id == R.id.left);
-            ((FrameLayout.LayoutParams) rounded.getLayoutParams()).gravity = gravity;
-            setRoundedCornerOrientation(rounded, gravity);
-            rounded.setVisibility(View.VISIBLE);
-        }
-    }
-
-    private int getRoundedCornerGravity(@BoundsPosition int pos, boolean isStart) {
-        final int rotatedPos = getBoundPositionFromRotation(pos, mRotation);
-        switch (rotatedPos) {
-            case BOUNDS_POSITION_LEFT:
-                return isStart ? Gravity.TOP | Gravity.LEFT : Gravity.BOTTOM | Gravity.LEFT;
-            case BOUNDS_POSITION_TOP:
-                return isStart ? Gravity.TOP | Gravity.LEFT : Gravity.TOP | Gravity.RIGHT;
-            case BOUNDS_POSITION_RIGHT:
-                return isStart ? Gravity.TOP | Gravity.RIGHT : Gravity.BOTTOM | Gravity.RIGHT;
-            case BOUNDS_POSITION_BOTTOM:
-                return isStart ? Gravity.BOTTOM | Gravity.LEFT : Gravity.BOTTOM | Gravity.RIGHT;
-            default:
-                throw new IllegalArgumentException("Incorrect position: " + rotatedPos);
-        }
-    }
-
-    /**
-     * Configures the rounded corner drawable's view matrix based on the gravity.
-     *
-     * The gravity describes which corner to configure for, and the drawable we are rotating is
-     * assumed to be oriented for the top-left corner of the device regardless of the target corner.
-     * Therefore we need to rotate 180 degrees to get a bottom-left corner, and mirror in the x- or
-     * y-axis for the top-right and bottom-left corners.
-     */
-    private void setRoundedCornerOrientation(View corner, int gravity) {
-        corner.setRotation(0);
-        corner.setScaleX(1);
-        corner.setScaleY(1);
-        switch (gravity) {
-            case Gravity.TOP | Gravity.LEFT:
-                return;
-            case Gravity.TOP | Gravity.RIGHT:
-                corner.setScaleX(-1); // flip X axis
-                return;
-            case Gravity.BOTTOM | Gravity.LEFT:
-                corner.setScaleY(-1); // flip Y axis
-                return;
-            case Gravity.BOTTOM | Gravity.RIGHT:
-                corner.setRotation(180);
-                return;
-            default:
-                throw new IllegalArgumentException("Unsupported gravity: " + gravity);
-        }
-    }
     private boolean hasRoundedCorners() {
-        return mRoundedCornerResDelegate.getBottomRoundedSize().getWidth() > 0
-                || mRoundedCornerResDelegate.getTopRoundedSize().getWidth() > 0
-                || mRoundedCornerResDelegate.isMultipleRadius();
+        return mRoundedCornerFactory.getHasProviders();
     }
 
     private boolean isDefaultShownOverlayPos(@BoundsPosition int pos,
@@ -987,17 +906,19 @@
         }
     }
 
-    private boolean shouldShowRoundedCorner(@BoundsPosition int pos,
+    private boolean shouldShowSwLayerRoundedCorner(@BoundsPosition int pos,
             @Nullable DisplayCutout cutout) {
         return hasRoundedCorners() && isDefaultShownOverlayPos(pos, cutout)
                 && mHwcScreenDecorationSupport == null;
     }
 
-    private boolean shouldShowPrivacyDot(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
+    private boolean shouldShowSwLayerPrivacyDot(@BoundsPosition int pos,
+            @Nullable DisplayCutout cutout) {
         return isPrivacyDotEnabled() && isDefaultShownOverlayPos(pos, cutout);
     }
 
-    private boolean shouldShowCutout(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
+    private boolean shouldShowSwLayerCutout(@BoundsPosition int pos,
+            @Nullable DisplayCutout cutout) {
         final Rect[] bounds = cutout == null ? null : cutout.getBoundingRectsAll();
         final int rotatedPos = getBoundPositionFromRotation(pos, mRotation);
         return (bounds != null && !bounds[rotatedPos].isEmpty()
@@ -1032,54 +953,33 @@
             return;
         }
         mExecutor.execute(() -> {
-            if (mOverlays == null) return;
-            if (SIZE.equals(key)) {
-                if (newValue != null) {
-                    try {
-                        mRoundedCornerResDelegate.updateTuningSizeFactor(
-                                Integer.parseInt(newValue));
-                    } catch (Exception e) {
-                    }
+            if (mOverlays == null || !SIZE.equals(key)) {
+                return;
+            }
+            ++mProviderRefreshToken;
+            try {
+                final int sizeFactor = Integer.parseInt(newValue);
+                mRoundedCornerResDelegate.updateTuningSizeFactor(sizeFactor, mProviderRefreshToken);
+            } catch (NumberFormatException e) {
+                mRoundedCornerResDelegate.updateTuningSizeFactor(null, mProviderRefreshToken);
+            }
+            Integer[] filterIds = {
+                    R.id.rounded_corner_top_left,
+                    R.id.rounded_corner_top_right,
+                    R.id.rounded_corner_bottom_left,
+                    R.id.rounded_corner_bottom_right
+            };
+            for (final OverlayWindow overlay: mOverlays) {
+                if (overlay == null) {
+                    continue;
                 }
-                updateRoundedCornerSize(
-                        mRoundedCornerResDelegate.getTopRoundedSize(),
-                        mRoundedCornerResDelegate.getBottomRoundedSize());
+                overlay.onReloadResAndMeasure(filterIds, mProviderRefreshToken, mRotation,
+                        mDisplayUniqueId);
             }
+            updateHwLayerRoundedCornerSize();
         });
     }
 
-    private void updateRoundedCornerDrawable() {
-        mRoundedCornerResDelegate.reloadAll(mDisplayUniqueId);
-        updateRoundedCornerImageView();
-    }
-
-    private void updateRoundedCornerImageView() {
-        final Drawable top = mRoundedCornerResDelegate.getTopRoundedDrawable();
-        final Drawable bottom = mRoundedCornerResDelegate.getBottomRoundedDrawable();
-
-        if (mScreenDecorHwcLayer != null) {
-            mScreenDecorHwcLayer.updateRoundedCornerDrawable(top, bottom);
-            return;
-        }
-
-        if (mOverlays == null) {
-            return;
-        }
-        final ColorStateList colorStateList = ColorStateList.valueOf(mTintColor);
-        for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
-            if (mOverlays[i] == null) {
-                continue;
-            }
-            final ViewGroup overlayView = mOverlays[i].getRootView();
-            ((ImageView) overlayView.findViewById(R.id.left)).setImageTintList(colorStateList);
-            ((ImageView) overlayView.findViewById(R.id.right)).setImageTintList(colorStateList);
-            ((ImageView) overlayView.findViewById(R.id.left)).setImageDrawable(
-                    isTopRoundedCorner(i, R.id.left) ? top : bottom);
-            ((ImageView) overlayView.findViewById(R.id.right)).setImageDrawable(
-                    isTopRoundedCorner(i, R.id.right) ? top : bottom);
-        }
-    }
-
     private void updateHwLayerRoundedCornerDrawable() {
         if (mScreenDecorHwcLayer == null) {
             return;
@@ -1094,25 +994,6 @@
         mScreenDecorHwcLayer.updateRoundedCornerDrawable(topDrawable, bottomDrawable);
     }
 
-    @VisibleForTesting
-    boolean isTopRoundedCorner(@BoundsPosition int pos, int id) {
-        switch (pos) {
-            case BOUNDS_POSITION_LEFT:
-            case BOUNDS_POSITION_RIGHT:
-                if (mRotation == ROTATION_270) {
-                    return id == R.id.left ? false : true;
-                } else {
-                    return id == R.id.left ? true : false;
-                }
-            case BOUNDS_POSITION_TOP:
-                return true;
-            case BOUNDS_POSITION_BOTTOM:
-                return false;
-            default:
-                throw new IllegalArgumentException("Unknown bounds position");
-        }
-    }
-
     private void updateHwLayerRoundedCornerSize() {
         if (mScreenDecorHwcLayer == null) {
             return;
@@ -1124,28 +1005,6 @@
         mScreenDecorHwcLayer.updateRoundedCornerSize(topWidth, bottomWidth);
     }
 
-    private void updateRoundedCornerSize(Size sizeTop, Size sizeBottom) {
-
-        if (mScreenDecorHwcLayer != null) {
-            mScreenDecorHwcLayer.updateRoundedCornerSize(sizeTop.getWidth(), sizeBottom.getWidth());
-            return;
-        }
-
-        if (mOverlays == null) {
-            return;
-        }
-        for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
-            if (mOverlays[i] == null) {
-                continue;
-            }
-            final ViewGroup overlayView = mOverlays[i].getRootView();
-            setSize(overlayView.findViewById(R.id.left),
-                    isTopRoundedCorner(i, R.id.left) ? sizeTop : sizeBottom);
-            setSize(overlayView.findViewById(R.id.right),
-                    isTopRoundedCorner(i, R.id.right) ? sizeTop : sizeBottom);
-        }
-    }
-
     @VisibleForTesting
     protected void setSize(View view, Size pixelSize) {
         LayoutParams params = view.getLayoutParams();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index 2f2ca5b..ed84a37 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -390,7 +390,9 @@
     private void showNowWipingDialog() {
         final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
                 .setMessage(getNowWipingMessage(getUserTypeForWipe()))
-                .setPositiveButton(R.string.biometric_dialog_now_wiping_dialog_dismiss, null)
+                .setPositiveButton(
+                        com.android.settingslib.R.string.failed_attempts_now_wiping_dialog_dismiss,
+                        null /* OnClickListener */)
                 .setOnDismissListener(
                         dialog -> mContainerView.animateAway(AuthDialogCallback.DISMISSED_ERROR))
                 .create();
@@ -513,13 +515,13 @@
         int resId;
         switch (userType) {
             case USER_TYPE_PRIMARY:
-                resId = R.string.biometric_dialog_failed_attempts_now_wiping_device;
+                resId = com.android.settingslib.R.string.failed_attempts_now_wiping_device;
                 break;
             case USER_TYPE_MANAGED_PROFILE:
-                resId = R.string.biometric_dialog_failed_attempts_now_wiping_profile;
+                resId = com.android.settingslib.R.string.failed_attempts_now_wiping_profile;
                 break;
             case USER_TYPE_SECONDARY:
-                resId = R.string.biometric_dialog_failed_attempts_now_wiping_user;
+                resId = com.android.settingslib.R.string.failed_attempts_now_wiping_user;
                 break;
             default:
                 throw new IllegalArgumentException("Unrecognized user type:" + userType);
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 46a03e8..49f7584 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -77,8 +77,8 @@
         initBroadcastReceiver()
     }
 
-    override fun onResume() {
-        super.onResume()
+    override fun onStart() {
+        super.onStart()
 
         parent = requireViewById<ViewGroup>(R.id.global_actions_controls)
         parent.alpha = 0f
@@ -91,8 +91,8 @@
         finish()
     }
 
-    override fun onPause() {
-        super.onPause()
+    override fun onStop() {
+        super.onStop()
 
         uiController.hide()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 166c265..59fcf87 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -43,6 +43,7 @@
 import com.android.systemui.log.dagger.LogModule;
 import com.android.systemui.lowlightclock.LowLightClockController;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBarComponent;
 import com.android.systemui.plugins.BcSmartspaceDataPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.Recents;
@@ -132,6 +133,7 @@
         },
         subcomponents = {
             CentralSurfacesComponent.class,
+            NavigationBarComponent.class,
             NotificationRowComponent.class,
             DozeComponent.class,
             ExpandableNotificationRowComponent.class,
diff --git a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
index 3543bb4..03ee8b1 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
@@ -15,8 +15,8 @@
  */
 
 package com.android.systemui.decor
+import android.content.Context
 import android.view.DisplayCutout
-import android.view.LayoutInflater
 import android.view.Surface
 import android.view.View
 import android.view.ViewGroup
@@ -38,9 +38,20 @@
     /** The aligned bounds for the view which is created through inflateView() */
     abstract val alignedBounds: List<Int>
 
+    /**
+     * Called when res info changed.
+     * Child provider needs to implement it if its view needs to be updated.
+     */
+    abstract fun onReloadResAndMeasure(
+        view: View,
+        reloadToken: Int,
+        @Surface.Rotation rotation: Int,
+        displayUniqueId: String? = null
+    )
+
     /** Inflate view into parent as current rotation */
     abstract fun inflateView(
-        inflater: LayoutInflater,
+        context: Context,
         parent: ViewGroup,
         @Surface.Rotation rotation: Int
     ): View
diff --git a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt
index 9f8679c..f38ff14 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt
@@ -16,31 +16,22 @@
 package com.android.systemui.decor
 
 import android.annotation.IdRes
-import android.view.DisplayCutout
-import android.view.LayoutInflater
+import android.content.Context
 import android.view.Surface
 import android.view.View
 import android.view.ViewGroup
-import com.android.systemui.R
-import java.util.HashMap
+import com.android.systemui.RegionInterceptingFrameLayout
 
-class OverlayWindow(private val layoutInflater: LayoutInflater, private val pos: Int) {
+class OverlayWindow(private val context: Context) {
 
-    private val layoutId: Int
-    get() {
-        return if (pos == DisplayCutout.BOUNDS_POSITION_LEFT ||
-                pos == DisplayCutout.BOUNDS_POSITION_TOP) {
-            R.layout.rounded_corners_top
-        } else {
-            R.layout.rounded_corners_bottom
-        }
-    }
+    val rootView = RegionInterceptingFrameLayout(context) as ViewGroup
+    private val viewProviderMap = mutableMapOf<Int, Pair<View, DecorProvider>>()
 
-    val rootView = layoutInflater.inflate(layoutId, null) as ViewGroup
-    private val viewProviderMap: MutableMap<Int, Pair<View, DecorProvider>> = HashMap()
-
-    fun addDecorProvider(decorProvider: DecorProvider, @Surface.Rotation rotation: Int) {
-        val view = decorProvider.inflateView(layoutInflater, rootView, rotation)
+    fun addDecorProvider(
+        decorProvider: DecorProvider,
+        @Surface.Rotation rotation: Int
+    ) {
+        val view = decorProvider.inflateView(context, rootView, rotation)
         viewProviderMap[decorProvider.viewId] = Pair(view, decorProvider)
     }
 
@@ -56,4 +47,35 @@
             viewProviderMap.remove(id)
         }
     }
+
+    /**
+     * Apply new configuration info into views.
+     * @param filterIds target view ids. Apply to all if null.
+     * @param rotation current or new rotation direction.
+     * @param displayUniqueId new displayUniqueId if any.
+     */
+    fun onReloadResAndMeasure(
+        filterIds: Array<Int>? = null,
+        reloadToken: Int,
+        @Surface.Rotation rotation: Int,
+        displayUniqueId: String? = null
+    ) {
+        filterIds?.forEach { id ->
+            viewProviderMap[id]?.let {
+                it.second.onReloadResAndMeasure(
+                        view = it.first,
+                        reloadToken = reloadToken,
+                        displayUniqueId = displayUniqueId,
+                        rotation = rotation)
+            }
+        } ?: run {
+            viewProviderMap.values.forEach {
+                it.second.onReloadResAndMeasure(
+                        view = it.first,
+                        reloadToken = reloadToken,
+                        displayUniqueId = displayUniqueId,
+                        rotation = rotation)
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
index 7afd7e0e..136f135 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.decor
 
+import android.content.Context
 import android.content.res.Resources
 import android.view.DisplayCutout
 import android.view.LayoutInflater
@@ -76,12 +77,21 @@
     private val layoutId: Int
 ) : CornerDecorProvider() {
 
+    override fun onReloadResAndMeasure(
+        view: View,
+        reloadToken: Int,
+        rotation: Int,
+        displayUniqueId: String?
+    ) {
+        // Do nothing here because it is handled inside PrivacyDotViewController
+    }
+
     override fun inflateView(
-        inflater: LayoutInflater,
+        context: Context,
         parent: ViewGroup,
         @Surface.Rotation rotation: Int
     ): View {
-        inflater.inflate(layoutId, parent, true)
+        LayoutInflater.from(context).inflate(layoutId, parent, true)
         return parent.getChildAt(parent.childCount - 1 /* latest new added child */)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderFactory.kt
new file mode 100644
index 0000000..4388b8b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderFactory.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.systemui.decor
+
+import android.view.DisplayCutout
+import com.android.systemui.R
+
+class RoundedCornerDecorProviderFactory(
+    private val roundedCornerResDelegate: RoundedCornerResDelegate
+) : DecorProviderFactory() {
+
+    override val hasProviders: Boolean
+        get() = roundedCornerResDelegate.run {
+            // We don't consider isMultipleRadius here because it makes no sense if size is zero.
+            topRoundedSize.width > 0 || bottomRoundedSize.width > 0
+        }
+
+    override val providers: List<DecorProvider>
+    get() {
+        val hasTop = roundedCornerResDelegate.topRoundedSize.width > 0
+        val hasBottom = roundedCornerResDelegate.bottomRoundedSize.width > 0
+        return when {
+            hasTop && hasBottom -> listOf(
+                RoundedCornerDecorProviderImpl(
+                    viewId = R.id.rounded_corner_top_left,
+                    alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP,
+                    alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT,
+                    roundedCornerResDelegate = roundedCornerResDelegate),
+                RoundedCornerDecorProviderImpl(
+                    viewId = R.id.rounded_corner_top_right,
+                    alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP,
+                    alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT,
+                    roundedCornerResDelegate = roundedCornerResDelegate),
+                RoundedCornerDecorProviderImpl(
+                    viewId = R.id.rounded_corner_bottom_left,
+                    alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM,
+                    alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT,
+                    roundedCornerResDelegate = roundedCornerResDelegate),
+                RoundedCornerDecorProviderImpl(
+                    viewId = R.id.rounded_corner_bottom_right,
+                    alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM,
+                    alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT,
+                    roundedCornerResDelegate = roundedCornerResDelegate)
+            )
+            hasTop -> listOf(
+                RoundedCornerDecorProviderImpl(
+                    viewId = R.id.rounded_corner_top_left,
+                    alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP,
+                    alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT,
+                    roundedCornerResDelegate = roundedCornerResDelegate),
+                RoundedCornerDecorProviderImpl(
+                    viewId = R.id.rounded_corner_top_right,
+                    alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP,
+                    alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT,
+                    roundedCornerResDelegate = roundedCornerResDelegate)
+            )
+            hasBottom -> listOf(
+                RoundedCornerDecorProviderImpl(
+                    viewId = R.id.rounded_corner_bottom_left,
+                    alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM,
+                    alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT,
+                    roundedCornerResDelegate = roundedCornerResDelegate),
+                RoundedCornerDecorProviderImpl(
+                    viewId = R.id.rounded_corner_bottom_right,
+                    alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM,
+                    alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT,
+                    roundedCornerResDelegate = roundedCornerResDelegate)
+            )
+            else -> emptyList()
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt
new file mode 100644
index 0000000..90ff950
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.decor
+
+import android.content.Context
+import android.view.DisplayCutout
+import android.view.Gravity
+import android.view.Surface
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.ImageView
+import com.android.systemui.R
+
+class RoundedCornerDecorProviderImpl(
+    override val viewId: Int,
+    @DisplayCutout.BoundsPosition override val alignedBound1: Int,
+    @DisplayCutout.BoundsPosition override val alignedBound2: Int,
+    private val roundedCornerResDelegate: RoundedCornerResDelegate
+) : CornerDecorProvider() {
+
+    private val isTop = alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP)
+
+    override fun inflateView(
+        context: Context,
+        parent: ViewGroup,
+        @Surface.Rotation rotation: Int
+    ): View {
+        return ImageView(context).also { view ->
+            // View
+            view.id = viewId
+            initView(view, rotation)
+
+            // LayoutParams
+            val layoutSize = if (isTop) {
+                roundedCornerResDelegate.topRoundedSize
+            } else {
+                roundedCornerResDelegate.bottomRoundedSize
+            }
+            val params = FrameLayout.LayoutParams(
+                    layoutSize.width,
+                    layoutSize.height,
+                    alignedBound1.toLayoutGravity(rotation) or
+                            alignedBound2.toLayoutGravity(rotation))
+
+            // AddView
+            parent.addView(view, params)
+        }
+    }
+
+    private fun initView(view: ImageView, @Surface.Rotation rotation: Int) {
+        view.setRoundedCornerImage(roundedCornerResDelegate, isTop)
+        view.adjustRotation(alignedBounds, rotation)
+        view.setColorFilter(IMAGE_TINT_COLOR)
+    }
+
+    override fun onReloadResAndMeasure(
+        view: View,
+        reloadToken: Int,
+        @Surface.Rotation rotation: Int,
+        displayUniqueId: String?
+    ) {
+        roundedCornerResDelegate.updateDisplayUniqueId(displayUniqueId, reloadToken)
+
+        initView((view as ImageView), rotation)
+
+        val layoutSize = if (isTop) {
+            roundedCornerResDelegate.topRoundedSize
+        } else {
+            roundedCornerResDelegate.bottomRoundedSize
+        }
+        (view.layoutParams as FrameLayout.LayoutParams).let {
+            it.width = layoutSize.width
+            it.height = layoutSize.height
+            it.gravity = alignedBound1.toLayoutGravity(rotation) or
+                    alignedBound2.toLayoutGravity(rotation)
+            view.setLayoutParams(it)
+        }
+    }
+}
+
+private const val IMAGE_TINT_COLOR: Int = 0xFF000000.toInt()
+
+@DisplayCutout.BoundsPosition
+private fun Int.toLayoutGravity(@Surface.Rotation rotation: Int): Int = when (rotation) {
+    Surface.ROTATION_0 -> when (this) {
+        DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.LEFT
+        DisplayCutout.BOUNDS_POSITION_TOP -> Gravity.TOP
+        DisplayCutout.BOUNDS_POSITION_RIGHT -> Gravity.RIGHT
+        else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.BOTTOM
+    }
+    Surface.ROTATION_90 -> when (this) {
+        DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.BOTTOM
+        DisplayCutout.BOUNDS_POSITION_TOP -> Gravity.LEFT
+        DisplayCutout.BOUNDS_POSITION_RIGHT -> Gravity.TOP
+        else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.LEFT
+    }
+    Surface.ROTATION_270 -> when (this) {
+        DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.TOP
+        DisplayCutout.BOUNDS_POSITION_TOP -> Gravity.RIGHT
+        DisplayCutout.BOUNDS_POSITION_RIGHT -> Gravity.BOTTOM
+        else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.LEFT
+    }
+    else /* Surface.ROTATION_180 */ -> when (this) {
+        DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.RIGHT
+        DisplayCutout.BOUNDS_POSITION_TOP -> Gravity.BOTTOM
+        DisplayCutout.BOUNDS_POSITION_RIGHT -> Gravity.LEFT
+        else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.TOP
+    }
+}
+
+private fun ImageView.setRoundedCornerImage(
+    resDelegate: RoundedCornerResDelegate,
+    isTop: Boolean
+) {
+    val drawable = if (isTop)
+        resDelegate.topRoundedDrawable
+    else
+        resDelegate.bottomRoundedDrawable
+
+    if (drawable != null) {
+        setImageDrawable(drawable)
+    } else {
+        setImageResource(
+                if (isTop)
+                    R.drawable.rounded_corner_top
+                else
+                    R.drawable.rounded_corner_bottom
+        )
+    }
+}
+
+/**
+ * Configures the rounded corner drawable's view matrix based on the gravity.
+ *
+ * The gravity describes which corner to configure for, and the drawable we are rotating is assumed
+ * to be oriented for the top-left corner of the device regardless of the target corner.
+ * Therefore we need to rotate 180 degrees to get a bottom-left corner, and mirror in the x- or
+ * y-axis for the top-right and bottom-left corners.
+ */
+private fun ImageView.adjustRotation(alignedBounds: List<Int>, @Surface.Rotation rotation: Int) {
+    var newRotation = 0F
+    var newScaleX = 1F
+    var newScaleY = 1F
+
+    val isTop = alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP)
+    val isLeft = alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_LEFT)
+    when (rotation) {
+        Surface.ROTATION_0 -> when {
+            isTop && isLeft -> {}
+            isTop && !isLeft -> { newScaleX = -1F }
+            !isTop && isLeft -> { newScaleY = -1F }
+            else /* !isTop && !isLeft */ -> { newRotation = 180F }
+        }
+        Surface.ROTATION_90 -> when {
+            isTop && isLeft -> { newScaleY = -1F }
+            isTop && !isLeft -> {}
+            !isTop && isLeft -> { newRotation = 180F }
+            else /* !isTop && !isLeft */ -> { newScaleX = -1F }
+        }
+        Surface.ROTATION_270 -> when {
+            isTop && isLeft -> { newScaleX = -1F }
+            isTop && !isLeft -> { newRotation = 180F }
+            !isTop && isLeft -> {}
+            else /* !isTop && !isLeft */ -> { newScaleY = -1F }
+        }
+        else /* Surface.ROTATION_180 */ -> when {
+            isTop && isLeft -> { newRotation = 180F }
+            isTop && !isLeft -> { newScaleY = -1F }
+            !isTop && isLeft -> { newScaleX = -1F }
+            else /* !isTop && !isLeft */ -> {}
+        }
+    }
+
+    this.rotation = newRotation
+    this.scaleX = newScaleX
+    this.scaleY = newScaleY
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
index c817f89..1d38e58 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
@@ -36,6 +36,8 @@
     private val density: Float
         get() = res.displayMetrics.density
 
+    private var reloadToken: Int = 0
+
     var isMultipleRadius: Boolean = false
         private set
 
@@ -60,12 +62,26 @@
         reloadMeasures()
     }
 
-    fun reloadAll(newDisplayUniqueId: String?) {
-        displayUniqueId = newDisplayUniqueId
+    private fun reloadAll(newReloadToken: Int) {
+        if (reloadToken == newReloadToken) {
+            return
+        }
+        reloadToken = newReloadToken
         reloadDrawables()
         reloadMeasures()
     }
 
+    fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) {
+        if (displayUniqueId != newDisplayUniqueId) {
+            displayUniqueId = newDisplayUniqueId
+            newReloadToken ?.let { reloadToken = it }
+            reloadDrawables()
+            reloadMeasures()
+        } else {
+            newReloadToken?.let { reloadAll(it) }
+        }
+    }
+
     private fun reloadDrawables() {
         val configIdx = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId)
         isMultipleRadius = getIsMultipleRadius(configIdx)
@@ -85,34 +101,6 @@
                 arrayResId = R.array.config_roundedCornerBottomDrawableArray,
                 backupDrawableId = R.drawable.rounded_corner_bottom
         ) ?: roundedDrawable
-
-        // If config_roundedCornerMultipleRadius set as true, ScreenDecorations respect the
-        // (width, height) size of drawable/rounded.xml instead of rounded_corner_radius
-        if (isMultipleRadius) {
-            roundedSize = Size(
-                    roundedDrawable?.intrinsicWidth ?: 0,
-                    roundedDrawable?.intrinsicHeight ?: 0)
-            topRoundedDrawable?.let {
-                topRoundedSize = Size(it.intrinsicWidth, it.intrinsicHeight)
-            }
-            bottomRoundedDrawable?.let {
-                bottomRoundedSize = Size(it.intrinsicWidth, it.intrinsicHeight)
-            }
-        } else {
-            val defaultRadius = RoundedCorners.getRoundedCornerRadius(res, displayUniqueId)
-            val topRadius = RoundedCorners.getRoundedCornerTopRadius(res, displayUniqueId)
-            val bottomRadius = RoundedCorners.getRoundedCornerBottomRadius(res, displayUniqueId)
-            roundedSize = Size(defaultRadius, defaultRadius)
-            topRoundedSize = Size(topRadius, topRadius)
-            bottomRoundedSize = Size(bottomRadius, bottomRadius)
-        }
-
-        if (topRoundedSize.width == 0) {
-            topRoundedSize = roundedSize
-        }
-        if (bottomRoundedSize.width == 0) {
-            bottomRoundedSize = roundedSize
-        }
     }
 
     private fun reloadMeasures(roundedSizeFactor: Int? = null) {
@@ -137,20 +125,25 @@
             bottomRoundedSize = Size(bottomRadius, bottomRadius)
         }
 
-        roundedSizeFactor ?.let {
-            val length: Int = (it * density).toInt()
-            roundedSize = Size(length, length)
-        }
-
         if (topRoundedSize.width == 0) {
             topRoundedSize = roundedSize
         }
         if (bottomRoundedSize.width == 0) {
             bottomRoundedSize = roundedSize
         }
+
+        if (roundedSizeFactor != null && roundedSizeFactor > 0) {
+            val length: Int = (roundedSizeFactor * density).toInt()
+            topRoundedSize = Size(length, length)
+            bottomRoundedSize = Size(length, length)
+        }
     }
 
-    fun updateTuningSizeFactor(factor: Int) {
+    fun updateTuningSizeFactor(factor: Int?, newReloadToken: Int) {
+        if (reloadToken == newReloadToken) {
+            return
+        }
+        reloadToken = newReloadToken
         reloadMeasures(factor)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index dfbb0c7..db225cf 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -26,10 +26,13 @@
 import android.view.WindowManager;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.LifecycleRegistry;
 import androidx.lifecycle.ViewModelStore;
 
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.policy.PhoneWindow;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -61,6 +64,7 @@
     private final DreamOverlayContainerViewController mDreamOverlayContainerViewController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final DreamPreviewComplication mPreviewComplication;
+    private final UiEventLogger mUiEventLogger;
 
     // A reference to the {@link Window} used to hold the dream overlay.
     private Window mWindow;
@@ -97,6 +101,25 @@
 
     private DreamOverlayStateController mStateController;
 
+    @VisibleForTesting
+    public enum DreamOverlayEvent implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "The dream overlay has entered start.")
+        DREAM_OVERLAY_ENTER_START(989),
+        @UiEvent(doc = "The dream overlay has completed start.")
+        DREAM_OVERLAY_COMPLETE_START(990);
+
+        private final int mId;
+
+        DreamOverlayEvent(int id) {
+            mId = id;
+        }
+
+        @Override
+        public int getId() {
+            return mId;
+        }
+    }
+
     @Inject
     public DreamOverlayService(
             Context context,
@@ -104,13 +127,15 @@
             DreamOverlayComponent.Factory dreamOverlayComponentFactory,
             DreamOverlayStateController stateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            DreamPreviewComplication previewComplication) {
+            DreamPreviewComplication previewComplication,
+            UiEventLogger uiEventLogger) {
         mContext = context;
         mExecutor = executor;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
         mStateController = stateController;
         mPreviewComplication = previewComplication;
+        mUiEventLogger = uiEventLogger;
 
         final DreamOverlayComponent component =
                 dreamOverlayComponentFactory.create(mViewModelStore, mHost);
@@ -143,6 +168,7 @@
 
     @Override
     public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
+        mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
         setCurrentState(Lifecycle.State.STARTED);
         mExecutor.execute(() -> {
             if (mDestroyed) {
@@ -159,6 +185,7 @@
             addOverlayWindowLocked(layoutParams);
             setCurrentState(Lifecycle.State.RESUMED);
             mStateController.setOverlayActive(true);
+            mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
         });
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt
index 4e228a1..9b99c52 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt
@@ -160,9 +160,8 @@
             return
         }
 
-        // TODO(b/217559844): Replace with "dream" session when available.
         val newSession = smartspaceManager.createSmartspaceSession(
-                SmartspaceConfig.Builder(context, "lockscreen").build())
+                SmartspaceConfig.Builder(context, "dream").build())
         Log.d(TAG, "Starting smartspace session for dream")
         newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
         this.session = newSession
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 61cfe92..9356b16 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -146,7 +146,6 @@
     // 900 - media
     public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true);
     public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, false);
-    public static final BooleanFlag MEDIA_SESSION_LAYOUT = new BooleanFlag(902, true);
     public static final BooleanFlag MEDIA_NEARBY_DEVICES = new BooleanFlag(903, true);
     public static final BooleanFlag MEDIA_MUTE_AWAIT = new BooleanFlag(904, true);
 
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index af553c7..acb080a 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -1067,13 +1067,7 @@
             // Add a little delay before executing, to give the dialog a chance to go away before
             // switching user
             mHandler.postDelayed(() -> {
-                try {
-                    int currentUserId = getCurrentUser().id;
-                    mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
-                    mIActivityManager.stopUser(currentUserId, true /*force*/, null);
-                } catch (RemoteException re) {
-                    Log.e(TAG, "Couldn't logout user " + re);
-                }
+                mDevicePolicyManager.logoutUser();
             }, mDialogPressDelay);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index acad30b..d36bb72 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1238,14 +1238,52 @@
     }
 
     /**
-     * Locks the keyguard if {@link #mPendingLock} is true, unless we're playing the screen off
-     * animation.
+     * Locks the keyguard if {@link #mPendingLock} is true, and there are no reasons to further
+     * delay the pending lock.
      *
-     * If we are, we will lock the keyguard either when the screen off animation ends, or in
-     * {@link #onStartedWakingUp} if the animation is cancelled.
+     * If you do delay handling the pending lock, you must ensure that this method is ALWAYS called
+     * again when the condition causing the delay changes. Otherwise, the device may remain unlocked
+     * indefinitely.
      */
     public void maybeHandlePendingLock() {
-        if (mPendingLock && !mScreenOffAnimationController.isKeyguardShowDelayed()) {
+        if (mPendingLock) {
+
+            // The screen off animation is playing, so if we lock now, the foreground app will
+            // vanish and the keyguard will jump-cut in. Delay it, until either:
+            //   - The screen off animation ends. We will call maybeHandlePendingLock from
+            //     the end action in UnlockedScreenOffAnimationController#animateInKeyguard.
+            //   - 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.");
+                }
+
+                return;
+            }
+
+            // The device was re-locked while in the process of unlocking. If we lock now, callbacks
+            // in the unlock sequence might end up re-unlocking the device. Delay the lock until the
+            // keyguard is done going away. We'll call maybeHandlePendingLock again in
+            // 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.");
+                }
+
+                return;
+            }
+
+            if (DEBUG) {
+                Log.d(TAG, "#maybeHandlePendingLock: handling pending lock; locking keyguard.");
+            }
+
             doKeyguardLocked(null);
             mPendingLock = false;
         }
@@ -1669,16 +1707,11 @@
             return;
         }
 
-        // If the keyguard is already showing, don't bother unless it was in the process of going
-        // away. If it was going away, keyguard state may be out of sync and we should make sure to
-        // re-show it explicitly. Check flags in both files to account for the hiding animation
-        // which results in a delay and discrepancy between flags.
-        if ((mShowing && mKeyguardViewControllerLazy.get().isShowing())
-                && !mKeyguardStateController.isKeyguardGoingAway()) {
-            if (DEBUG) {
-                Log.d(TAG, "doKeyguard: not showing "
-                        + "because it is already showing and not going away");
-            }
+        // if the keyguard is already showing, don't bother. check flags in both files
+        // 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");
             resetStateLocked();
             return;
         }
@@ -2185,14 +2218,7 @@
             mKeyguardExitAnimationRunner = null;
             mScreenOnCoordinator.setWakeAndUnlocking(false);
             mPendingLock = false;
-
-            // If we're asked to re-show while the keyguard is going away, force callbacks to ensure
-            // that state is re-set correctly. Otherwise, we might short circuit since mShowing is
-            // true during the keyguard going away process, despite having partially set some state
-            // to unlocked.
-            setShowingLocked(
-                    true, mKeyguardStateController.isKeyguardGoingAway() /* forceCallbacks */);
-
+            setShowingLocked(true);
             mKeyguardViewControllerLazy.get().show(options);
             resetKeyguardDonePendingLocked();
             mHideAnimationRun = false;
@@ -2362,28 +2388,14 @@
                             @Override
                             public void onAnimationFinished() throws RemoteException {
                                 try {
-                                    // WindowManager always needs to know that this animation
-                                    // finished so it does not wait the 10s until timeout.
                                     finishedCallback.onAnimationFinished();
                                 } catch (RemoteException e) {
                                     Slog.w(TAG, "Failed to call onAnimationFinished", e);
                                 }
-
-                                // If we're not interactive, it means the device is going back to
-                                // sleep. This happens if the power button is pressed during the
-                                // activity launch. If we're going back to sleep, we should *not*
-                                // run keyguard exit finished callbacks and hide the keyguard, since
-                                // we are in the process of locking again and this might result in
-                                // the device staying unlocked when it shouldn't.
-                                // We need to directly query isInteractive rather than mGoingToSleep
-                                // because mGoingToSleep is set in onStartedGoingToSleep, which is
-                                // dispatched asynchronously.
-                                if (mPM.isInteractive()) {
-                                    onKeyguardExitFinished();
-                                    mKeyguardViewControllerLazy.get().hide(0 /* startTime */,
-                                            0 /* fadeoutDuration */);
-                                    mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
-                                }
+                                onKeyguardExitFinished();
+                                mKeyguardViewControllerLazy.get().hide(0 /* startTime */,
+                                        0 /* fadeoutDuration */);
+                                mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
                             }
 
                             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
index c3f4ce9..5a8b7e3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
@@ -45,8 +45,7 @@
     private val statusBarStateController: SysuiStatusBarStateController,
     private val notifLockscreenUserManager: NotificationLockscreenUserManager,
     private val context: Context,
-    configurationController: ConfigurationController,
-    private val mediaFlags: MediaFlags
+    configurationController: ConfigurationController
 ) {
 
     init {
@@ -62,11 +61,7 @@
         })
 
         // First let's set the desired state that we want for this host
-        mediaHost.expansion = if (mediaFlags.useMediaSessionLayout()) {
-            MediaHostState.EXPANDED
-        } else {
-            MediaHostState.COLLAPSED
-        }
+        mediaHost.expansion = MediaHostState.EXPANDED
         mediaHost.showsOnlyActiveMedia = true
         mediaHost.falsingProtectionNeeded = true
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 83ad027..20029fe 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -419,15 +419,8 @@
                 .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
         if (existingPlayer == null) {
             var newPlayer = mediaControlPanelFactory.get()
-            if (mediaFlags.useMediaSessionLayout()) {
-                newPlayer.attachPlayer(
-                        PlayerSessionViewHolder.create(LayoutInflater.from(context), mediaContent),
-                        MediaViewController.TYPE.PLAYER_SESSION)
-            } else {
-                newPlayer.attachPlayer(
-                        PlayerViewHolder.create(LayoutInflater.from(context), mediaContent),
-                        MediaViewController.TYPE.PLAYER)
-            }
+            newPlayer.attachPlayer(MediaViewHolder.create(
+                    LayoutInflater.from(context), mediaContent))
             newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
             val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                     ViewGroup.LayoutParams.WRAP_CONTENT)
@@ -522,7 +515,7 @@
 
     private fun recreatePlayers() {
         bgColor = getBackgroundColor()
-        pageIndicator.tintList = ColorStateList.valueOf(getForegroundColor())
+        pageIndicator.tintList = ColorStateList.valueOf(R.color.material_dynamic_neutral_variant80)
 
         MediaPlayerData.mediaData().forEach { (key, data, isSsMediaRec) ->
             if (isSsMediaRec) {
@@ -543,14 +536,6 @@
         return context.getColor(R.color.material_dynamic_secondary95)
     }
 
-    private fun getForegroundColor(): Int {
-        return if (mediaFlags.useMediaSessionLayout()) {
-            context.getColor(R.color.material_dynamic_neutral_variant80)
-        } else {
-            context.getColor(R.color.material_dynamic_secondary10)
-        }
-    }
-
     private fun updatePageIndicator() {
         val numPages = mediaContent.getChildCount()
         pageIndicator.setNumPages(numPages)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index ffdd537..958c244 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -35,7 +35,6 @@
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
 import android.os.Process;
-import android.text.Layout;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -65,7 +64,6 @@
 import com.android.systemui.util.time.SystemClock;
 
 import java.net.URISyntaxException;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -96,22 +94,22 @@
 
     private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS);
 
-    // Button IDs for QS controls
-    static final int[] ACTION_IDS = {
-            R.id.action0,
-            R.id.action1,
-            R.id.action2,
-            R.id.action3,
-            R.id.action4
-    };
-
     // Buttons to show in small player when using semantic actions
-    private static final List<Integer> SEMANTIC_ACTION_IDS = List.of(
+    private static final List<Integer> SEMANTIC_ACTIONS_COMPACT = List.of(
             R.id.actionPlayPause,
             R.id.actionPrev,
             R.id.actionNext
     );
 
+    // Buttons to show in small player when using semantic actions
+    private static final List<Integer> SEMANTIC_ACTIONS_ALL = List.of(
+            R.id.actionPlayPause,
+            R.id.actionPrev,
+            R.id.actionNext,
+            R.id.action0,
+            R.id.action1
+    );
+
     private final SeekBarViewModel mSeekBarViewModel;
     private SeekBarObserver mSeekBarObserver;
     protected final Executor mBackgroundExecutor;
@@ -127,8 +125,6 @@
     private MediaController mController;
     private Lazy<MediaDataManager> mMediaDataManagerLazy;
     private int mBackgroundColor;
-    private int mDevicePadding;
-    private int mAlbumArtSize;
     // Instance id for logging purpose.
     protected int mInstanceId = -1;
     // Uid for the media app.
@@ -167,7 +163,6 @@
         mMediaCarouselController = mediaCarouselController;
         mFalsingManager = falsingManager;
         mSystemClock = systemClock;
-        loadDimens();
 
         mSeekBarViewModel.setLogSmartspaceClick(() -> {
             logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT,
@@ -184,12 +179,6 @@
         mMediaViewController.onDestroy();
     }
 
-    private void loadDimens() {
-        mAlbumArtSize = mContext.getResources().getDimensionPixelSize(R.dimen.qs_media_album_size);
-        mDevicePadding = mContext.getResources()
-                .getDimensionPixelSize(R.dimen.qs_media_album_device_padding);
-    }
-
     /**
      * Get the view holder used to display media controls.
      *
@@ -241,15 +230,14 @@
     }
 
     /** Attaches the player to the player view holder. */
-    public void attachPlayer(MediaViewHolder vh, MediaViewController.TYPE playerType) {
+    public void attachPlayer(MediaViewHolder vh) {
         mMediaViewHolder = vh;
         TransitionLayout player = vh.getPlayer();
 
-        boolean useSessionLayout = playerType == MediaViewController.TYPE.PLAYER_SESSION;
-        mSeekBarObserver = new SeekBarObserver(vh, useSessionLayout);
+        mSeekBarObserver = new SeekBarObserver(vh);
         mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver);
         mSeekBarViewModel.attachTouchHandlers(vh.getSeekBar());
-        mMediaViewController.attach(player, playerType);
+        mMediaViewController.attach(player, MediaViewController.TYPE.PLAYER);
 
         vh.getPlayer().setOnLongClickListener(v -> {
             if (!mMediaViewController.isGutsVisible()) {
@@ -305,16 +293,6 @@
         if (mMediaViewHolder == null) {
             return;
         }
-        bindPlayerCommon(data, key);
-        if (mMediaViewHolder instanceof PlayerViewHolder) {
-            bindNotificationPlayer(data, key);
-        } else if (mMediaViewHolder instanceof PlayerSessionViewHolder) {
-            bindSessionPlayer(data, key);
-        }
-    }
-
-    /** Bind elements common to both layouts */
-    private void bindPlayerCommon(@NonNull MediaData data, String key) {
         mKey = key;
         MediaSession.Token token = data.getToken();
         PackageManager packageManager = mContext.getPackageManager();
@@ -371,18 +349,24 @@
         final MediaController controller = getController();
         mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
 
-        // Guts label
-        boolean isDismissible = data.isClearable();
-        mMediaViewHolder.getLongPressText().setText(isDismissible
-                ? R.string.controls_media_close_session
-                : R.string.controls_media_active_session);
+        bindOutputSwitcherChip(data);
+        bindLongPressMenu(data);
+        bindActionButtons(data);
+        bindArtworkAndColors(data);
 
+        // TODO: We don't need to refresh this state constantly, only if the state actually changed
+        // to something which might impact the measurement
+        mMediaViewController.refreshState();
+    }
+
+    private void bindOutputSwitcherChip(MediaData data) {
         // Output switcher chip
         ViewGroup seamlessView = mMediaViewHolder.getSeamless();
         seamlessView.setVisibility(View.VISIBLE);
         ImageView iconView = mMediaViewHolder.getSeamlessIcon();
         TextView deviceName = mMediaViewHolder.getSeamlessText();
         final MediaDeviceData device = data.getDevice();
+
         // Disable clicking on output switcher for invalid devices and resumption controls
         final boolean seamlessDisabled = (device != null && !device.getEnabled())
                 || data.getResumption();
@@ -426,9 +410,20 @@
                         mMediaOutputDialogFactory.create(data.getPackageName(), true,
                                 mMediaViewHolder.getSeamlessButton());
                     }
-            });
+                });
+    }
 
-        // Dismiss
+    private void bindLongPressMenu(MediaData data) {
+        boolean isDismissible = data.isClearable();
+        String dismissText;
+        if (isDismissible) {
+            dismissText = mContext.getString(R.string.controls_media_close_session, data.getApp());
+        } else {
+            dismissText = mContext.getString(R.string.controls_media_active_session);
+        }
+        mMediaViewHolder.getLongPressText().setText(dismissText);
+
+        // Dismiss button
         mMediaViewHolder.getDismissText().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
         mMediaViewHolder.getDismiss().setEnabled(isDismissible);
         mMediaViewHolder.getDismiss().setOnClickListener(v -> {
@@ -443,136 +438,16 @@
                         MediaViewController.GUTS_ANIMATION_DURATION + 100)) {
                     Log.w(TAG, "Manager failed to dismiss media " + mKey);
                     // Remove directly from carousel so user isn't stuck with defunct controls
-                    mMediaCarouselController.removePlayer(key, false, false);
+                    mMediaCarouselController.removePlayer(mKey, false, false);
                 }
             } else {
                 Log.w(TAG, "Dismiss media with null notification. Token uid="
                         + data.getToken().getUid());
             }
         });
-
-        // TODO: We don't need to refresh this state constantly, only if the state actually changed
-        // to something which might impact the measurement
-        mMediaViewController.refreshState();
     }
 
-    /** Bind elements specific to PlayerViewHolder */
-    private void bindNotificationPlayer(@NonNull MediaData data, String key) {
-        ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
-        ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
-
-        // Album art
-        ImageView albumView = mMediaViewHolder.getAlbumView();
-        boolean hasArtwork = data.getArtwork() != null;
-        if (hasArtwork) {
-            Drawable artwork = getScaledThumbnail(data.getArtwork());
-            albumView.setPadding(0, 0, 0, 0);
-            albumView.setImageDrawable(artwork);
-        } else {
-            Drawable deviceIcon;
-            if (data.getDevice() != null && data.getDevice().getIcon() != null) {
-                deviceIcon = data.getDevice().getIcon().getConstantState().newDrawable().mutate();
-            } else {
-                deviceIcon = getContext().getDrawable(R.drawable.ic_headphone);
-            }
-            deviceIcon.setTintList(ColorStateList.valueOf(mBackgroundColor));
-            albumView.setPadding(mDevicePadding, mDevicePadding, mDevicePadding, mDevicePadding);
-            albumView.setImageDrawable(deviceIcon);
-        }
-
-        // App icon - use notification icon
-        ImageView appIconView = mMediaViewHolder.getAppIcon();
-        appIconView.clearColorFilter();
-        if (data.getAppIcon() != null && !data.getResumption()) {
-            appIconView.setImageIcon(data.getAppIcon());
-            int color = mContext.getColor(R.color.material_dynamic_secondary10);
-            appIconView.setColorFilter(color);
-        } else {
-            // Resume players use launcher icon
-            appIconView.setColorFilter(getGrayscaleFilter());
-            try {
-                Drawable icon = mContext.getPackageManager().getApplicationIcon(
-                        data.getPackageName());
-                appIconView.setImageDrawable(icon);
-            } catch (PackageManager.NameNotFoundException e) {
-                Log.w(TAG, "Cannot find icon for package " + data.getPackageName(), e);
-                appIconView.setImageResource(R.drawable.ic_music_note);
-            }
-        }
-
-        // Media action buttons
-        List<MediaAction> actionIcons = data.getActions();
-        List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
-
-        // If we got session actions, use those instead
-        if (data.getSemanticActions() != null) {
-            MediaButton semanticActions = data.getSemanticActions();
-
-            actionIcons = new ArrayList<MediaAction>();
-            actionIcons.add(semanticActions.getCustom0());
-            actionIcons.add(semanticActions.getPrevOrCustom());
-            actionIcons.add(semanticActions.getPlayOrPause());
-            actionIcons.add(semanticActions.getNextOrCustom());
-            actionIcons.add(semanticActions.getCustom1());
-
-            actionsWhenCollapsed = new ArrayList<Integer>();
-            actionsWhenCollapsed.add(1);
-            actionsWhenCollapsed.add(2);
-            actionsWhenCollapsed.add(3);
-        }
-
-        int i = 0;
-        for (; i < actionIcons.size() && i < ACTION_IDS.length; i++) {
-            int actionId = ACTION_IDS[i];
-            boolean visibleInCompat = actionsWhenCollapsed.contains(i);
-            final ImageButton button = mMediaViewHolder.getAction(actionId);
-            MediaAction mediaAction = actionIcons.get(i);
-            if (mediaAction != null) {
-                button.setImageIcon(mediaAction.getIcon());
-                button.setContentDescription(mediaAction.getContentDescription());
-                Runnable action = mediaAction.getAction();
-
-                if (action == null) {
-                    button.setEnabled(false);
-                } else {
-                    button.setEnabled(true);
-                    button.setOnClickListener(v -> {
-                        if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-                            logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT,
-                                    /* isRecommendationCard */ false);
-                            action.run();
-                        }
-                    });
-                }
-                setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
-                setVisibleAndAlpha(expandedSet, actionId, true /*visible */);
-            } else {
-                button.setImageIcon(null);
-                button.setContentDescription(null);
-                button.setEnabled(false);
-                setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
-                // for expanded layout, set as INVISIBLE so that we still reserve space in the UI
-                expandedSet.setVisibility(actionId, ConstraintSet.INVISIBLE);
-                expandedSet.setAlpha(actionId, 0.0f);
-            }
-        }
-
-        // Hide any unused buttons
-        for (; i < ACTION_IDS.length; i++) {
-            setVisibleAndAlpha(collapsedSet, ACTION_IDS[i], false /*visible */);
-            setVisibleAndAlpha(expandedSet, ACTION_IDS[i], false /* visible */);
-        }
-        // If no actions, set the first view as INVISIBLE so expanded height remains constant
-        if (actionIcons.size() == 0) {
-            expandedSet.setVisibility(ACTION_IDS[0], ConstraintSet.INVISIBLE);
-        }
-    }
-
-    /** Bind elements specific to PlayerSessionViewHolder */
-    private void bindSessionPlayer(@NonNull MediaData data, String key) {
-        ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
-        ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
-
+    private void bindArtworkAndColors(MediaData data) {
         // Default colors
         int surfaceColor = mBackgroundColor;
         int accentPrimary = com.android.settingslib.Utils.getColorAttr(mContext,
@@ -592,7 +467,7 @@
         boolean hasArtwork = data.getArtwork() != null;
         if (hasArtwork) {
             colorScheme = new ColorScheme(WallpaperColors.fromBitmap(data.getArtwork().getBitmap()),
-                        true);
+                    true);
 
             // Scale artwork to fit background
             int width = mMediaViewHolder.getPlayer().getWidth();
@@ -661,6 +536,14 @@
         seekbar.setProgressTintList(textColorList);
         seekbar.setProgressBackgroundTintList(ColorStateList.valueOf(textTertiary));
 
+        // Action buttons
+        mMediaViewHolder.getActionPlayPause().setBackgroundTintList(accentColorList);
+        mMediaViewHolder.getActionPlayPause().setImageTintList(
+                ColorStateList.valueOf(textPrimaryInverse));
+        for (ImageButton button : mMediaViewHolder.getTransparentActionButtons()) {
+            button.setImageTintList(textColorList);
+        }
+
         // Output switcher
         View seamlessView = mMediaViewHolder.getSeamlessButton();
         seamlessView.setBackgroundTintList(accentColorList);
@@ -669,21 +552,26 @@
         TextView seamlessText = mMediaViewHolder.getSeamlessText();
         seamlessText.setTextColor(surfaceColor);
 
-        // Media action buttons
+        // Long press buttons
+        mMediaViewHolder.getLongPressText().setTextColor(textColorList);
+        mMediaViewHolder.getSettings().setImageTintList(accentColorList);
+        mMediaViewHolder.getCancelText().setTextColor(textColorList);
+        mMediaViewHolder.getCancelText().setBackgroundTintList(accentColorList);
+        mMediaViewHolder.getDismissText().setTextColor(surfaceColor);
+        mMediaViewHolder.getDismissText().setBackgroundTintList(accentColorList);
+    }
+
+    private void bindActionButtons(MediaData data) {
         MediaButton semanticActions = data.getSemanticActions();
-        PlayerSessionViewHolder sessionHolder = (PlayerSessionViewHolder) mMediaViewHolder;
         ImageButton[] genericButtons = new ImageButton[]{
-                sessionHolder.getAction0(),
-                sessionHolder.getAction1(),
-                sessionHolder.getAction2(),
-                sessionHolder.getAction3(),
-                sessionHolder.getAction4()};
+                mMediaViewHolder.getAction0(),
+                mMediaViewHolder.getAction1(),
+                mMediaViewHolder.getAction2(),
+                mMediaViewHolder.getAction3(),
+                mMediaViewHolder.getAction4()};
 
-        ImageButton[] semanticButtons = new ImageButton[]{
-                sessionHolder.getActionPlayPause(),
-                sessionHolder.getActionNext(),
-                sessionHolder.getActionPrev()};
-
+        ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
+        ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
         if (semanticActions != null) {
             // Hide all the generic buttons
             for (ImageButton b: genericButtons) {
@@ -691,22 +579,15 @@
                 setVisibleAndAlpha(expandedSet, b.getId(), false);
             }
 
-            // Play/pause button has a background
-            sessionHolder.getActionPlayPause().setBackgroundTintList(accentColorList);
-            setSemanticButton(sessionHolder.getActionPlayPause(), semanticActions.getPlayOrPause(),
-                    ColorStateList.valueOf(textPrimaryInverse), collapsedSet, expandedSet, true);
-
-            setSemanticButton(sessionHolder.getActionNext(), semanticActions.getNextOrCustom(),
-                    textColorList, collapsedSet, expandedSet, true);
-            setSemanticButton(sessionHolder.getActionPrev(), semanticActions.getPrevOrCustom(),
-                    textColorList, collapsedSet, expandedSet, true);
-            setSemanticButton(sessionHolder.getAction0(), semanticActions.getCustom0(),
-                    textColorList, collapsedSet, expandedSet, false);
-            setSemanticButton(sessionHolder.getAction1(), semanticActions.getCustom1(),
-                    textColorList, collapsedSet, expandedSet, false);
+            for (int id : SEMANTIC_ACTIONS_ALL) {
+                boolean showInCompact = SEMANTIC_ACTIONS_COMPACT.contains(id);
+                ImageButton button = mMediaViewHolder.getAction(id);
+                MediaAction action = semanticActions.getActionById(id);
+                setSemanticButton(button, action, collapsedSet, expandedSet, showInCompact);
+            }
         } else {
-            // Hide all the semantic buttons
-            for (int id : SEMANTIC_ACTION_IDS) {
+            // Hide buttons that only appear for semantic actions
+            for (int id : SEMANTIC_ACTIONS_COMPACT) {
                 setVisibleAndAlpha(collapsedSet, id, false);
                 setVisibleAndAlpha(expandedSet, id, false);
             }
@@ -717,13 +598,12 @@
             int i = 0;
             for (; i < actions.size(); i++) {
                 boolean showInCompact = actionsWhenCollapsed.contains(i);
-                setSemanticButton(genericButtons[i], actions.get(i), textColorList, collapsedSet,
+                setSemanticButton(genericButtons[i], actions.get(i),  collapsedSet,
                         expandedSet, showInCompact);
             }
             for (; i < 5; i++) {
                 // Hide any unused buttons
-                setSemanticButton(genericButtons[i], null, textColorList, collapsedSet,
-                        expandedSet, false);
+                setSemanticButton(genericButtons[i], null,  collapsedSet, expandedSet, false);
             }
         }
 
@@ -732,21 +612,10 @@
         expandedSet.setVisibility(R.id.media_progress_bar,
                 seekbarEnabled ? ConstraintSet.VISIBLE : ConstraintSet.INVISIBLE);
         expandedSet.setAlpha(R.id.media_progress_bar, seekbarEnabled ? 1.0f : 0.0f);
-
-        // Long press buttons
-        mMediaViewHolder.getLongPressText().setTextColor(textColorList);
-        mMediaViewHolder.getSettingsText().setTextColor(textColorList);
-        mMediaViewHolder.getSettingsText().setBackgroundTintList(accentColorList);
-        mMediaViewHolder.getCancelText().setTextColor(textColorList);
-        mMediaViewHolder.getCancelText().setBackgroundTintList(accentColorList);
-        mMediaViewHolder.getDismissText().setTextColor(textColorList);
-        mMediaViewHolder.getDismissText().setBackgroundTintList(accentColorList);
     }
 
     private void setSemanticButton(final ImageButton button, MediaAction mediaAction,
-            ColorStateList fgColor, ConstraintSet collapsedSet, ConstraintSet expandedSet,
-            boolean showInCompact) {
-        button.setImageTintList(fgColor);
+            ConstraintSet collapsedSet, ConstraintSet expandedSet, boolean showInCompact) {
         if (mediaAction != null) {
             button.setImageIcon(mediaAction.getIcon());
             button.setContentDescription(mediaAction.getContentDescription());
@@ -982,60 +851,15 @@
     }
 
     private void openGuts() {
-        ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
-        ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
-
-        boolean wasTruncated = false;
-        Layout l = null;
         if (mMediaViewHolder != null) {
             mMediaViewHolder.marquee(true, mMediaViewController.GUTS_ANIMATION_DURATION);
-            l = mMediaViewHolder.getSettingsText().getLayout();
         } else if (mRecommendationViewHolder != null) {
             mRecommendationViewHolder.marquee(true, mMediaViewController.GUTS_ANIMATION_DURATION);
-            l = mRecommendationViewHolder.getSettingsText().getLayout();
         }
-        if (l != null) {
-            wasTruncated = l.getEllipsisCount(0) > 0;
-        }
-        mMediaViewController.setShouldHideGutsSettings(wasTruncated);
-        if (wasTruncated) {
-            // not enough room for the settings button to show fully, let's hide it
-            expandedSet.constrainMaxWidth(R.id.settings, 0);
-            collapsedSet.constrainMaxWidth(R.id.settings, 0);
-        }
-
         mMediaViewController.openGuts();
     }
 
     /**
-     * Scale drawable to fit into the square album art thumbnail
-     */
-    @UiThread
-    private Drawable getScaledThumbnail(Icon icon) {
-        if (icon == null) {
-            return null;
-        }
-        // Let's scale down the View, such that the content always nicely fills the view.
-        // ThumbnailUtils actually scales it down such that it may not be filled for odd aspect
-        // ratios
-        Drawable drawable = icon.loadDrawable(mContext);
-        float aspectRatio = drawable.getIntrinsicHeight() / (float) drawable.getIntrinsicWidth();
-        Rect bounds;
-        if (aspectRatio > 1.0f) {
-            bounds = new Rect(0, 0, mAlbumArtSize, (int) (mAlbumArtSize * aspectRatio));
-        } else {
-            bounds = new Rect(0, 0, (int) (mAlbumArtSize / aspectRatio), mAlbumArtSize);
-        }
-        if (bounds.width() > mAlbumArtSize || bounds.height() > mAlbumArtSize) {
-            float offsetX = (bounds.width() - mAlbumArtSize) / 2.0f;
-            float offsetY = (bounds.height() - mAlbumArtSize) / 2.0f;
-            bounds.offset((int) -offsetX, (int) -offsetY);
-        }
-        drawable.setBounds(bounds);
-        return drawable;
-    }
-
-    /**
      * Scale artwork to fill the background of the panel
      */
     @UiThread
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index 4cf6291..f1712db 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -20,6 +20,7 @@
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
 import android.media.session.MediaSession
+import com.android.systemui.R
 
 /** State of a media view. */
 data class MediaData(
@@ -154,7 +155,18 @@
      * Second custom action space
      */
     var custom1: MediaAction? = null
-)
+) {
+    fun getActionById(id: Int): MediaAction? {
+        return when (id) {
+            R.id.actionPlayPause -> playOrPause
+            R.id.actionNext -> nextOrCustom
+            R.id.actionPrev -> prevOrCustom
+            R.id.action0 -> custom0
+            R.id.action1 -> custom1
+            else -> null
+        }
+    }
+}
 
 /** State of a media action. */
 data class MediaAction(
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
index 59237d9..b85ae48 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
@@ -35,13 +35,6 @@
     }
 
     /**
-     * Check whether media controls should use the new session-based layout
-     */
-    fun useMediaSessionLayout(): Boolean {
-        return featureFlags.isEnabled(Flags.MEDIA_SESSION_LAYOUT)
-    }
-
-    /**
      * Check whether we support displaying information about mute await connections.
      */
     fun areMuteAwaitConnectionsEnabled() = featureFlags.isEnabled(Flags.MEDIA_MUTE_AWAIT)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index d472aee..d978d02 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -98,6 +98,10 @@
     private var currentBounds = Rect()
     private var animationStartBounds: Rect = Rect()
 
+    private var animationStartClipping = Rect()
+    private var currentClipping = Rect()
+    private var targetClipping = Rect()
+
     /**
      * The cross fade progress at the start of the animation. 0.5f means it's just switching between
      * the start and the end location and the content is fully faded, while 0.75f means that we're
@@ -144,7 +148,8 @@
             }
             interpolateBounds(animationStartBounds, targetBounds, boundsProgress,
                     result = currentBounds)
-            applyState(currentBounds, currentAlpha)
+            resolveClipping(currentClipping)
+            applyState(currentBounds, currentAlpha, clipBounds = currentClipping)
         }
         addListener(object : AnimatorListenerAdapter() {
             private var cancelled: Boolean = false
@@ -169,6 +174,12 @@
         })
     }
 
+    private fun resolveClipping(result: Rect) {
+        if (animationStartClipping.isEmpty) result.set(targetClipping)
+        else if (targetClipping.isEmpty) result.set(animationStartClipping)
+        else result.setIntersect(animationStartClipping, targetClipping)
+    }
+
     private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_DREAM_OVERLAY + 1)
     /**
      * The last location where this view was at before going to the desired location. This is
@@ -629,10 +640,12 @@
                 // We also go in here in case the view was detached, since the bounds wouldn't
                 // be correct anymore
                 animationStartBounds.set(currentBounds)
+                animationStartClipping.set(currentClipping)
             } else {
                 // otherwise, let's take the freshest state, since the current one could
                 // be outdated
                 animationStartBounds.set(previousHost.currentBounds)
+                animationStartClipping.set(previousHost.currentClipping)
             }
             val transformationType = calculateTransformationType()
             var needsCrossFade = transformationType == TRANSFORMATION_TYPE_FADE
@@ -745,7 +758,7 @@
             // Let's immediately apply the target state (which is interpolated) if there is
             // no animation running. Otherwise the animation update will already update
             // the location
-            applyState(targetBounds, carouselAlpha)
+            applyState(targetBounds, carouselAlpha, clipBounds = targetClipping)
         }
     }
 
@@ -769,9 +782,11 @@
             val newBounds = endHost.currentBounds
             val previousBounds = starthost.currentBounds
             targetBounds = interpolateBounds(previousBounds, newBounds, progress)
+            targetClipping = endHost.currentClipping
         } else if (endHost != null) {
             val bounds = endHost.currentBounds
             targetBounds.set(bounds)
+            targetClipping = endHost.currentClipping
         }
     }
 
@@ -879,8 +894,14 @@
     /**
      * Apply the current state to the view, updating it's bounds and desired state
      */
-    private fun applyState(bounds: Rect, alpha: Float, immediately: Boolean = false) {
+    private fun applyState(
+        bounds: Rect,
+        alpha: Float,
+        immediately: Boolean = false,
+        clipBounds: Rect = EMPTY_RECT
+    ) {
         currentBounds.set(bounds)
+        currentClipping = clipBounds
         carouselAlpha = if (isCurrentlyFading()) alpha else 1.0f
         val onlyUseEndState = !isCurrentlyInGuidedTransformation() || isCurrentlyFading()
         val startLocation = if (onlyUseEndState) -1 else previousLocation
@@ -889,6 +910,10 @@
         mediaCarouselController.setCurrentState(startLocation, endLocation, progress, immediately)
         updateHostAttachment()
         if (currentAttachmentLocation == IN_OVERLAY) {
+            // Setting the clipping on the hierarchy of `mediaFrame` does not work
+            if (!currentClipping.isEmpty) {
+                currentBounds.intersect(currentClipping)
+            }
             mediaFrame.setLeftTopRightBottom(
                     currentBounds.left,
                     currentBounds.top,
@@ -1113,6 +1138,7 @@
         const val TRANSFORMATION_TYPE_FADE = 1
     }
 }
+private val EMPTY_RECT = Rect()
 
 @IntDef(prefix = ["TRANSFORMATION_TYPE_"], value = [
     MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION,
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index eb209f7..d08b6f82 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -55,6 +55,13 @@
             return field
         }
 
+    /**
+     * Set the clipping that this host should use, based on its parent's bounds.
+     *
+     * Use [Rect.set].
+     */
+    val currentClipping = Rect()
+
     private val listener = object : MediaDataManager.Listener {
         override fun onMediaDataLoaded(
             key: String,
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
index a60016b..5210499 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
@@ -22,7 +22,10 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import com.android.systemui.R
 import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.animation.*
+import com.android.systemui.util.animation.MeasurementOutput
+import com.android.systemui.util.animation.TransitionLayout
+import com.android.systemui.util.animation.TransitionLayoutController
+import com.android.systemui.util.animation.TransitionViewState
 import javax.inject.Inject
 
 /**
@@ -40,7 +43,7 @@
      * session-based player, or recommendation
      */
     enum class TYPE {
-        PLAYER, PLAYER_SESSION, RECOMMENDATION
+        PLAYER, RECOMMENDATION
     }
 
     companion object {
@@ -186,11 +189,6 @@
     var isGutsVisible = false
         private set
 
-    /**
-     * Whether the settings button in the guts should be visible
-     */
-    var shouldHideGutsSettings = false
-
     init {
         mediaHostStatesManager.addController(this)
         layoutController.sizeChangedListener = { width: Int, height: Int ->
@@ -259,13 +257,11 @@
      */
     private fun setGutsViewState(viewState: TransitionViewState) {
         val controlsIds = when (type) {
-            TYPE.PLAYER -> PlayerViewHolder.controlsIds
-            TYPE.PLAYER_SESSION -> PlayerSessionViewHolder.controlsIds
+            TYPE.PLAYER -> MediaViewHolder.controlsIds
             TYPE.RECOMMENDATION -> RecommendationViewHolder.controlsIds
         }
         val gutsIds = when (type) {
-            TYPE.PLAYER -> PlayerViewHolder.gutsIds
-            TYPE.PLAYER_SESSION -> PlayerSessionViewHolder.gutsIds
+            TYPE.PLAYER -> MediaViewHolder.gutsIds
             TYPE.RECOMMENDATION -> RecommendationViewHolder.gutsIds
         }
         controlsIds.forEach { id ->
@@ -279,23 +275,22 @@
             viewState.widgetStates.get(id)?.alpha = if (isGutsVisible) 1f else 0f
             viewState.widgetStates.get(id)?.gone = !isGutsVisible
         }
-        if (shouldHideGutsSettings) {
-            viewState.widgetStates.get(R.id.settings)?.gone = true
-        }
     }
 
     /**
      * Apply squishFraction to a copy of viewState such that the cached version is untouched.
      */
-    private fun squishViewState(viewState: TransitionViewState,
-                                squishFraction: Float): TransitionViewState {
+    private fun squishViewState(
+        viewState: TransitionViewState,
+        squishFraction: Float
+    ): TransitionViewState {
         val squishedViewState = viewState.copy()
         squishedViewState.height = (squishedViewState.height * squishFraction).toInt()
         val albumArtViewState = viewState.widgetStates.get(R.id.album_art)
         if (albumArtViewState != null) {
             albumArtViewState.height = squishedViewState.height
         }
-        return squishedViewState;
+        return squishedViewState
     }
 
     /**
@@ -314,7 +309,7 @@
         if (viewState != null) {
             // we already have cached this measurement, let's continue
             if (state.squishFraction < 1f) {
-                return squishViewState(viewState, state.squishFraction);
+                return squishViewState(viewState, state.squishFraction)
             }
             return viewState
         }
@@ -351,7 +346,7 @@
                     state.expansion)
         }
         if (state.squishFraction < 1f) {
-            return squishViewState(result, state.squishFraction);
+            return squishViewState(result, state.squishFraction)
         }
         return result
     }
@@ -492,10 +487,6 @@
         // These XML resources contain ConstraintSets that will apply to this player type's layout
         when (type) {
             TYPE.PLAYER -> {
-                collapsedLayout.load(context, R.xml.media_collapsed)
-                expandedLayout.load(context, R.xml.media_expanded)
-            }
-            TYPE.PLAYER_SESSION -> {
                 collapsedLayout.load(context, R.xml.media_session_collapsed)
                 expandedLayout.load(context, R.xml.media_session_expanded)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
index 5f60696..836b5cb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.media
 
 import android.util.Log
+import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageButton
@@ -29,9 +30,9 @@
 private const val TAG = "MediaViewHolder"
 
 /**
- * Parent class for different media player views
+ * Holder class for media player view
  */
-abstract class MediaViewHolder constructor(itemView: View) {
+class MediaViewHolder constructor(itemView: View) {
     val player = itemView as TransitionLayout
 
     // Player information
@@ -57,10 +58,12 @@
     val cancelText = itemView.requireViewById<TextView>(R.id.cancel_text)
     val dismiss = itemView.requireViewById<ViewGroup>(R.id.dismiss)
     val dismissText = itemView.requireViewById<TextView>(R.id.dismiss_text)
-    val settings = itemView.requireViewById<View>(R.id.settings)
-    val settingsText = itemView.requireViewById<TextView>(R.id.settings_text)
+    val settings = itemView.requireViewById<ImageButton>(R.id.settings)
 
     // Action Buttons
+    val actionPlayPause = itemView.requireViewById<ImageButton>(R.id.actionPlayPause)
+    val actionNext = itemView.requireViewById<ImageButton>(R.id.actionNext)
+    val actionPrev = itemView.requireViewById<ImageButton>(R.id.actionPrev)
     val action0 = itemView.requireViewById<ImageButton>(R.id.action0)
     val action1 = itemView.requireViewById<ImageButton>(R.id.action1)
     val action2 = itemView.requireViewById<ImageButton>(R.id.action2)
@@ -73,6 +76,9 @@
             it.registerLightSource(cancel)
             it.registerLightSource(dismiss)
             it.registerLightSource(settings)
+            it.registerLightSource(actionPlayPause)
+            it.registerLightSource(actionNext)
+            it.registerLightSource(actionPrev)
             it.registerLightSource(action0)
             it.registerLightSource(action1)
             it.registerLightSource(action2)
@@ -81,7 +87,33 @@
         }
     }
 
-    abstract fun getAction(id: Int): ImageButton
+    fun getAction(id: Int): ImageButton {
+        return when (id) {
+            R.id.actionPlayPause -> actionPlayPause
+            R.id.actionNext -> actionNext
+            R.id.actionPrev -> actionPrev
+            R.id.action0 -> action0
+            R.id.action1 -> action1
+            R.id.action2 -> action2
+            R.id.action3 -> action3
+            R.id.action4 -> action4
+            else -> {
+                throw IllegalArgumentException()
+            }
+        }
+    }
+
+    fun getTransparentActionButtons(): List<ImageButton> {
+        return listOf(
+                actionNext,
+                actionPrev,
+                action0,
+                action1,
+                action2,
+                action3,
+                action4
+        )
+    }
 
     fun marquee(start: Boolean, delay: Long) {
         val longPressTextHandler = longPressText.getHandler()
@@ -91,4 +123,52 @@
         }
         longPressTextHandler.postDelayed({ longPressText.setSelected(start) }, delay)
     }
+
+    companion object {
+        /**
+         * Creates a MediaViewHolder.
+         *
+         * @param inflater LayoutInflater to use to inflate the layout.
+         * @param parent Parent of inflated view.
+         */
+        @JvmStatic fun create(
+            inflater: LayoutInflater,
+            parent: ViewGroup
+        ): MediaViewHolder {
+            val mediaView = inflater.inflate(R.layout.media_session_view, parent, false)
+            mediaView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+            // Because this media view (a TransitionLayout) is used to measure and layout the views
+            // in various states before being attached to its parent, we can't depend on the default
+            // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
+            mediaView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
+            return MediaViewHolder(mediaView).apply {
+                // Media playback is in the direction of tape, not time, so it stays LTR
+                seekBar.layoutDirection = View.LAYOUT_DIRECTION_LTR
+            }
+        }
+
+        val controlsIds = setOf(
+                R.id.icon,
+                R.id.app_name,
+                R.id.header_title,
+                R.id.header_artist,
+                R.id.media_seamless,
+                R.id.media_progress_bar,
+                R.id.actionPlayPause,
+                R.id.actionNext,
+                R.id.actionPrev,
+                R.id.action0,
+                R.id.action1,
+                R.id.action2,
+                R.id.action3,
+                R.id.action4,
+                R.id.icon
+        )
+        val gutsIds = setOf(
+                R.id.remove_text,
+                R.id.cancel,
+                R.id.dismiss,
+                R.id.settings
+        )
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerSessionViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerSessionViewHolder.kt
deleted file mode 100644
index 6928ebb..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerSessionViewHolder.kt
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media
-
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ImageButton
-import com.android.systemui.R
-
-/**
- * ViewHolder for a media player with MediaSession-based controls
- */
-class PlayerSessionViewHolder private constructor(itemView: View) : MediaViewHolder(itemView) {
-
-    // Action Buttons
-    val actionPlayPause = itemView.requireViewById<ImageButton>(R.id.actionPlayPause)
-    val actionNext = itemView.requireViewById<ImageButton>(R.id.actionNext)
-    val actionPrev = itemView.requireViewById<ImageButton>(R.id.actionPrev)
-
-    init {
-        (player.background as IlluminationDrawable).let {
-            it.registerLightSource(actionPlayPause)
-            it.registerLightSource(actionNext)
-            it.registerLightSource(actionPrev)
-        }
-    }
-
-    override fun getAction(id: Int): ImageButton {
-        return when (id) {
-            R.id.actionPlayPause -> actionPlayPause
-            R.id.actionNext -> actionNext
-            R.id.actionPrev -> actionPrev
-            R.id.action0 -> action0
-            R.id.action1 -> action1
-            R.id.action2 -> action2
-            R.id.action3 -> action3
-            R.id.action4 -> action4
-            else -> {
-                throw IllegalArgumentException()
-            }
-        }
-    }
-
-    companion object {
-        /**
-         * Creates a PlayerSessionViewHolder.
-         *
-         * @param inflater LayoutInflater to use to inflate the layout.
-         * @param parent Parent of inflated view.
-         */
-        @JvmStatic fun create(
-            inflater: LayoutInflater,
-            parent: ViewGroup
-        ): PlayerSessionViewHolder {
-            val mediaView = inflater.inflate(R.layout.media_session_view, parent, false)
-            mediaView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
-            // Because this media view (a TransitionLayout) is used to measure and layout the views
-            // in various states before being attached to its parent, we can't depend on the default
-            // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
-            mediaView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
-            return PlayerSessionViewHolder(mediaView).apply {
-                // Media playback is in the direction of tape, not time, so it stays LTR
-                seekBar.layoutDirection = View.LAYOUT_DIRECTION_LTR
-            }
-        }
-
-        val controlsIds = setOf(
-                R.id.icon,
-                R.id.app_name,
-                R.id.header_title,
-                R.id.header_artist,
-                R.id.media_seamless,
-                R.id.media_progress_bar,
-                R.id.actionPlayPause,
-                R.id.actionNext,
-                R.id.actionPrev,
-                R.id.action0,
-                R.id.action1,
-                R.id.action2,
-                R.id.action3,
-                R.id.action4,
-                R.id.icon
-        )
-        val gutsIds = setOf(
-                R.id.remove_text,
-                R.id.cancel,
-                R.id.dismiss,
-                R.id.settings
-        )
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
deleted file mode 100644
index dd3fa89..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ /dev/null
@@ -1,93 +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.systemui.media
-
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ImageButton
-import android.widget.TextView
-import com.android.systemui.R
-
-/**
- * ViewHolder for a media player.
- */
-class PlayerViewHolder private constructor(itemView: View) : MediaViewHolder(itemView) {
-
-    // Seek bar
-    val progressTimes = itemView.requireViewById<ViewGroup>(R.id.notification_media_progress_time)
-    override val elapsedTimeView = itemView.requireViewById<TextView>(R.id.media_elapsed_time)
-    override val totalTimeView = itemView.requireViewById<TextView>(R.id.media_total_time)
-
-    override fun getAction(id: Int): ImageButton {
-        return when (id) {
-            R.id.action0 -> action0
-            R.id.action1 -> action1
-            R.id.action2 -> action2
-            R.id.action3 -> action3
-            R.id.action4 -> action4
-            else -> {
-                throw IllegalArgumentException()
-            }
-        }
-    }
-
-    companion object {
-        /**
-         * Creates a PlayerViewHolder.
-         *
-         * @param inflater LayoutInflater to use to inflate the layout.
-         * @param parent Parent of inflated view.
-         */
-        @JvmStatic fun create(inflater: LayoutInflater, parent: ViewGroup): PlayerViewHolder {
-            val mediaView = inflater.inflate(R.layout.media_view, parent, false)
-            mediaView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
-            // Because this media view (a TransitionLayout) is used to measure and layout the views
-            // in various states before being attached to its parent, we can't depend on the default
-            // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
-            mediaView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
-            return PlayerViewHolder(mediaView).apply {
-                // Media playback is in the direction of tape, not time, so it stays LTR
-                seekBar.layoutDirection = View.LAYOUT_DIRECTION_LTR
-                progressTimes.layoutDirection = View.LAYOUT_DIRECTION_LTR
-            }
-        }
-
-        val controlsIds = setOf(
-                R.id.icon,
-                R.id.app_name,
-                R.id.album_art,
-                R.id.header_title,
-                R.id.header_artist,
-                R.id.media_seamless,
-                R.id.notification_media_progress_time,
-                R.id.media_progress_bar,
-                R.id.action0,
-                R.id.action1,
-                R.id.action2,
-                R.id.action3,
-                R.id.action4,
-                R.id.icon
-        )
-        val gutsIds = setOf(
-                R.id.remove_text,
-                R.id.cancel,
-                R.id.dismiss,
-                R.id.settings
-        )
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
index 57701ab..e5b41b1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
@@ -27,28 +27,17 @@
  * <p>Updates the seek bar views in response to changes to the model.
  */
 class SeekBarObserver(
-    private val holder: MediaViewHolder,
-    private val useSessionLayout: Boolean
+    private val holder: MediaViewHolder
 ) : Observer<SeekBarViewModel.Progress> {
 
     val seekBarEnabledMaxHeight = holder.seekBar.context.resources
         .getDimensionPixelSize(R.dimen.qs_media_enabled_seekbar_height)
     val seekBarDisabledHeight = holder.seekBar.context.resources
         .getDimensionPixelSize(R.dimen.qs_media_disabled_seekbar_height)
-    val seekBarEnabledVerticalPadding = if (useSessionLayout) {
-        holder.seekBar.context.resources
+    val seekBarEnabledVerticalPadding = holder.seekBar.context.resources
                 .getDimensionPixelSize(R.dimen.qs_media_session_enabled_seekbar_vertical_padding)
-    } else {
-        holder.seekBar.context.resources
-                .getDimensionPixelSize(R.dimen.qs_media_enabled_seekbar_vertical_padding)
-    }
-    val seekBarDisabledVerticalPadding = if (useSessionLayout) {
-        holder.seekBar.context.resources
+    val seekBarDisabledVerticalPadding = holder.seekBar.context.resources
                 .getDimensionPixelSize(R.dimen.qs_media_session_disabled_seekbar_vertical_padding)
-    } else {
-        holder.seekBar.context.resources
-                .getDimensionPixelSize(R.dimen.qs_media_disabled_seekbar_vertical_padding)
-    }
 
     init {
         val seekBarProgressWavelength = holder.seekBar.context.resources
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 ea7f7f2..1cc96c0f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -637,7 +637,8 @@
     }
 
     private boolean isPlayBackInfoLocal() {
-        return mMediaController.getPlaybackInfo() != null
+        return mMediaController != null
+                && mMediaController.getPlaybackInfo() != null
                 && mMediaController.getPlaybackInfo().getPlaybackType()
                 == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index a1a3198..df820a7 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -33,6 +33,7 @@
 import android.os.Looper;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.provider.Settings.Secure;
 import android.view.View;
 import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityManager;
@@ -72,6 +73,7 @@
  */
 @SysUISingleton
 public final class NavBarHelper implements
+        AccessibilityManager.AccessibilityServicesStateChangeListener,
         AccessibilityButtonModeObserver.ModeChangedListener,
         AccessibilityButtonTargetsObserver.TargetsChangedListener,
         OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
@@ -123,8 +125,7 @@
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
         mUserTracker = userTracker;
         mSystemActions = systemActions;
-        accessibilityManager.addAccessibilityServicesStateChangeListener(
-                accessibilityManager1 -> NavBarHelper.this.dispatchA11yEventUpdate());
+        accessibilityManager.addAccessibilityServicesStateChangeListener(this);
         mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
         mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver;
 
@@ -146,6 +147,7 @@
                 Settings.Secure.getUriFor(Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED),
                 false, mAssistContentObserver, UserHandle.USER_ALL);
         updateAssistantAvailability();
+        updateA11yState();
     }
 
     public void destroy() {
@@ -178,6 +180,12 @@
     }
 
     @Override
+    public void onAccessibilityServicesStateChanged(AccessibilityManager manager) {
+        dispatchA11yEventUpdate();
+        updateA11yState();
+    }
+
+    @Override
     public void onAccessibilityButtonModeChanged(int mode) {
         updateA11yState();
         dispatchA11yEventUpdate();
@@ -190,7 +198,9 @@
     }
 
     /**
-     * Updates the current accessibility button state.
+     * Updates the current accessibility button state. The accessibility button state is only
+     * used for {@link Secure#ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR} and
+     * {@link Secure#ACCESSIBILITY_BUTTON_MODE_GESTURE}, otherwise it is reset to 0.
      */
     private void updateA11yState() {
         final int prevState = mA11yButtonState;
@@ -213,6 +223,9 @@
             final int requestingServices = a11yButtonTargets.size();
 
             clickable = requestingServices >= 1;
+
+            // `longClickable` is used to determine whether to pop up the accessibility chooser
+            // dialog or not, and it’s also only for multiple services.
             longClickable = requestingServices >= 2;
             mA11yButtonState = (clickable ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0)
                     | (longClickable ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0);
@@ -237,11 +250,13 @@
     }
 
     /**
-     * See {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_CLICKABLE} and
-     * {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE}
+     * Gets the accessibility button state based on the {@link Secure#ACCESSIBILITY_BUTTON_MODE}.
      *
-     * @return the a11y button clickable and long_clickable states, or 0 if there is no
-     *         a11y button in the navbar
+     * @return the accessibility button state:
+     * 0 = disable state
+     * 16 = {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_CLICKABLE}
+     * 48 = the combination of {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_CLICKABLE} and
+     * {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE}
      */
     public int getA11yButtonState() {
         return mA11yButtonState;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index d8d8677..fab19d6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -86,7 +86,6 @@
 import android.view.InsetsState.InternalInsetsType;
 import android.view.InsetsVisibilities;
 import android.view.KeyEvent;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.View;
@@ -98,6 +97,7 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.inputmethod.InputMethodManager;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
@@ -111,8 +111,10 @@
 import com.android.systemui.R;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
 import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener;
 import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
 import com.android.systemui.navigationbar.buttons.DeadZone;
@@ -140,7 +142,10 @@
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.ViewController;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.pip.Pip;
 
@@ -149,15 +154,15 @@
 import java.util.Optional;
 import java.util.function.Consumer;
 
+import javax.inject.Inject;
+
 import dagger.Lazy;
-import dagger.assisted.Assisted;
-import dagger.assisted.AssistedFactory;
-import dagger.assisted.AssistedInject;
 
 /**
  * Contains logic for a navigation bar view.
  */
-public class NavigationBar implements View.OnAttachStateChangeListener, Callbacks {
+@NavigationBarScope
+public class NavigationBar extends ViewController<NavigationBarView> implements Callbacks {
 
     public static final String TAG = "NavigationBar";
     private static final boolean DEBUG = false;
@@ -172,12 +177,14 @@
     private static final long AUTODIM_TIMEOUT_MS = 2250;
 
     private final Context mContext;
+    private final Bundle mSavedState;
     private final WindowManager mWindowManager;
     private final AccessibilityManager mAccessibilityManager;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final StatusBarStateController mStatusBarStateController;
     private final MetricsLogger mMetricsLogger;
     private final Lazy<AssistManager> mAssistManagerLazy;
+    private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final SysUiState mSysUiFlagsContainer;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
     private final ShadeController mShadeController;
@@ -188,15 +195,13 @@
     private final CommandQueue mCommandQueue;
     private final Optional<Pip> mPipOptional;
     private final Optional<Recents> mRecentsOptional;
+    private final DeviceConfigProxy mDeviceConfigProxy;
     private final Optional<BackAnimation> mBackAnimation;
     private final Handler mHandler;
     private final NavigationBarOverlayController mNavbarOverlayController;
     private final UiEventLogger mUiEventLogger;
     private final NavBarHelper mNavBarHelper;
     private final NotificationShadeDepthController mNotificationShadeDepthController;
-
-    private Bundle mSavedState;
-    private NavigationBarView mNavigationBarView;
     private NavigationBarFrame mFrame;
 
     private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
@@ -256,7 +261,7 @@
     private int mCurrentRotation;
     private ViewTreeObserver.OnGlobalLayoutListener mOrientationHandleGlobalLayoutListener;
     private boolean mShowOrientedHandleForImmersiveMode;
-    private DeadZone mDeadZone;
+    private final DeadZone mDeadZone;
     private boolean mImeVisible;
 
     @com.android.internal.annotations.VisibleForTesting
@@ -311,7 +316,7 @@
                     // TODO(b/198002034): Content observers currently can still be called back after
                     //  being unregistered, and in this case we can ignore the change if the nav bar
                     //  has been destroyed already
-                    if (mNavigationBarView == null) {
+                    if (mView == null) {
                         return;
                     }
                     mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled();
@@ -322,14 +327,14 @@
     private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() {
         @Override
         public void onConnectionChanged(boolean isConnected) {
-            mNavigationBarView.updateStates();
+            mView.updateStates();
             updateScreenPinningGestures();
         }
 
         @Override
         public void onQuickStepStarted() {
             // Use navbar dragging as a signal to hide the rotate button
-            mNavigationBarView.getRotationButtonController().setRotateSuggestionButtonState(false);
+            mView.getRotationButtonController().setRotateSuggestionButtonState(false);
 
             // Hide the notifications panel when quick step starts
             mShadeController.collapsePanel(true /* animate */);
@@ -366,12 +371,12 @@
                 // Disallow home handle animations when in gestural
                 animate = false;
                 forceVisible = mAllowForceNavBarHandleOpaque && mForceNavBarHandleOpaque;
-                buttonDispatcher = mNavigationBarView.getHomeHandle();
+                buttonDispatcher = mView.getHomeHandle();
                 if (getBarTransitions() != null) {
                     getBarTransitions().setBackgroundOverrideAlpha(alpha);
                 }
             } else if (QuickStepContract.isSwipeUpMode(mNavBarMode)) {
-                buttonDispatcher = mNavigationBarView.getBackButton();
+                buttonDispatcher = mView.getBackButton();
             }
             if (buttonDispatcher != null) {
                 buttonDispatcher.setVisibility(
@@ -382,7 +387,7 @@
 
         @Override
         public void onHomeRotationEnabled(boolean enabled) {
-            mNavigationBarView.getRotationButtonController().setHomeRotationEnabled(enabled);
+            mView.getRotationButtonController().setHomeRotationEnabled(enabled);
         }
 
         @Override
@@ -390,20 +395,18 @@
             // If the overview has fixed orientation that may change display to natural rotation,
             // we don't want the user rotation to be reset. So after user returns to application,
             // it can keep in the original rotation.
-            mNavigationBarView.getRotationButtonController().setSkipOverrideUserLockPrefsOnce();
+            mView.getRotationButtonController().setSkipOverrideUserLockPrefsOnce();
         }
 
         @Override
         public void onTaskbarStatusUpdated(boolean visible, boolean stashed) {
-            mNavigationBarView
-                    .getFloatingRotationButton()
-                    .onTaskbarStateChanged(visible, stashed);
+            mView.getFloatingRotationButton().onTaskbarStateChanged(visible, stashed);
         }
 
         @Override
         public void onToggleRecentApps() {
             // The same case as onOverviewShown but only for 3-button navigation.
-            mNavigationBarView.getRotationButtonController().setSkipOverrideUserLockPrefsOnce();
+            mView.getRotationButtonController().setSkipOverrideUserLockPrefsOnce();
         }
     };
 
@@ -416,11 +419,10 @@
             };
 
     private final Runnable mAutoDim = () -> getBarTransitions().setAutoDim(true);
-    private final Runnable mEnableLayoutTransitions = () ->
-            mNavigationBarView.setLayoutTransitionsEnabled(true);
+    private final Runnable mEnableLayoutTransitions = () -> mView.setLayoutTransitionsEnabled(true);
     private final Runnable mOnVariableDurationHomeLongClick = () -> {
-        if (onHomeLongClick(mNavigationBarView.getHomeButton().getCurrentView())) {
-            mNavigationBarView.getHomeButton().getCurrentView().performHapticFeedback(
+        if (onHomeLongClick(mView.getHomeButton().getCurrentView())) {
+            mView.getHomeButton().getCurrentView().performHapticFeedback(
                     HapticFeedbackConstants.LONG_PRESS,
                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
         }
@@ -439,7 +441,7 @@
                         mHomeButtonLongPressDurationMs = Optional.of(
                             properties.getLong(HOME_BUTTON_LONG_PRESS_DURATION_MS, 0)
                         ).filter(duration -> duration != 0);
-                        if (mNavigationBarView != null) {
+                        if (mView != null) {
                             reconfigureHomeLongClick();
                         }
                     }
@@ -469,14 +471,17 @@
                         return;
                     }
                     mHasBlurs = hasBlurs;
-                    mNavigationBarView.setWindowHasBlurs(hasBlurs);
+                    mView.setWindowHasBlurs(hasBlurs);
                 }
             };
 
-    @AssistedInject
+    @Inject
     NavigationBar(
-            @Assisted Context context,
-            @Assisted WindowManager windowManager,
+            NavigationBarView navigationBarView,
+            NavigationBarFrame navigationBarFrame,
+            @Nullable Bundle savedState,
+            @DisplayId Context context,
+            @DisplayId WindowManager windowManager,
             Lazy<AssistManager> assistManagerLazy,
             AccessibilityManager accessibilityManager,
             DeviceProvisionedController deviceProvisionedController,
@@ -484,6 +489,7 @@
             OverviewProxyService overviewProxyService,
             NavigationModeController navigationModeController,
             StatusBarStateController statusBarStateController,
+            StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             SysUiState sysUiFlagsContainer,
             BroadcastDispatcher broadcastDispatcher,
             CommandQueue commandQueue,
@@ -503,14 +509,20 @@
             AutoHideController.Factory autoHideControllerFactory,
             Optional<TelecomManager> telecomManagerOptional,
             InputMethodManager inputMethodManager,
+            DeadZone deadZone,
+            DeviceConfigProxy deviceConfigProxy,
             Optional<BackAnimation> backAnimation) {
+        super(navigationBarView);
+        mFrame = navigationBarFrame;
         mContext = context;
+        mSavedState = savedState;
         mWindowManager = windowManager;
         mAccessibilityManager = accessibilityManager;
         mDeviceProvisionedController = deviceProvisionedController;
         mStatusBarStateController = statusBarStateController;
         mMetricsLogger = metricsLogger;
         mAssistManagerLazy = assistManagerLazy;
+        mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mSysUiFlagsContainer = sysUiFlagsContainer;
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
         mShadeController = shadeController;
@@ -521,6 +533,8 @@
         mCommandQueue = commandQueue;
         mPipOptional = pipOptional;
         mRecentsOptional = recentsOptional;
+        mDeadZone = deadZone;
+        mDeviceConfigProxy = deviceConfigProxy;
         mBackAnimation = backAnimation;
         mHandler = mainHandler;
         mNavbarOverlayController = navbarOverlayController;
@@ -538,25 +552,23 @@
     }
 
     public NavigationBarView getView() {
-        return mNavigationBarView;
+        return mView;
     }
 
-    public View createView(Bundle savedState, boolean initialVisibility) {
-        mFrame = (NavigationBarFrame) LayoutInflater.from(mContext).inflate(
-                R.layout.navigation_bar_window, null);
-        View barView = LayoutInflater.from(mFrame.getContext()).inflate(
-                R.layout.navigation_bar, mFrame);
-        barView.addOnAttachStateChangeListener(this);
-        mNavigationBarView = barView.findViewById(R.id.navigation_bar_view);
-        mDeadZone = new DeadZone(mNavigationBarView);
-        mNavigationBarView.setTouchHandler(mTouchHandler);
-        mNavigationBarView.setNavBarMode(mNavBarMode);
+    @Override
+    public void onInit() {
+        // TODO: A great deal of this code should probalby live in onViewAttached.
+        // It should also has corresponding cleanup in onViewDetached.
+        mView.setTouchHandler(mTouchHandler);
+        mView.setNavBarMode(mNavBarMode);
 
-        mNavigationBarView.updateRotationButton();
+        mView.updateRotationButton();
 
-        mNavigationBarView.setVisibility(initialVisibility ? View.VISIBLE : View.INVISIBLE);
+        mView.setVisibility(
+                mStatusBarKeyguardViewManager.isNavBarVisible() ? View.VISIBLE : View.INVISIBLE);
 
-        if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + barView);
+        if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + mView);
+
         mWindowManager.addView(mFrame,
                 getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration
                         .getRotation()));
@@ -568,26 +580,25 @@
         mNavBarHelper.init();
         mAllowForceNavBarHandleOpaque = mContext.getResources().getBoolean(
                 R.bool.allow_force_nav_bar_handle_opaque);
-        mForceNavBarHandleOpaque = DeviceConfig.getBoolean(
+        mForceNavBarHandleOpaque = mDeviceConfigProxy.getBoolean(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 NAV_BAR_HANDLE_FORCE_OPAQUE,
                 /* defaultValue = */ true);
-        mHomeButtonLongPressDurationMs = Optional.of(DeviceConfig.getLong(
+        mHomeButtonLongPressDurationMs = Optional.of(mDeviceConfigProxy.getLong(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 HOME_BUTTON_LONG_PRESS_DURATION_MS,
                 /* defaultValue = */ 0
         )).filter(duration -> duration != 0);
-        DeviceConfig.addOnPropertiesChangedListener(
+        mDeviceConfigProxy.addOnPropertiesChangedListener(
                 DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener);
 
-        if (savedState != null) {
-            mDisabledFlags1 = savedState.getInt(EXTRA_DISABLE_STATE, 0);
-            mDisabledFlags2 = savedState.getInt(EXTRA_DISABLE2_STATE, 0);
-            mAppearance = savedState.getInt(EXTRA_APPEARANCE, 0);
-            mBehavior = savedState.getInt(EXTRA_BEHAVIOR, 0);
-            mTransientShown = savedState.getBoolean(EXTRA_TRANSIENT_STATE, false);
+        if (mSavedState != null) {
+            mDisabledFlags1 = mSavedState.getInt(EXTRA_DISABLE_STATE, 0);
+            mDisabledFlags2 = mSavedState.getInt(EXTRA_DISABLE2_STATE, 0);
+            mAppearance = mSavedState.getInt(EXTRA_APPEARANCE, 0);
+            mBehavior = mSavedState.getInt(EXTRA_BEHAVIOR, 0);
+            mTransientShown = mSavedState.getBoolean(EXTRA_TRANSIENT_STATE, false);
         }
-        mSavedState = savedState;
 
         // Respect the latest disabled-flags.
         mCommandQueue.recomputeDisableFlags(mDisplayId, false);
@@ -595,14 +606,12 @@
         mIsCurrentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
         mDeviceProvisionedController.addCallback(mUserSetupListener);
         mNotificationShadeDepthController.addListener(mDepthListener);
-
-        return barView;
     }
 
     public void destroyView() {
         setAutoHideController(/* autoHideController */ null);
         mCommandQueue.removeCallback(this);
-        mWindowManager.removeViewImmediate(mNavigationBarView.getRootView());
+        mWindowManager.removeViewImmediate(mView.getRootView());
         mNavigationModeController.removeListener(mModeChangedListener);
 
         mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
@@ -610,29 +619,29 @@
         mDeviceProvisionedController.removeCallback(mUserSetupListener);
         mNotificationShadeDepthController.removeListener(mDepthListener);
 
-        DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
+        mDeviceConfigProxy.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
     }
 
     @Override
-    public void onViewAttachedToWindow(View v) {
-        final Display display = v.getDisplay();
-        mNavigationBarView.setComponents(mRecentsOptional);
-        mNavigationBarView.setComponents(mCentralSurfacesOptionalLazy.get().get().getPanelController());
-        mNavigationBarView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer);
-        mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
-        mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
+    public void onViewAttached() {
+        final Display display = mView.getDisplay();
+        mView.setComponents(mRecentsOptional);
+        mView.setComponents(mCentralSurfacesOptionalLazy.get().get().getPanelController());
+        mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer);
+        mView.setOnVerticalChangedListener(this::onVerticalChanged);
+        mView.setOnTouchListener(this::onNavigationTouch);
         if (mSavedState != null) {
-            mNavigationBarView.getLightTransitionsController().restoreState(mSavedState);
+            mView.getLightTransitionsController().restoreState(mSavedState);
         }
         setNavigationIconHints(mNavigationIconHints);
-        mNavigationBarView.setWindowVisible(isNavBarWindowVisible());
-        mNavigationBarView.setBehavior(mBehavior);
-        mNavigationBarView.setNavBarMode(mNavBarMode);
+        mView.setWindowVisible(isNavBarWindowVisible());
+        mView.setBehavior(mBehavior);
+        mView.setNavBarMode(mNavBarMode);
 
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
-        mPipOptional.ifPresent(mNavigationBarView::addPipExclusionBoundsChangeListener);
-        mBackAnimation.ifPresent(mNavigationBarView::registerBackAnimation);
+        mPipOptional.ifPresent(mView::addPipExclusionBoundsChangeListener);
+        mBackAnimation.ifPresent(mView::registerBackAnimation);
 
         prepareNavigationBarView();
         checkNavBarModes();
@@ -650,7 +659,7 @@
         // Currently there is no accelerometer sensor on non-default display.
         if (mIsOnDefaultDisplay) {
             final RotationButtonController rotationButtonController =
-                    mNavigationBarView.getRotationButtonController();
+                    mView.getRotationButtonController();
             rotationButtonController.setRotationCallback(mRotationWatcher);
 
             // Reset user rotation pref to match that of the WindowManager if starting in locked
@@ -684,12 +693,12 @@
     }
 
     @Override
-    public void onViewDetachedFromWindow(View v) {
+    public void onViewDetached() {
         final RotationButtonController rotationButtonController =
-                mNavigationBarView.getRotationButtonController();
+                mView.getRotationButtonController();
         rotationButtonController.setRotationCallback(null);
-        mNavigationBarView.getBarTransitions().destroy();
-        mNavigationBarView.getLightTransitionsController().destroy(mContext);
+        mView.getBarTransitions().destroy();
+        mView.getLightTransitionsController().destroy(mContext);
         mOverviewProxyService.removeCallback(mOverviewProxyListener);
         mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
         if (mOrientationHandle != null) {
@@ -703,9 +712,8 @@
         mHandler.removeCallbacks(mOnVariableDurationHomeLongClick);
         mHandler.removeCallbacks(mEnableLayoutTransitions);
         mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
-        mPipOptional.ifPresent(mNavigationBarView::removePipExclusionBoundsChangeListener);
+        mPipOptional.ifPresent(mView::removePipExclusionBoundsChangeListener);
         mFrame = null;
-        mNavigationBarView = null;
         mOrientationHandle = null;
     }
 
@@ -716,7 +724,7 @@
         outState.putInt(EXTRA_APPEARANCE, mAppearance);
         outState.putInt(EXTRA_BEHAVIOR, mBehavior);
         outState.putBoolean(EXTRA_TRANSIENT_STATE, mTransientShown);
-        mNavigationBarView.getLightTransitionsController().saveState(outState);
+        mView.getLightTransitionsController().saveState(outState);
     }
 
     /**
@@ -779,7 +787,7 @@
                     mOrientationHandle.mapRectFromViewToScreenCoords(boundsOnScreen, true);
                     Rect boundsRounded = new Rect();
                     boundsOnScreen.roundOut(boundsRounded);
-                    mNavigationBarView.setOrientedHandleSamplingRegion(boundsRounded);
+                    mView.setOrientedHandleSamplingRegion(boundsRounded);
                 };
         mOrientationHandle.getViewTreeObserver().addOnGlobalLayoutListener(
                 mOrientationHandleGlobalLayoutListener);
@@ -808,7 +816,7 @@
                 case Surface.ROTATION_90:
                 case Surface.ROTATION_270:
                     height = dispSize.height();
-                    width = mNavigationBarView.getHeight();
+                    width = mView.getHeight();
                     break;
                 case Surface.ROTATION_180:
                 case Surface.ROTATION_0:
@@ -818,7 +826,7 @@
                         return;
                     }
                     width = dispSize.width();
-                    height = mNavigationBarView.getHeight();
+                    height = mView.getHeight();
                     break;
             }
 
@@ -828,7 +836,7 @@
             mOrientationParams.height = height;
             mOrientationParams.width = width;
             mWindowManager.updateViewLayout(mOrientationHandle, mOrientationParams);
-            mNavigationBarView.setVisibility(View.GONE);
+            mView.setVisibility(View.GONE);
             mOrientationHandle.setVisibility(View.VISIBLE);
         }
     }
@@ -839,22 +847,22 @@
             // mOrientedHandle is initialized lazily
             mOrientationHandle.setVisibility(View.GONE);
         }
-        mNavigationBarView.setVisibility(View.VISIBLE);
-        mNavigationBarView.setOrientedHandleSamplingRegion(null);
+        mView.setVisibility(View.VISIBLE);
+        mView.setOrientedHandleSamplingRegion(null);
     }
 
     private void reconfigureHomeLongClick() {
-        if (mNavigationBarView.getHomeButton().getCurrentView() == null) {
+        if (mView.getHomeButton().getCurrentView() == null) {
             return;
         }
         if (mHomeButtonLongPressDurationMs.isPresent() || !mLongPressHomeEnabled) {
-            mNavigationBarView.getHomeButton().getCurrentView().setLongClickable(false);
-            mNavigationBarView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(false);
-            mNavigationBarView.getHomeButton().setOnLongClickListener(null);
+            mView.getHomeButton().getCurrentView().setLongClickable(false);
+            mView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(false);
+            mView.getHomeButton().setOnLongClickListener(null);
         } else {
-            mNavigationBarView.getHomeButton().getCurrentView().setLongClickable(true);
-            mNavigationBarView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(true);
-            mNavigationBarView.getHomeButton().setOnLongClickListener(this::onHomeLongClick);
+            mView.getHomeButton().getCurrentView().setLongClickable(true);
+            mView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(true);
+            mView.getHomeButton().setOnLongClickListener(this::onHomeLongClick);
         }
     }
 
@@ -877,8 +885,8 @@
         pw.println("  mTransientShown=" + mTransientShown);
         pw.println("  mTransientShownFromGestureOnSystemBar="
                 + mTransientShownFromGestureOnSystemBar);
-        dumpBarTransitions(pw, "mNavigationBarView", mNavigationBarView.getBarTransitions());
-        mNavigationBarView.dump(pw);
+        dumpBarTransitions(pw, "mNavigationBarView", mView.getBarTransitions());
+        mView.dump(pw);
     }
 
     // ----- CommandQueue Callbacks -----
@@ -914,7 +922,7 @@
                 orientSecondaryHomeHandle();
             }
             if (DEBUG_WINDOW_STATE) Log.d(TAG, "Navigation bar " + windowStateToString(state));
-            mNavigationBarView.setWindowVisible(isNavBarWindowVisible());
+            mView.setWindowVisible(isNavBarWindowVisible());
         }
     }
 
@@ -923,12 +931,12 @@
         // The CommandQueue callbacks are added when the view is created to ensure we track other
         // states, but until the view is attached (at the next traversal), the view's display is
         // not valid.  Just ignore the rotation in this case.
-        if (!mNavigationBarView.isAttachedToWindow()) return;
+        if (!mView.isAttachedToWindow()) return;
 
         final boolean rotateSuggestionsDisabled = RotationButtonController
                 .hasDisable2RotateSuggestionFlag(mDisabledFlags2);
         final RotationButtonController rotationButtonController =
-                mNavigationBarView.getRotationButtonController();
+                mView.getRotationButtonController();
         final RotationButton rotationButton = rotationButtonController.getRotationButton();
 
         if (RotationContextButton.DEBUG_ROTATION) {
@@ -950,7 +958,7 @@
         if (running) {
             mNavbarOverlayController.setButtonState(/* visible */false, /* force */true);
         }
-        mNavigationBarView.getRotationButtonController().setRecentsAnimationRunning(running);
+        mView.getRotationButtonController().setRecentsAnimationRunning(running);
     }
 
     /** Restores the appearance and the transient saved state to {@link NavigationBar}. */
@@ -985,7 +993,7 @@
         }
         if (mBehavior != behavior) {
             mBehavior = behavior;
-            mNavigationBarView.setBehavior(behavior);
+            mView.setBehavior(behavior);
             updateSystemUiStateFlags();
         }
     }
@@ -1026,7 +1034,7 @@
     }
 
     private void handleTransientChanged() {
-        mNavigationBarView.onTransientStateChanged(mTransientShown,
+        mView.onTransientStateChanged(mTransientShown,
                 mTransientShownFromGestureOnSystemBar);
         final int transitionMode = transitionMode(mTransientShown, mAppearance);
         if (updateTransitionMode(transitionMode) && mLightBarController != null) {
@@ -1076,7 +1084,7 @@
                 | StatusBarManager.DISABLE_SEARCH);
         if (masked != mDisabledFlags1) {
             mDisabledFlags1 = masked;
-            mNavigationBarView.setDisabledFlags(state1, mSysUiFlagsContainer);
+            mView.setDisabledFlags(state1, mSysUiFlagsContainer);
             updateScreenPinningGestures();
         }
 
@@ -1092,13 +1100,13 @@
 
     private void setDisabled2Flags(int state2) {
         // Method only called on change of disable2 flags
-        mNavigationBarView.getRotationButtonController().onDisable2FlagChanged(state2);
+        mView.getRotationButtonController().onDisable2FlagChanged(state2);
     }
 
     // ----- Internal stuff -----
 
     private void refreshLayout(int layoutDirection) {
-        mNavigationBarView.setLayoutDirection(layoutDirection);
+        mView.setLayoutDirection(layoutDirection);
     }
 
     private boolean shouldDisableNavbarGestures() {
@@ -1107,7 +1115,7 @@
     }
 
     private void repositionNavigationBar(int rotation) {
-        if (mNavigationBarView == null || !mNavigationBarView.isAttachedToWindow()) return;
+        if (mView == null || !mView.isAttachedToWindow()) return;
 
         prepareNavigationBarView();
 
@@ -1117,10 +1125,10 @@
     private void updateScreenPinningGestures() {
         // Change the cancel pin gesture to home and back if recents button is invisible
         boolean pinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive();
-        ButtonDispatcher backButton = mNavigationBarView.getBackButton();
-        ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
+        ButtonDispatcher backButton = mView.getBackButton();
+        ButtonDispatcher recentsButton = mView.getRecentsButton();
         if (pinningActive) {
-            boolean recentsVisible = mNavigationBarView.isRecentsButtonVisible();
+            boolean recentsVisible = mView.isRecentsButtonVisible();
             backButton.setOnLongClickListener(recentsVisible
                     ? this::onLongPressBackRecents
                     : this::onLongPressBackHome);
@@ -1135,27 +1143,27 @@
     }
 
     private void notifyNavigationBarScreenOn() {
-        mNavigationBarView.updateNavButtonIcons();
+        mView.updateNavButtonIcons();
     }
 
     private void prepareNavigationBarView() {
-        mNavigationBarView.reorient();
+        mView.reorient();
 
-        ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
+        ButtonDispatcher recentsButton = mView.getRecentsButton();
         recentsButton.setOnClickListener(this::onRecentsClick);
         recentsButton.setOnTouchListener(this::onRecentsTouch);
 
-        ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
+        ButtonDispatcher homeButton = mView.getHomeButton();
         homeButton.setOnTouchListener(this::onHomeTouch);
 
         reconfigureHomeLongClick();
 
-        ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
+        ButtonDispatcher accessibilityButton = mView.getAccessibilityButton();
         accessibilityButton.setOnClickListener(this::onAccessibilityClick);
         accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
         updateAccessibilityStateFlags();
 
-        ButtonDispatcher imeSwitcherButton = mNavigationBarView.getImeSwitchButton();
+        ButtonDispatcher imeSwitcherButton = mView.getImeSwitchButton();
         imeSwitcherButton.setOnClickListener(this::onImeSwitcherClick);
 
         updateScreenPinningGestures();
@@ -1212,7 +1220,7 @@
 
     @VisibleForTesting
     boolean onHomeLongClick(View v) {
-        if (!mNavigationBarView.isRecentsButtonVisible()
+        if (!mView.isRecentsButtonVisible()
                 && ActivityManagerWrapper.getInstance().isScreenPinningActive()) {
             return onLongPressBackHome(v);
         }
@@ -1227,7 +1235,7 @@
                 AssistManager.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
         mAssistManagerLazy.get().startAssist(args);
         mCentralSurfacesOptionalLazy.get().ifPresent(CentralSurfaces::awakenDreams);
-        mNavigationBarView.abortCurrentGesture();
+        mView.abortCurrentGesture();
         return true;
     }
 
@@ -1302,8 +1310,7 @@
                         return true;
                     } else if (v.getId() == btnId1) {
                         ButtonDispatcher button = btnId2 == R.id.recent_apps
-                                ? mNavigationBarView.getRecentsButton()
-                                : mNavigationBarView.getHomeButton();
+                                ? mView.getRecentsButton() : mView.getHomeButton();
                         if (!button.getCurrentView().isPressed()) {
                             // If we aren't pressing recents/home right now then they presses
                             // won't be together, so send the standard long-press action.
@@ -1323,15 +1330,14 @@
                     } else if (v.getId() == btnId2) {
                         return btnId2 == R.id.recent_apps
                                 ? false
-                                : onHomeLongClick(
-                                        mNavigationBarView.getHomeButton().getCurrentView());
+                                : onHomeLongClick(mView.getHomeButton().getCurrentView());
                     }
                 }
             } finally {
                 if (stopLockTaskMode) {
                     activityManager.stopSystemLockTaskMode();
                     // When exiting refresh disabled flags.
-                    mNavigationBarView.updateNavButtonIcons();
+                    mView.updateNavButtonIcons();
                 }
             }
 
@@ -1363,11 +1369,11 @@
     }
 
     void updateAccessibilityStateFlags() {
-        if (mNavigationBarView != null) {
+        if (mView != null) {
             int a11yFlags = mNavBarHelper.getA11yButtonState();
             boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
             boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
-            mNavigationBarView.setAccessibilityButtonState(clickable, longClickable);
+            mView.setAccessibilityButtonState(clickable, longClickable);
         }
         updateSystemUiStateFlags();
     }
@@ -1416,7 +1422,7 @@
         mLightBarController = lightBarController;
         if (mLightBarController != null) {
             mLightBarController.setNavigationBar(
-                    mNavigationBarView.getLightTransitionsController());
+                    mView.getLightTransitionsController());
         }
     }
 
@@ -1426,7 +1432,7 @@
         if (mAutoHideController != null) {
             mAutoHideController.setNavigationBar(mAutoHideUiElement);
         }
-        mNavigationBarView.setAutoHideController(autoHideController);
+        mView.setAutoHideController(autoHideController);
     }
 
     private boolean isTransientShown() {
@@ -1458,11 +1464,11 @@
                 mCentralSurfacesOptionalLazy.get().map(CentralSurfaces::isDeviceInteractive)
                         .orElse(false)
                 && mNavigationBarWindowState != WINDOW_STATE_HIDDEN;
-        mNavigationBarView.getBarTransitions().transitionTo(mTransitionMode, anim);
+        mView.getBarTransitions().transitionTo(mTransitionMode, anim);
     }
 
     public void disableAnimationsDuringHide(long delay) {
-        mNavigationBarView.setLayoutTransitionsEnabled(false);
+        mView.setLayoutTransitionsEnabled(false);
         mHandler.postDelayed(mEnableLayoutTransitions,
                 delay + StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
     }
@@ -1478,11 +1484,11 @@
     }
 
     public NavigationBarTransitions getBarTransitions() {
-        return mNavigationBarView.getBarTransitions();
+        return mView.getBarTransitions();
     }
 
     public void finishBarAnimations() {
-        mNavigationBarView.getBarTransitions().finishAnimations();
+        mView.getBarTransitions().finishAnimations();
     }
 
     private WindowManager.LayoutParams getBarLayoutParams(int rotation) {
@@ -1567,8 +1573,7 @@
     }
 
     private final Consumer<Integer> mRotationWatcher = rotation -> {
-        if (mNavigationBarView != null
-                && mNavigationBarView.needsReorient(rotation)) {
+        if (mView != null && mView.needsReorient(rotation)) {
             repositionNavigationBar(rotation);
         }
     };
@@ -1578,14 +1583,14 @@
         public void onReceive(Context context, Intent intent) {
             // TODO(193941146): Currently unregistering a receiver through BroadcastDispatcher is
             // async, but we've already cleared the fields. Just return early in this case.
-            if (mNavigationBarView == null) {
+            if (mView == null) {
                 return;
             }
             String action = intent.getAction();
             if (Intent.ACTION_SCREEN_OFF.equals(action)
                     || Intent.ACTION_SCREEN_ON.equals(action)) {
                 notifyNavigationBarScreenOn();
-                mNavigationBarView.onScreenStateChanged(Intent.ACTION_SCREEN_ON.equals(action));
+                mView.onScreenStateChanged(Intent.ACTION_SCREEN_ON.equals(action));
             }
             if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                 // The accessibility settings may be different for the new user
@@ -1607,11 +1612,11 @@
             final boolean oldBackAlt =
                     (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
             if (newBackAlt != oldBackAlt) {
-                mNavigationBarView.onImeVisibilityChanged(newBackAlt);
+                mView.onImeVisibilityChanged(newBackAlt);
                 mImeVisible = newBackAlt;
             }
 
-            mNavigationBarView.setNavigationIconHints(hints);
+            mView.setNavigationIconHints(hints);
         }
         if (DEBUG) {
             android.widget.Toast.makeText(mContext,
@@ -1637,8 +1642,8 @@
             if (!canShowSecondaryHandle()) {
                 resetSecondaryHandle();
             }
-            if (mNavigationBarView != null) {
-                mNavigationBarView.setNavBarMode(mode);
+            if (mView != null) {
+                mView.setNavBarMode(mode);
             }
         }
     };
@@ -1671,14 +1676,14 @@
                 switch (action) {
                     case MotionEvent.ACTION_DOWN:
                         // Allow gestures starting in the deadzone to be slippery
-                        mNavigationBarView.setSlippery(true);
+                        mView.setSlippery(true);
                         mDeadZoneConsuming = true;
                         break;
                     case MotionEvent.ACTION_CANCEL:
                     case MotionEvent.ACTION_UP:
                         // When a gesture started in the deadzone is finished, restore
                         // slippery state
-                        mNavigationBarView.updateSlippery();
+                        mView.updateSlippery();
                         mDeadZoneConsuming = false;
                         break;
                 }
@@ -1687,14 +1692,4 @@
             return false;
         }
     };
-
-
-    /**
-     * Injectable factory for construction a {@link NavigationBar}.
-     */
-    @AssistedFactory
-    public interface Factory {
-        /** Construct a {@link NavigationBar} */
-        NavigationBar create(Context context, WindowManager windowManager);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarComponent.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarComponent.java
new file mode 100644
index 0000000..1d792af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarComponent.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.navigationbar;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.dagger.qualifiers.DisplayId;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Subcomponent for a NavigationBar.
+ *
+ * Generally creatd on a per-display basis.
+ */
+@Subcomponent(modules = { NavigationBarModule.class })
+@NavigationBarComponent.NavigationBarScope
+public interface NavigationBarComponent {
+
+    /** Factory for {@link NavigationBarComponent}. */
+    @Subcomponent.Factory
+    interface Factory {
+        NavigationBarComponent create(
+                @BindsInstance @DisplayId Context context,
+                @BindsInstance @Nullable Bundle savedState);
+    }
+
+    /** */
+    NavigationBar getNavigationBar();
+
+    /**
+     * Scope annotation for singleton items within the NavigationBarComponent.
+     */
+    @Documented
+    @Retention(RUNTIME)
+    @Scope
+    @interface NavigationBarScope {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index b640e1d..ade86ae 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -39,7 +39,6 @@
 import android.view.Display;
 import android.view.IWindowManager;
 import android.view.View;
-import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 
 import androidx.annotation.NonNull;
@@ -84,7 +83,7 @@
 
     private final Context mContext;
     private final Handler mHandler;
-    private final NavigationBar.Factory mNavigationBarFactory;
+    private final NavigationBarComponent.Factory mNavigationBarComponentFactory;
     private final DisplayManager mDisplayManager;
     private final TaskbarDelegate mTaskbarDelegate;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -110,7 +109,7 @@
             ConfigurationController configurationController,
             NavBarHelper navBarHelper,
             TaskbarDelegate taskbarDelegate,
-            NavigationBar.Factory navigationBarFactory,
+            NavigationBarComponent.Factory navigationBarComponentFactory,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             DumpManager dumpManager,
             AutoHideController autoHideController,
@@ -119,7 +118,7 @@
             Optional<BackAnimation> backAnimation) {
         mContext = context;
         mHandler = mainHandler;
-        mNavigationBarFactory = navigationBarFactory;
+        mNavigationBarComponentFactory = navigationBarComponentFactory;
         mDisplayManager = mContext.getSystemService(DisplayManager.class);
         commandQueue.addCallback(this);
         configurationController.addCallback(this);
@@ -324,14 +323,13 @@
         final Context context = isOnDefaultDisplay
                 ? mContext
                 : mContext.createDisplayContext(display);
-        NavigationBar navBar = mNavigationBarFactory.create(
-                context, context.getSystemService(WindowManager.class));
-
+        NavigationBarComponent component = mNavigationBarComponentFactory.create(
+                context, savedState);
+        NavigationBar navBar = component.getNavigationBar();
+        navBar.init();
         mNavigationBars.put(displayId, navBar);
 
-        boolean navBarVisible = mStatusBarKeyguardViewManager.isNavBarVisible();
-        View navigationBarView = navBar.createView(savedState, navBarVisible);
-        navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+        navBar.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
                 if (result != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
new file mode 100644
index 0000000..93bf136
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
@@ -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.systemui.navigationbar;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.DisplayId;
+import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Module for {@link com.android.systemui.navigationbar.NavigationBarComponent}. */
+@Module
+public interface NavigationBarModule {
+    /** A Layout inflater specific to the display's context. */
+    @Provides
+    @NavigationBarScope
+    @DisplayId
+    static LayoutInflater provideLayoutInflater(@DisplayId Context context) {
+        return LayoutInflater.from(context);
+    }
+
+    /** */
+    @Provides
+    @NavigationBarScope
+    static NavigationBarFrame provideNavigationBarFrame(@DisplayId LayoutInflater layoutInflater) {
+        return (NavigationBarFrame) layoutInflater.inflate(R.layout.navigation_bar_window, null);
+    }
+
+    /** */
+    @Provides
+    @NavigationBarScope
+    static NavigationBarView provideNavigationBarview(
+            @DisplayId LayoutInflater layoutInflater, NavigationBarFrame frame) {
+        View barView = layoutInflater.inflate(R.layout.navigation_bar, frame);
+        return barView.findViewById(R.id.navigation_bar_view);
+    }
+
+    /** A WindowManager specific to the display's context. */
+    @Provides
+    @NavigationBarScope
+    @DisplayId
+    static WindowManager provideWindowManager(@DisplayId Context context) {
+        return context.getSystemService(WindowManager.class);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
index 7fb58f0..9305d05 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
@@ -30,6 +30,8 @@
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
 
+import javax.inject.Inject;
+
 /**
  * The "dead zone" consumes unintentional taps along the top edge of the navigation bar.
  * When users are typing quickly on an IME they may attempt to hit the space bar, overshoot, and
@@ -82,6 +84,7 @@
         }
     };
 
+    @Inject
     public DeadZone(NavigationBarView view) {
         mNavigationBarView = view;
         mNavBarController = Dependency.get(NavigationBarController.class);
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 2ac34b2..369a552 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -271,6 +271,9 @@
             mPlaySound = false;
             return;
         }
+        if (!showLowBatteryNotification()) {
+            return;
+        }
 
         final int warningLevel = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_lowBatteryWarningLevel);
@@ -323,6 +326,29 @@
         return isSevereState && mUseSevereDialog;
     }
 
+    /**
+     * Disable low battery warning notification if battery saver schedule mode set as
+     * "Based on percentage".
+     *
+     * return {@code false} if scheduled by percentage.
+     */
+    private boolean showLowBatteryNotification() {
+        final ContentResolver resolver = mContext.getContentResolver();
+        final int mode = Settings.Global.getInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
+                PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
+
+        // Return true if battery saver schedule mode will not trigger by percentage.
+        if (mode != PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE) {
+            return true;
+        }
+
+        // Return true if battery saver mode trigger percentage is less than 0, which means it is
+        // set as "Based on routine" mode, otherwise it will be "Based on percentage" mode.
+        final int threshold =
+                Settings.Global.getInt(resolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
+        return threshold <= 0;
+    }
+
     private void showAutoSaverSuggestionNotification() {
         final CharSequence message = mContext.getString(R.string.auto_saver_text);
         final Notification.Builder nb =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index 27da6f3..842a1b9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -247,18 +247,7 @@
     }
 
     fun setExpansion(headerExpansionFraction: Float) {
-        if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
-            if (headerExpansionFraction != lastExpansion) {
-                if (headerExpansionFraction >= 1f) {
-                    mView.animate().alpha(1f).setDuration(500L).start()
-                } else if (lastExpansion >= 1f && headerExpansionFraction < 1f) {
-                    mView.animate().alpha(0f).setDuration(250L).start()
-                }
-                lastExpansion = headerExpansionFraction
-            }
-        } else {
-            alphaAnimator.setPosition(headerExpansionFraction)
-        }
+        alphaAnimator.setPosition(headerExpansionFraction)
     }
 
     fun setKeyguardShowing(showing: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index f868055..ceb895f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -139,13 +139,17 @@
     }
 
     void updateResources(QSPanelController qsPanelController,
-            QuickStatusBarHeaderController quickStatusBarHeaderController) {
+            QuickStatusBarHeaderController quickStatusBarHeaderController,
+            boolean newFooter) {
+        int bottomPadding = 0;
+        if (newFooter) {
+            bottomPadding = getResources().getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
+        }
         mQSPanelContainer.setPaddingRelative(
                 mQSPanelContainer.getPaddingStart(),
                 Utils.getQsHeaderSystemIconsAreaHeight(mContext),
                 mQSPanelContainer.getPaddingEnd(),
-                mQSPanelContainer.getPaddingBottom()
-        );
+                bottomPadding);
 
         int sideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
         int padding = getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index 7d61991..61da182 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -18,6 +18,8 @@
 
 import android.content.res.Configuration;
 
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.ViewController;
@@ -30,23 +32,26 @@
     private final QSPanelController mQsPanelController;
     private final QuickStatusBarHeaderController mQuickStatusBarHeaderController;
     private final ConfigurationController mConfigurationController;
+    private final boolean mNewFooter;
 
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
         @Override
         public void onConfigChanged(Configuration newConfig) {
-            mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
+            mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController, mNewFooter);
         }
     };
 
     @Inject
     QSContainerImplController(QSContainerImpl view, QSPanelController qsPanelController,
             QuickStatusBarHeaderController quickStatusBarHeaderController,
-            ConfigurationController configurationController) {
+            ConfigurationController configurationController,
+            FeatureFlags featureFlags) {
         super(view);
         mQsPanelController = qsPanelController;
         mQuickStatusBarHeaderController = quickStatusBarHeaderController;
         mConfigurationController = configurationController;
+        mNewFooter = featureFlags.isEnabled(Flags.NEW_FOOTER);
     }
 
     @Override
@@ -60,7 +65,7 @@
 
     @Override
     protected void onViewAttached() {
-        mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
+        mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController, mNewFooter);
         mConfigurationController.addCallback(mConfigurationListener);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index fe8c309..4e63104 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -599,6 +599,9 @@
                     mQSPanelScrollView.getHeight());
         }
         mQSPanelScrollView.setClipBounds(mQsBounds);
+
+        mQsMediaHost.getCurrentClipping().set(0, 0, getView().getMeasuredWidth(),
+                mQSPanelScrollView.getMeasuredHeight() - mQSPanelScrollView.getPaddingBottom());
     }
 
     private void updateMediaPositions() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index b04d752..11a36ad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -374,7 +374,7 @@
         setPaddingRelative(getPaddingStart(),
                 paddingTop,
                 getPaddingEnd(),
-                mUseNewFooter ? res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom) : 0);
+                getPaddingEnd());
     }
 
     void addOnConfigurationChangedListener(OnConfigurationChangedListener listener) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 865f093..dd2929c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -33,7 +33,6 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.media.MediaFlags;
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.media.MediaHostState;
@@ -46,7 +45,6 @@
 import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.Utils;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -65,7 +63,6 @@
     private final FalsingManager mFalsingManager;
     private final BrightnessController mBrightnessController;
     private final BrightnessSliderController mBrightnessSliderController;
-    private final MediaFlags mMediaFlags;
     private final BrightnessMirrorHandler mBrightnessMirrorHandler;
     private final FeatureFlags mFeatureFlags;
 
@@ -75,7 +72,6 @@
             new QSPanel.OnConfigurationChangedListener() {
         @Override
         public void onConfigurationChange(Configuration newConfig) {
-            updateMediaExpansion();
             mView.updateResources();
             mQsSecurityFooter.onConfigurationChanged();
             if (mView.isListening()) {
@@ -105,8 +101,7 @@
             DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
             QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
             BrightnessSliderController.Factory brightnessSliderFactory,
-            FalsingManager falsingManager, FeatureFlags featureFlags,
-            MediaFlags mediaFlags) {
+            FalsingManager falsingManager, FeatureFlags featureFlags) {
         super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
                 metricsLogger, uiEventLogger, qsLogger, dumpManager);
         mQSFgsManagerFooter = qsFgsManagerFooter;
@@ -117,7 +112,6 @@
         mFalsingManager = falsingManager;
 
         mBrightnessSliderController = brightnessSliderFactory.create(getContext(), mView);
-        mMediaFlags = mediaFlags;
         mView.setBrightnessView(mBrightnessSliderController.getRootView());
 
         mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController);
@@ -129,7 +123,7 @@
     @Override
     public void onInit() {
         super.onInit();
-        updateMediaExpansion();
+        mMediaHost.setExpansion(MediaHostState.EXPANDED);
         mMediaHost.setShowsOnlyActiveMedia(false);
         mMediaHost.init(MediaHierarchyManager.LOCATION_QS);
         mQsCustomizerController.init();
@@ -137,17 +131,6 @@
         mQSFgsManagerFooter.init();
     }
 
-    private void updateMediaExpansion() {
-        boolean inSplitShade = Utils.shouldUseSplitNotificationShade(getResources());
-        float expansion;
-        if (inSplitShade && !mMediaFlags.useMediaSessionLayout()) {
-            expansion = MediaHostState.COLLAPSED;
-        } else {
-            expansion = MediaHostState.EXPANDED;
-        }
-        mMediaHost.setExpansion(expansion);
-    }
-
     @Override
     protected void onViewAttached() {
         super.onViewAttached();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index b2e008b..c6ebd73 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -26,7 +26,6 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaFlags;
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.qs.QSTile;
@@ -53,7 +52,6 @@
                 }
             };
 
-    private final MediaFlags mMediaFlags;
     private final boolean mUsingCollapsedLandscapeMedia;
 
     @Inject
@@ -62,14 +60,12 @@
             @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
             @Named(QUICK_QS_PANEL) MediaHost mediaHost,
             @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA) boolean usingCollapsedLandscapeMedia,
-            MediaFlags mediaFlags,
             MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
             DumpManager dumpManager
     ) {
         super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
                 uiEventLogger, qsLogger, dumpManager);
         mUsingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia;
-        mMediaFlags = mediaFlags;
     }
 
     @Override
@@ -84,8 +80,7 @@
         int rotation = getRotation();
         boolean isLandscape = rotation == RotationUtils.ROTATION_LANDSCAPE
                 || rotation == RotationUtils.ROTATION_SEASCAPE;
-        if (mMediaFlags.useMediaSessionLayout()
-                && (!mUsingCollapsedLandscapeMedia || !isLandscape)) {
+        if (!mUsingCollapsedLandscapeMedia || !isLandscape) {
             mMediaHost.setExpansion(MediaHost.EXPANDED);
         } else {
             mMediaHost.setExpansion(MediaHost.COLLAPSED);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 7fb9ef3..be6982a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -16,13 +16,14 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.ArgbEvaluator;
+import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.Color;
 import android.graphics.drawable.Animatable2;
 import android.graphics.drawable.Animatable2.AnimationCallback;
 import android.graphics.drawable.Drawable;
@@ -53,6 +54,8 @@
     @Nullable
     private QSTile.Icon mLastIcon;
 
+    private ValueAnimator mColorAnimator = new ValueAnimator();
+
     public QSIconViewImpl(Context context) {
         super(context);
 
@@ -61,6 +64,7 @@
 
         mIcon = createIcon();
         addView(mIcon);
+        mColorAnimator.setDuration(QS_ANIM_LENGTH);
     }
 
     @Override
@@ -165,7 +169,6 @@
             mState = state.state;
             if (mTint != 0 && allowAnimations && shouldAnimate(iv)) {
                 animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations));
-                mTint = color;
             } else {
                 if (iv instanceof AlphaControlledSlashImageView) {
                     ((AlphaControlledSlashImageView)iv)
@@ -173,7 +176,6 @@
                 } else {
                     setTint(iv, color);
                 }
-                mTint = color;
                 updateIcon(iv, state, allowAnimations);
             }
         } else {
@@ -191,39 +193,30 @@
             ((AlphaControlledSlashImageView)iv)
                     .setFinalImageTintList(ColorStateList.valueOf(toColor));
         }
+        mColorAnimator.cancel();
         if (mAnimationEnabled && ValueAnimator.areAnimatorsEnabled()) {
-            final float fromAlpha = Color.alpha(fromColor);
-            final float toAlpha = Color.alpha(toColor);
-            final float fromChannel = Color.red(fromColor);
-            final float toChannel = Color.red(toColor);
-
-            ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
-            anim.setDuration(QS_ANIM_LENGTH);
-            anim.addUpdateListener(animation -> {
-                float fraction = animation.getAnimatedFraction();
-                int alpha = (int) (fromAlpha + (toAlpha - fromAlpha) * fraction);
-                int channel = (int) (fromChannel + (toChannel - fromChannel) * fraction);
-
-                setTint(iv, Color.argb(alpha, channel, channel, channel));
+            PropertyValuesHolder values = PropertyValuesHolder.ofInt("color", fromColor, toColor);
+            values.setEvaluator(ArgbEvaluator.getInstance());
+            mColorAnimator.setValues(values);
+            mColorAnimator.removeAllListeners();
+            mColorAnimator.addUpdateListener(animation -> {
+                setTint(iv, (int) animation.getAnimatedValue());
             });
-            anim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    endRunnable.run();
-                }
-            });
-            anim.start();
+            mColorAnimator.addListener(new EndRunnableAnimatorListener(endRunnable));
+
+            mColorAnimator.start();
         } else {
+
             setTint(iv, toColor);
             endRunnable.run();
         }
     }
 
-    public static void setTint(ImageView iv, int color) {
+    public void setTint(ImageView iv, int color) {
         iv.setImageTintList(ColorStateList.valueOf(color));
+        mTint = color;
     }
 
-
     protected int getIconMeasureMode() {
         return MeasureSpec.EXACTLY;
     }
@@ -261,4 +254,25 @@
                 return 0;
         }
     }
+
+    private static class EndRunnableAnimatorListener extends AnimatorListenerAdapter {
+        private Runnable mRunnable;
+
+        EndRunnableAnimatorListener(Runnable endRunnable) {
+            super();
+            mRunnable = endRunnable;
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            super.onAnimationCancel(animation);
+            mRunnable.run();
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            super.onAnimationEnd(animation);
+            mRunnable.run();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
index 3c6ab34..bbba007 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
@@ -199,7 +199,7 @@
             drawConcave(canvas);
         } else if (mCornerRadiusEnabled && mCornerRadius > 0) {
             canvas.drawRoundRect(getBounds().left, getBounds().top, getBounds().right,
-                    getBounds().bottom + mCornerRadius,
+                    getBounds().bottom,
                     /* x radius*/ mCornerRadius, /* y radius*/ mCornerRadius, mPaint);
         } else {
             canvas.drawRect(getBounds().left, getBounds().top, getBounds().right,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index d51aaad..55ca8f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -575,13 +575,7 @@
                                     return;
                                 }
                                 int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
-                                try {
-                                    mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
-                                    mIActivityManager.stopUser(currentUserId, true /* force */,
-                                            null);
-                                } catch (RemoteException re) {
-                                    Log.e(TAG, "Failed to logout user", re);
-                                }
+                                mDevicePolicyManager.logoutUser();
                             })
                             .build(),
                     false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java
index 4b0e2ff..41eeada0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java
@@ -21,7 +21,6 @@
 import android.os.AsyncTask;
 import android.util.Log;
 
-import java.io.IOException;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
@@ -82,16 +81,8 @@
 
         @Override
         protected Drawable doInBackground(Uri... uris) {
-            Drawable drawable = null;
             Uri target = uris[0];
-
-            try {
-                drawable = mResolver.resolveImage(target);
-            } catch (IOException | SecurityException ex) {
-                Log.d(TAG, "PreloadImageTask: Resolve failed from " + target, ex);
-            }
-
-            return drawable;
+            return mResolver.resolveImage(target);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
index 44ccb68..b05e64ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
@@ -31,7 +31,6 @@
 import com.android.internal.widget.LocalImageResolver;
 import com.android.internal.widget.MessagingMessage;
 
-import java.io.IOException;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -111,30 +110,30 @@
      * To resolve image from specified uri directly. If the resulting image is larger than the
      * maximum allowed size, scale it down.
      * @param uri Uri of the image.
-     * @return Drawable of the image.
-     * @throws IOException Throws if failed at resolving the image.
+     * @return Drawable of the image, or null if unable to load.
      */
-    Drawable resolveImage(Uri uri) throws IOException {
-        return LocalImageResolver.resolveImage(uri, mContext, mMaxImageWidth, mMaxImageHeight);
+    Drawable resolveImage(Uri uri) {
+        try {
+            return LocalImageResolver.resolveImage(uri, mContext, mMaxImageWidth, mMaxImageHeight);
+        } catch (Exception ex) {
+            // Catch general Exception because ContentResolver can re-throw arbitrary Exception
+            // from remote process as a RuntimeException. See: Parcel#readException
+            Log.d(TAG, "resolveImage: Can't load image from " + uri, ex);
+        }
+        return null;
     }
 
     @Override
     public Drawable loadImage(Uri uri) {
-        Drawable result = null;
-        try {
-            if (hasCache()) {
-                // if the uri isn't currently cached, try caching it first
-                if (!mImageCache.hasEntry(uri)) {
-                    mImageCache.preload((uri));
-                }
-                result = mImageCache.get(uri);
-            } else {
-                result = resolveImage(uri);
-            }
-        } catch (IOException | SecurityException ex) {
-            Log.d(TAG, "loadImage: Can't load image from " + uri, ex);
+        return hasCache() ? loadImageFromCache(uri) : resolveImage(uri);
+    }
+
+    private Drawable loadImageFromCache(Uri uri) {
+        // if the uri isn't currently cached, try caching it first
+        if (!mImageCache.hasEntry(uri)) {
+            mImageCache.preload((uri));
         }
-        return result;
+        return mImageCache.get(uri);
     }
 
     /**
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 c89f4d7..5c68559 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
@@ -689,7 +689,8 @@
         boolean showFooterView = (showDismissView || mController.getVisibleNotificationCount() > 0)
                 && mIsCurrentUserSetup  // see: b/193149550
                 && mStatusBarState != StatusBarState.KEYGUARD
-                && mQsExpansionFraction != 1
+                // quick settings don't affect notifications when not in full screen
+                && (mQsExpansionFraction != 1 || !mQsFullScreen)
                 && !mScreenOffAnimationController.shouldHideNotificationsFooter()
                 && !mIsRemoteInputActive;
         boolean showHistory = mController.isHistoryEnabled();
@@ -799,6 +800,9 @@
         y = (int) (mAmbientState.getStackY() + mKeyguardNotificationAvailableSpace);
         drawDebugInfo(canvas, y, Color.RED, /* label= */
                 "mAmbientState.getStackY() + mKeyguardNotificationAvailableSpace = " + y);
+
+        drawDebugInfo(canvas, mRoundedRectClippingBottom, Color.DKGRAY,
+                /* label= */ "mRoundedRectClippingBottom) = " + y);
     }
 
     private void drawDebugInfo(Canvas canvas, int y, int color, String label) {
@@ -1311,7 +1315,7 @@
         if (mOnStackYChanged != null) {
             mOnStackYChanged.accept(listenerNeedsAnimation);
         }
-        if (mQsExpansionFraction <= 0 && !shouldSkipHeightUpdate()) {
+        if ((mQsExpansionFraction <= 0 || !mQsFullScreen) && !shouldSkipHeightUpdate()) {
             final float endHeight = updateStackEndHeight();
             updateStackHeight(endHeight, fraction);
         }
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 b46ed57..aab5ff8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -537,27 +537,6 @@
         mPendingShowBouncer = false;
     }
 
-    @Override
-    public void onStartedGoingToSleep(int why) {
-        resetMode();
-        mFadedAwayAfterWakeAndUnlock = false;
-        mPendingAuthenticated = null;
-    }
-
-    @Override
-    public void onFinishedGoingToSleep(int why) {
-        Trace.beginSection("BiometricUnlockController#onFinishedGoingToSleep");
-        if (mPendingAuthenticated != null) {
-            PendingAuthenticated pendingAuthenticated = mPendingAuthenticated;
-            // Post this to make sure it's executed after the device is fully locked.
-            mHandler.post(() -> onBiometricAuthenticated(pendingAuthenticated.userId,
-                    pendingAuthenticated.biometricSourceType,
-                    pendingAuthenticated.isStrongBiometric));
-            mPendingAuthenticated = null;
-        }
-        Trace.endSection();
-    }
-
     public boolean hasPendingAuthentication() {
         return mPendingAuthenticated != null
                 && mUpdateMonitor
@@ -752,13 +731,34 @@
     @VisibleForTesting
     final WakefulnessLifecycle.Observer mWakefulnessObserver =
             new WakefulnessLifecycle.Observer() {
-        @Override
-        public void onFinishedWakingUp() {
-            if (mPendingShowBouncer) {
-                BiometricUnlockController.this.showBouncer();
-            }
-        }
-    };
+                @Override
+                public void onFinishedWakingUp() {
+                    if (mPendingShowBouncer) {
+                        BiometricUnlockController.this.showBouncer();
+                    }
+                }
+
+                @Override
+                public void onStartedGoingToSleep() {
+                    resetMode();
+                    mFadedAwayAfterWakeAndUnlock = false;
+                    mPendingAuthenticated = null;
+                }
+
+                @Override
+                public void onFinishedGoingToSleep() {
+                    Trace.beginSection("BiometricUnlockController#onFinishedGoingToSleep");
+                    if (mPendingAuthenticated != null) {
+                        PendingAuthenticated pendingAuthenticated = mPendingAuthenticated;
+                        // Post this to make sure it's executed after the device is fully locked.
+                        mHandler.post(() -> onBiometricAuthenticated(pendingAuthenticated.userId,
+                                pendingAuthenticated.biometricSourceType,
+                                pendingAuthenticated.isStrongBiometric));
+                        mPendingAuthenticated = null;
+                    }
+                    Trace.endSection();
+                }
+            };
 
     private final ScreenLifecycle.Observer mScreenObserver =
             new ScreenLifecycle.Observer() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 6a7e8eb..c2c8bd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -1877,13 +1877,7 @@
         if (!mPresenter.isCollapsing()) {
             onClosingFinished();
         }
-
-        // Collapse the panel if we're launching in fullscreen, over the lockscreen. Do not do this
-        // if the device has gone back to sleep - through a horrific chain of 15 or so function
-        // calls, instantCollapseNotificationPanel will eventually call through to
-        // StatusBar#wakeUpIfDozing, which will wake the device up even if it was put to sleep
-        // during the launch animation.
-        if (launchIsFullScreen && mPowerManager.isInteractive()) {
+        if (launchIsFullScreen) {
             instantCollapseNotificationPanel();
         }
     }
@@ -3136,6 +3130,10 @@
     public void finishKeyguardFadingAway() {
         mKeyguardStateController.notifyKeyguardDoneFading();
         mScrimController.setExpansionAffectsAlpha(true);
+
+        // If the device was re-locked while unlocking, we might have a pending lock that was
+        // delayed because the keyguard was in the middle of going away.
+        mKeyguardViewMediator.maybeHandlePendingLock();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 7f1611f..347e05c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -1101,7 +1101,9 @@
             mIndicationArea.setPadding(mIndicationPadding, 0, mIndicationPadding, 0);
         } else {
             mQRCodeScannerButton.setVisibility(GONE);
-            mIndicationArea.setPadding(0, 0, 0, 0);
+            if (mControlsButton.getVisibility() == GONE) {
+                mIndicationArea.setPadding(0, 0, 0, 0);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 5746ffb..6cca904 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -1098,6 +1098,8 @@
 
         if (splitNotificationShadeChanged) {
             updateClockAppearance();
+            updateQsState();
+            mNotificationStackScrollLayoutController.updateFooter();
         }
     }
 
@@ -2348,7 +2350,7 @@
     private int calculateBottomQsClippingBound(int top) {
         if (mShouldUseSplitNotificationShade) {
             return top + mNotificationStackScrollLayoutController.getHeight()
-                    - mSplitShadeNotificationsScrimMarginBottom;
+                    + mSplitShadeNotificationsScrimMarginBottom;
         } else {
             return getView().getBottom();
         }
@@ -2466,7 +2468,12 @@
             // be visible, otherwise you can see the bounds once swiping up to see bouncer
             mScrimController.setNotificationsBounds(0, 0, 0, 0);
         } else {
-            mScrimController.setNotificationsBounds(left, top, right, bottom);
+            // Increase the height of the notifications scrim when not in split shade
+            // (e.g. portrait tablet) so the rounded corners are not visible at the bottom,
+            // in this case they are rendered off-screen
+            final int notificationsScrimBottom =
+                    mShouldUseSplitNotificationShade ? bottom : bottom + radius;
+            mScrimController.setNotificationsBounds(left, top, right, notificationsScrimBottom);
         }
 
         if (mShouldUseSplitNotificationShade) {
@@ -2476,10 +2483,13 @@
         }
 
         mScrimController.setScrimCornerRadius(radius);
+
+        // Convert global clipping coordinates to local ones,
+        // relative to NotificationStackScrollLayout
         int nsslLeft = left - mNotificationStackScrollLayoutController.getLeft();
         int nsslRight = right - mNotificationStackScrollLayoutController.getLeft();
         int nsslTop = top - mNotificationStackScrollLayoutController.getTop();
-        int nsslBottom = bottom;
+        int nsslBottom = bottom - mNotificationStackScrollLayoutController.getTop();
         int bottomRadius = mShouldUseSplitNotificationShade ? radius : 0;
         mNotificationStackScrollLayoutController.setRoundedClippingBounds(
                 nsslLeft, nsslTop, nsslRight, nsslBottom, radius, bottomRadius);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 79d646c..d26b378 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -63,6 +63,8 @@
     // TODO(b/203389579): Remove this once the dialog width on large screens has been agreed on.
     private static final String FLAG_TABLET_DIALOG_WIDTH =
             "persist.systemui.flag_tablet_dialog_width";
+    private static final int DEFAULT_THEME = R.style.Theme_SystemUI_Dialog;
+    private static final boolean DEFAULT_DISMISS_ON_DEVICE_LOCK = true;
 
     private final Context mContext;
     @Nullable private final DismissReceiver mDismissReceiver;
@@ -78,11 +80,15 @@
     private List<Runnable> mOnCreateRunnables = new ArrayList<>();
 
     public SystemUIDialog(Context context) {
-        this(context, R.style.Theme_SystemUI_Dialog);
+        this(context, DEFAULT_THEME, DEFAULT_DISMISS_ON_DEVICE_LOCK);
     }
 
     public SystemUIDialog(Context context, int theme) {
-        this(context, theme, true /* dismissOnDeviceLock */);
+        this(context, theme, DEFAULT_DISMISS_ON_DEVICE_LOCK);
+    }
+
+    public SystemUIDialog(Context context, boolean dismissOnDeviceLock) {
+        this(context, DEFAULT_THEME, dismissOnDeviceLock);
     }
 
     public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 4d6d05f..006edca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -903,11 +903,11 @@
             this.radius = radius;
         }
 
-        Animator createCircularRevealAnimator(View view) {
+        Animator createCircularHideAnimator(View view) {
             return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, radius, 0);
         }
 
-        Animator createCircularHideAnimator(View view) {
+        Animator createCircularRevealAnimator(View view) {
             return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, 0, radius);
         }
     }
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 763f041..ca5edb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -447,14 +447,6 @@
         mResumeUserOnGuestLogout = resume;
     }
 
-    public void logoutCurrentUser() {
-        int currentUser = mUserTracker.getUserId();
-        if (currentUser != UserHandle.USER_SYSTEM) {
-            pauseRefreshUsers();
-            ActivityManager.logoutCurrentUser();
-        }
-    }
-
     /**
      * Returns whether the current user is a system user.
      */
@@ -711,8 +703,7 @@
         if (mUsers.isEmpty()) return null;
         UserRecord item = mUsers.stream().filter(x -> x.isCurrent).findFirst().orElse(null);
         if (item == null || item.info == null) return null;
-        if (item.isGuest) return mContext.getString(
-                com.android.settingslib.R.string.guest_nickname);
+        if (item.isGuest) return mContext.getString(com.android.internal.R.string.guest_name);
         return item.info.name;
     }
 
@@ -949,7 +940,7 @@
                             : com.android.settingslib.R.string.guest_exit_guest);
                 } else {
                     if (item.info != null) {
-                        return context.getString(com.android.settingslib.R.string.guest_nickname);
+                        return context.getString(com.android.internal.R.string.guest_name);
                     } else {
                         if (mController.mGuestUserAutoCreated) {
                             // If mGuestIsResetting=true, we expect the guest user to be created
@@ -961,7 +952,7 @@
                             return context.getString(
                                     mController.mGuestIsResetting.get()
                                             ? com.android.settingslib.R.string.guest_resetting
-                                            : com.android.settingslib.R.string.guest_nickname);
+                                            : com.android.internal.R.string.guest_name);
                         } else {
                             return context.getString(
                                     com.android.settingslib.R.string.guest_new_guest);
@@ -969,7 +960,7 @@
                     }
                 }
             } else if (item.isAddUser) {
-                return context.getString(R.string.user_add_user);
+                return context.getString(com.android.settingslib.R.string.user_add_user);
             } else if (item.isAddSupervisedUser) {
                 return context.getString(R.string.add_user_supervised);
             } else {
@@ -1136,7 +1127,7 @@
             super(context);
             setTitle(mGuestUserAutoCreated
                     ? com.android.settingslib.R.string.guest_reset_guest_dialog_title
-                    : R.string.guest_exit_guest_dialog_title);
+                    : com.android.settingslib.R.string.guest_remove_guest_dialog_title);
             setMessage(context.getString(R.string.guest_exit_guest_dialog_message));
             setButton(DialogInterface.BUTTON_NEUTRAL,
                     context.getString(android.R.string.cancel), this);
@@ -1173,8 +1164,8 @@
 
         public AddUserDialog(Context context) {
             super(context);
-            setTitle(R.string.user_add_user_title);
-            setMessage(context.getString(R.string.user_add_user_message_short));
+            setTitle(com.android.settingslib.R.string.user_add_user_title);
+            setMessage(com.android.settingslib.R.string.user_add_user_message_short);
             setButton(DialogInterface.BUTTON_NEUTRAL,
                     context.getString(android.R.string.cancel), this);
             setButton(DialogInterface.BUTTON_POSITIVE,
diff --git a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
index c9de966..cc6bf6a 100644
--- a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
@@ -110,7 +110,7 @@
                 },
                 null,
                 defaultUserName,
-                getString(R.string.user_add_user),
+                getString(com.android.settingslib.R.string.user_add_user),
                 this::addUserNow,
                 this::finish
         );
@@ -134,7 +134,7 @@
         mSetupUserDialog.dismiss();
 
         userName = (userName == null || userName.trim().isEmpty())
-                ? getString(R.string.user_new_user_name)
+                ? getString(com.android.settingslib.R.string.user_new_user_name)
                 : userName;
 
         mUserCreator.createUser(userName, userIcon,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index ec92adb..ac78626 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -33,7 +33,6 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -61,9 +60,11 @@
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.view.WindowMetrics;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
@@ -102,7 +103,7 @@
     private SecureSettings mSecureSettings;
     private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
     private FakeThreadFactory mThreadFactory;
-    private ArrayList<DecorProvider> mDecorProviders;
+    private ArrayList<DecorProvider> mPrivacyDecorProviders;
     @Mock
     private Display mDisplay;
     @Mock
@@ -197,17 +198,43 @@
         reset(mTunerService);
     }
 
+    @NonNull
+    private int[] getRoundCornerIdsFromOverlayId(@DisplayCutout.BoundsPosition int overlayId) {
+        switch (overlayId) {
+            case BOUNDS_POSITION_LEFT:
+                return new int[] {
+                        R.id.rounded_corner_top_left,
+                        R.id.rounded_corner_top_left };
+            case BOUNDS_POSITION_TOP:
+                return new int[] {
+                        R.id.rounded_corner_top_left,
+                        R.id.rounded_corner_top_right };
+            case BOUNDS_POSITION_RIGHT:
+                return new int[] {
+                        R.id.rounded_corner_top_right,
+                        R.id.rounded_corner_bottom_right };
+            case BOUNDS_POSITION_BOTTOM:
+                return new int[] {
+                        R.id.rounded_corner_bottom_left,
+                        R.id.rounded_corner_bottom_right };
+            default:
+                throw new IllegalArgumentException("unknown overlayId: " + overlayId);
+        }
+    }
 
-    private void verifyRoundedCornerViewsVisibility(
+    private void verifyRoundedCornerViewsExist(
             @DisplayCutout.BoundsPosition final int overlayId,
-            @View.Visibility final int visibility) {
+            @View.Visibility final boolean isExist) {
         final View overlay = mScreenDecorations.mOverlays[overlayId].getRootView();
-        final View left = overlay.findViewById(R.id.left);
-        final View right = overlay.findViewById(R.id.right);
-        assertNotNull(left);
-        assertNotNull(right);
-        assertThat(left.getVisibility()).isEqualTo(visibility);
-        assertThat(right.getVisibility()).isEqualTo(visibility);
+        for (int id: getRoundCornerIdsFromOverlayId(overlayId)) {
+            final View view = overlay.findViewById(id);
+            if (isExist) {
+                assertNotNull(view);
+                assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
+            } else {
+                assertNull(view);
+            }
+        }
     }
 
     @Nullable
@@ -351,8 +378,8 @@
         verifyOverlaysExistAndAdded(false, true, false, true);
 
         // Rounded corner views shall not exist
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, false);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, false);
 
         // Privacy dots shall exist but invisible
         verifyDotViewsVisibility(View.INVISIBLE);
@@ -380,8 +407,8 @@
         verifyOverlaysExistAndAdded(false, true, false, true);
 
         // Rounded corner views shall exist
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);
 
         // Privacy dots shall not exist
         verifyDotViewsNullable(true);
@@ -408,8 +435,8 @@
         verifyOverlaysExistAndAdded(false, true, false, true);
 
         // Rounded corner views shall exist
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);
 
         // Privacy dots shall exist but invisible
         verifyDotViewsVisibility(View.INVISIBLE);
@@ -449,21 +476,26 @@
 
         mScreenDecorations.start();
         View leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView()
-                .findViewById(R.id.left);
+                .findViewById(R.id.rounded_corner_top_left);
         View rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView()
-                .findViewById(R.id.right);
-        verify(mScreenDecorations, atLeastOnce())
-                .setSize(leftRoundedCorner, new Size(testTopRadius, testTopRadius));
-        verify(mScreenDecorations, atLeastOnce())
-                .setSize(rightRoundedCorner, new Size(testTopRadius, testTopRadius));
+                .findViewById(R.id.rounded_corner_top_right);
+        ViewGroup.LayoutParams leftParams = leftRoundedCorner.getLayoutParams();
+        ViewGroup.LayoutParams rightParams = rightRoundedCorner.getLayoutParams();
+        assertEquals(leftParams.width, testTopRadius);
+        assertEquals(leftParams.height, testTopRadius);
+        assertEquals(rightParams.width, testTopRadius);
+        assertEquals(rightParams.height, testTopRadius);
+
         leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView()
-                .findViewById(R.id.left);
+                .findViewById(R.id.rounded_corner_bottom_left);
         rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView()
-                .findViewById(R.id.right);
-        verify(mScreenDecorations, atLeastOnce())
-                .setSize(leftRoundedCorner, new Size(testBottomRadius, testBottomRadius));
-        verify(mScreenDecorations, atLeastOnce())
-                .setSize(rightRoundedCorner, new Size(testBottomRadius, testBottomRadius));
+                .findViewById(R.id.rounded_corner_bottom_right);
+        leftParams = leftRoundedCorner.getLayoutParams();
+        rightParams = rightRoundedCorner.getLayoutParams();
+        assertEquals(leftParams.width, testBottomRadius);
+        assertEquals(leftParams.height, testBottomRadius);
+        assertEquals(rightParams.width, testBottomRadius);
+        assertEquals(rightParams.height, testBottomRadius);
     }
 
     @Test
@@ -479,31 +511,27 @@
                 .when(mScreenDecorations).getCutout();
 
         mScreenDecorations.start();
-        final Size topRadius = new Size(testTopRadius, testTopRadius);
-        final Size bottomRadius = new Size(testBottomRadius, testBottomRadius);
-        View leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView()
-                .findViewById(R.id.left);
-        boolean isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_LEFT, R.id.left);
-        verify(mScreenDecorations, atLeastOnce())
-                .setSize(leftRoundedCorner, isTop ? topRadius : bottomRadius);
+        View topRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView()
+                .findViewById(R.id.rounded_corner_top_left);
+        View bottomRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView()
+                .findViewById(R.id.rounded_corner_bottom_left);
+        ViewGroup.LayoutParams topParams = topRoundedCorner.getLayoutParams();
+        ViewGroup.LayoutParams bottomParams = bottomRoundedCorner.getLayoutParams();
+        assertEquals(topParams.width, testTopRadius);
+        assertEquals(topParams.height, testTopRadius);
+        assertEquals(bottomParams.width, testBottomRadius);
+        assertEquals(bottomParams.height, testBottomRadius);
 
-        View rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView()
-                .findViewById(R.id.right);
-        isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_LEFT, R.id.right);
-        verify(mScreenDecorations, atLeastOnce())
-                .setSize(rightRoundedCorner, isTop ? topRadius : bottomRadius);
-
-        leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView()
-                .findViewById(R.id.left);
-        isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_RIGHT, R.id.left);
-        verify(mScreenDecorations, atLeastOnce())
-                .setSize(leftRoundedCorner, isTop ? topRadius : bottomRadius);
-
-        rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView()
-                .findViewById(R.id.right);
-        isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_RIGHT, R.id.right);
-        verify(mScreenDecorations, atLeastOnce())
-                .setSize(rightRoundedCorner, isTop ? topRadius : bottomRadius);
+        topRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView()
+                .findViewById(R.id.rounded_corner_top_right);
+        bottomRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView()
+                .findViewById(R.id.rounded_corner_bottom_right);
+        topParams = topRoundedCorner.getLayoutParams();
+        bottomParams = bottomRoundedCorner.getLayoutParams();
+        assertEquals(topParams.width, testTopRadius);
+        assertEquals(topParams.height, testTopRadius);
+        assertEquals(bottomParams.width, testBottomRadius);
+        assertEquals(bottomParams.height, testBottomRadius);
     }
 
     @Test
@@ -523,8 +551,8 @@
         verifyOverlaysExistAndAdded(false, true, false, true);
 
         // Rounded corner views shall exist
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);
 
         // Privacy dots shall not exist
         verifyDotViewsNullable(true);
@@ -557,8 +585,8 @@
         verifyOverlaysExistAndAdded(false, true, false, true);
 
         // Rounded corner views shall exist
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);
 
         // Privacy dots shall exist but invisible
         verifyDotViewsVisibility(View.INVISIBLE);
@@ -617,10 +645,10 @@
 
         // Top rounded corner views shall exist because of cutout
         // but be gone because of no rounded corner
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, false);
         // Bottom rounded corner views shall exist because of privacy dot
         // but be gone because of no rounded corner
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, false);
 
         // Privacy dots shall exist but invisible
         verifyDotViewsVisibility(View.INVISIBLE);
@@ -648,7 +676,7 @@
 
         // Left rounded corner views shall exist because of cutout
         // but be gone because of no rounded corner
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_LEFT, View.GONE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_LEFT, false);
 
         // Top privacy dots shall not exist because of no privacy
         verifyDotViewsNullable(true);
@@ -700,8 +728,8 @@
         verifyOverlaysExistAndAdded(false, true, false, true);
 
         // Rounded corner views shall exist
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);
 
         // Top privacy dots shall not exist because of no privacy dot
         verifyDotViewsNullable(true);
@@ -728,8 +756,8 @@
         verifyOverlaysExistAndAdded(false, true, false, true);
 
         // Rounded corner views shall exist
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);
 
         // Top privacy dots shall exist but invisible
         verifyDotViewsVisibility(View.INVISIBLE);
@@ -859,7 +887,7 @@
         verifyOverlaysExistAndAdded(true, false, true, false);
 
         // Verify each privacy dot id appears only once
-        mDecorProviders.stream().map(DecorProvider::getViewId).forEach(viewId -> {
+        mPrivacyDecorProviders.stream().map(DecorProvider::getViewId).forEach(viewId -> {
             int findCount = 0;
             for (OverlayWindow overlay: mScreenDecorations.mOverlays) {
                 if (overlay == null) {
@@ -913,8 +941,8 @@
         // Both top and bottom windows should be added because of privacy dot,
         // but their visibility shall be gone because of no rounding.
         verifyOverlaysExistAndAdded(false, true, false, true);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, false);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, false);
 
         when(mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout))
@@ -925,8 +953,8 @@
         // Both top and bottom windows should be added because of privacy dot,
         // but their visibility shall be gone because of no rounding.
         verifyOverlaysExistAndAdded(false, true, false, true);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, false);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, false);
     }
 
     @Test
@@ -1174,14 +1202,14 @@
         mContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, fillCutout);
 
-        mDecorProviders = new ArrayList<>();
+        mPrivacyDecorProviders = new ArrayList<>();
         if (privacyDot) {
-            mDecorProviders.add(mPrivacyDotTopLeftDecorProvider);
-            mDecorProviders.add(mPrivacyDotTopRightDecorProvider);
-            mDecorProviders.add(mPrivacyDotBottomLeftDecorProvider);
-            mDecorProviders.add(mPrivacyDotBottomRightDecorProvider);
+            mPrivacyDecorProviders.add(mPrivacyDotTopLeftDecorProvider);
+            mPrivacyDecorProviders.add(mPrivacyDotTopRightDecorProvider);
+            mPrivacyDecorProviders.add(mPrivacyDotBottomLeftDecorProvider);
+            mPrivacyDecorProviders.add(mPrivacyDotBottomRightDecorProvider);
         }
-        when(mPrivacyDotDecorProviderFactory.getProviders()).thenReturn(mDecorProviders);
+        when(mPrivacyDotDecorProviderFactory.getProviders()).thenReturn(mPrivacyDecorProviders);
         when(mPrivacyDotDecorProviderFactory.getHasProviders()).thenReturn(privacyDot);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewBoundAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
similarity index 92%
rename from packages/SystemUI/tests/src/com/android/systemui/animation/ViewBoundAnimatorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index 214fd4d..8eb0918 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewBoundAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -19,7 +19,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
-class ViewBoundAnimatorTest : SysuiTestCase() {
+class ViewHierarchyAnimatorTest : SysuiTestCase() {
     companion object {
         private const val TEST_DURATION = 1000L
         private val TEST_INTERPOLATOR = Interpolators.LINEAR
@@ -34,14 +34,14 @@
 
     @After
     fun tearDown() {
-        ViewBoundAnimator.stopAnimating(rootView)
+        ViewHierarchyAnimator.stopAnimating(rootView)
     }
 
     @Test
     fun respectsAnimationParameters() {
         rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
 
-        ViewBoundAnimator.animate(
+        ViewHierarchyAnimator.animate(
             rootView, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION
         )
         rootView.layout(0 /* l */, 0 /* t */, 100 /* r */, 100 /* b */)
@@ -56,7 +56,7 @@
     fun animatesFromStartToEnd() {
         rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
 
-        ViewBoundAnimator.animate(rootView)
+        ViewHierarchyAnimator.animate(rootView)
         // Change all bounds.
         rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */)
 
@@ -73,7 +73,7 @@
     fun animatesSuccessiveLayoutChanges() {
         rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
 
-        ViewBoundAnimator.animate(rootView)
+        ViewHierarchyAnimator.animate(rootView)
         // Change all bounds.
         rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */)
 
@@ -103,7 +103,7 @@
     fun animatesFromPreviousAnimationProgress() {
         rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
 
-        ViewBoundAnimator.animateNextUpdate(rootView, interpolator = TEST_INTERPOLATOR)
+        ViewHierarchyAnimator.animateNextUpdate(rootView, interpolator = TEST_INTERPOLATOR)
         // Change all bounds.
         rootView.layout(0 /* l */, 20 /* t */, 70 /* r */, 80 /* b */)
 
@@ -131,7 +131,7 @@
         firstChild.layout(0 /* l */, 0 /* t */, 100 /* r */, 100 /* b */)
         secondChild.layout(100 /* l */, 0 /* t */, 150 /* r */, 100 /* b */)
 
-        ViewBoundAnimator.animate(rootView)
+        ViewHierarchyAnimator.animate(rootView)
         // Change all bounds.
         rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */)
         firstChild.layout(10 /* l */, 20 /* t */, 150 /* r */, 120 /* b */)
@@ -154,7 +154,7 @@
     fun doesNotAnimateInvisibleViews() {
         rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
 
-        ViewBoundAnimator.animate(rootView)
+        ViewHierarchyAnimator.animate(rootView)
         // GONE.
         rootView.visibility = View.GONE
         rootView.layout(0 /* l */, 15 /* t */, 55 /* r */, 80 /* b */)
@@ -171,7 +171,7 @@
     fun doesNotAnimateUnchangingBounds() {
         rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
 
-        ViewBoundAnimator.animate(rootView)
+        ViewHierarchyAnimator.animate(rootView)
         // No bounds are changed.
         rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
 
@@ -191,9 +191,9 @@
     fun doesNotAnimateExcludedBounds() {
         rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
 
-        ViewBoundAnimator.animate(
+        ViewHierarchyAnimator.animate(
             rootView,
-            bounds = setOf(ViewBoundAnimator.Bound.LEFT, ViewBoundAnimator.Bound.TOP),
+            bounds = setOf(ViewHierarchyAnimator.Bound.LEFT, ViewHierarchyAnimator.Bound.TOP),
             interpolator = TEST_INTERPOLATOR
         )
         // Change all bounds.
@@ -211,7 +211,7 @@
     fun stopsAnimatingAfterSingleLayout() {
         rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
 
-        ViewBoundAnimator.animateNextUpdate(rootView)
+        ViewHierarchyAnimator.animateNextUpdate(rootView)
         // Change all bounds.
         rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */)
 
@@ -231,7 +231,7 @@
     fun stopsAnimatingWhenInstructed() {
         rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
 
-        ViewBoundAnimator.animate(rootView)
+        ViewHierarchyAnimator.animate(rootView)
         // Change all bounds.
         rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */)
 
@@ -240,7 +240,7 @@
         assertNull(rootView.getTag(R.id.tag_animator))
         checkBounds(rootView, l = 0, t = 15, r = 70, b = 80)
 
-        ViewBoundAnimator.stopAnimating(rootView)
+        ViewHierarchyAnimator.stopAnimating(rootView)
         // Change all bounds again.
         rootView.layout(10 /* l */, 10 /* t */, 50/* r */, 50 /* b */)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt
index ca74df0..5182210 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt
@@ -19,25 +19,19 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.DisplayCutout
-import android.view.LayoutInflater
 import android.view.Surface
 import android.view.View
-import android.view.ViewGroup
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.mockito.eq
 import org.junit.Assert
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
 
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper(setAsMainLooper = true)
@@ -45,62 +39,88 @@
 class OverlayWindowTest : SysuiTestCase() {
 
     companion object {
-        private val TEST_DECOR_VIEW_ID = R.id.privacy_dot_bottom_right_container
-        private val TEST_DECOR_LAYOUT_ID = R.layout.privacy_dot_bottom_right
+        private val TEST_DECOR_VIEW_ID_1 = R.id.privacy_dot_top_left_container
+        private val TEST_DECOR_VIEW_ID_2 = R.id.privacy_dot_bottom_right_container
     }
 
     private lateinit var overlay: OverlayWindow
-
-    @Mock private lateinit var layoutInflater: LayoutInflater
-    @Mock private lateinit var decorProvider: DecorProvider
+    private lateinit var decorProvider1: DecorProvider
+    private lateinit var decorProvider2: DecorProvider
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
+        decorProvider1 = spy(PrivacyDotCornerDecorProviderImpl(
+                viewId = TEST_DECOR_VIEW_ID_1,
+                alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP,
+                alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT,
+                layoutId = R.layout.privacy_dot_top_left))
+        decorProvider2 = spy(PrivacyDotCornerDecorProviderImpl(
+                viewId = TEST_DECOR_VIEW_ID_2,
+                alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM,
+                alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT,
+                layoutId = R.layout.privacy_dot_bottom_right))
 
-        layoutInflater = spy(LayoutInflater.from(mContext))
-
-        overlay = OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_RIGHT)
-
-        whenever(decorProvider.viewId).thenReturn(TEST_DECOR_VIEW_ID)
-        whenever(decorProvider.inflateView(
-            eq(layoutInflater),
-            eq(overlay.rootView),
-            anyInt())
-        ).then {
-            val layoutInflater = it.getArgument<LayoutInflater>(0)
-            val parent = it.getArgument<ViewGroup>(1)
-            layoutInflater.inflate(TEST_DECOR_LAYOUT_ID, parent)
-            return@then parent.getChildAt(parent.childCount - 1)
-        }
-    }
-
-    @Test
-    fun testAnyBoundsPositionShallNoExceptionForConstructor() {
-        OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_LEFT)
-        OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_TOP)
-        OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_RIGHT)
-        OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_BOTTOM)
+        overlay = OverlayWindow(mContext)
     }
 
     @Test
     fun testAddProvider() {
         @Surface.Rotation val rotation = Surface.ROTATION_270
-        overlay.addDecorProvider(decorProvider, rotation)
-        verify(decorProvider, Mockito.times(1)).inflateView(
-                eq(layoutInflater), eq(overlay.rootView), eq(rotation))
-        val viewFoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID)
-        Assert.assertNotNull(viewFoundFromRootView)
-        Assert.assertEquals(viewFoundFromRootView, overlay.getView(TEST_DECOR_VIEW_ID))
+        overlay.addDecorProvider(decorProvider1, rotation)
+        overlay.addDecorProvider(decorProvider2, rotation)
+
+        verify(decorProvider1, times(1)).inflateView(
+                mContext, overlay.rootView, rotation)
+        verify(decorProvider2, times(1)).inflateView(
+                mContext, overlay.rootView, rotation)
+
+        val view1FoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID_1)
+        Assert.assertNotNull(view1FoundFromRootView)
+        Assert.assertEquals(view1FoundFromRootView, overlay.getView(TEST_DECOR_VIEW_ID_1))
+        val view2FoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID_2)
+        Assert.assertNotNull(view2FoundFromRootView)
+        Assert.assertEquals(view2FoundFromRootView, overlay.getView(TEST_DECOR_VIEW_ID_2))
     }
 
     @Test
     fun testRemoveView() {
-        @Surface.Rotation val rotation = Surface.ROTATION_270
-        overlay.addDecorProvider(decorProvider, rotation)
-        overlay.removeView(TEST_DECOR_VIEW_ID)
-        val viewFoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID)
+        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270)
+        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270)
+        overlay.removeView(TEST_DECOR_VIEW_ID_1)
+
+        val viewFoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID_1)
         Assert.assertNull(viewFoundFromRootView)
-        Assert.assertNull(overlay.getView(TEST_DECOR_LAYOUT_ID))
+        Assert.assertNull(overlay.getView(TEST_DECOR_VIEW_ID_1))
+    }
+
+    @Test
+    fun testOnReloadResAndMeasureWithoutIds() {
+        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0)
+        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0)
+
+        overlay.onReloadResAndMeasure(
+                reloadToken = 1,
+                rotation = Surface.ROTATION_90,
+                displayUniqueId = null)
+        verify(decorProvider1, times(1)).onReloadResAndMeasure(
+                overlay.getView(TEST_DECOR_VIEW_ID_1)!!, 1, Surface.ROTATION_90, null)
+        verify(decorProvider2, times(1)).onReloadResAndMeasure(
+                overlay.getView(TEST_DECOR_VIEW_ID_2)!!, 1, Surface.ROTATION_90, null)
+    }
+
+    @Test
+    fun testOnReloadResAndMeasureWithIds() {
+        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0)
+        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0)
+
+        overlay.onReloadResAndMeasure(
+                filterIds = arrayOf(TEST_DECOR_VIEW_ID_2),
+                reloadToken = 1,
+                rotation = Surface.ROTATION_90,
+                displayUniqueId = null)
+        verify(decorProvider1, never()).onReloadResAndMeasure(
+                overlay.getView(TEST_DECOR_VIEW_ID_1)!!, 1, Surface.ROTATION_90, null)
+        verify(decorProvider2, times(1)).onReloadResAndMeasure(
+                overlay.getView(TEST_DECOR_VIEW_ID_2)!!, 1, Surface.ROTATION_90, null)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt
index bac0817..171b767 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt
@@ -18,7 +18,6 @@
 
 import android.content.res.Resources
 import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
 import android.view.DisplayCutout
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
@@ -32,7 +31,6 @@
 import org.mockito.Mockito.`when` as whenever
 
 @RunWith(AndroidTestingRunner::class)
-@RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class PrivacyDotDecorProviderFactoryTest : SysuiTestCase() {
     private lateinit var mPrivacyDotDecorProviderFactory: PrivacyDotDecorProviderFactory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt
new file mode 100644
index 0000000..621bcf6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.decor
+
+import android.testing.AndroidTestingRunner
+import android.util.Size
+import android.view.DisplayCutout
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.spy
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class RoundedCornerDecorProviderFactoryTest : SysuiTestCase() {
+
+    @Mock private lateinit var roundedCornerResDelegate: RoundedCornerResDelegate
+    private lateinit var roundedCornerDecorProviderFactory: RoundedCornerDecorProviderFactory
+
+    @Before
+    fun setUp() {
+        roundedCornerResDelegate = spy(RoundedCornerResDelegate(mContext.resources, null))
+    }
+
+    @Test
+    fun testNoRoundedCorners() {
+        Mockito.doReturn(Size(0, 0)).`when`(roundedCornerResDelegate).topRoundedSize
+        Mockito.doReturn(Size(0, 0)).`when`(roundedCornerResDelegate).bottomRoundedSize
+        Mockito.doReturn(false).`when`(roundedCornerResDelegate).isMultipleRadius
+
+        roundedCornerDecorProviderFactory =
+                RoundedCornerDecorProviderFactory(roundedCornerResDelegate)
+
+        Assert.assertEquals(false, roundedCornerDecorProviderFactory.hasProviders)
+        Assert.assertEquals(0, roundedCornerDecorProviderFactory.providers.size)
+    }
+
+    @Test
+    fun testHasRoundedCornersIfTopWidthLargerThan0() {
+        Mockito.doReturn(Size(1, 0)).`when`(roundedCornerResDelegate).topRoundedSize
+        Mockito.doReturn(Size(0, 0)).`when`(roundedCornerResDelegate).bottomRoundedSize
+        Mockito.doReturn(false).`when`(roundedCornerResDelegate).isMultipleRadius
+
+        roundedCornerDecorProviderFactory =
+                RoundedCornerDecorProviderFactory(roundedCornerResDelegate)
+
+        Assert.assertEquals(true, roundedCornerDecorProviderFactory.hasProviders)
+        roundedCornerDecorProviderFactory.providers.let { providers ->
+            Assert.assertEquals(2, providers.size)
+            Assert.assertEquals(1, providers.count {
+                ((it.viewId == R.id.rounded_corner_top_left)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_LEFT))
+            })
+            Assert.assertEquals(1, providers.count {
+                ((it.viewId == R.id.rounded_corner_top_right)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_RIGHT))
+            })
+        }
+    }
+
+    @Test
+    fun testHasRoundedCornersIfBottomWidthLargerThan0() {
+        Mockito.doReturn(Size(0, 0)).`when`(roundedCornerResDelegate).topRoundedSize
+        Mockito.doReturn(Size(1, 1)).`when`(roundedCornerResDelegate).bottomRoundedSize
+        Mockito.doReturn(false).`when`(roundedCornerResDelegate).isMultipleRadius
+
+        roundedCornerDecorProviderFactory =
+                RoundedCornerDecorProviderFactory(roundedCornerResDelegate)
+
+        Assert.assertEquals(true, roundedCornerDecorProviderFactory.hasProviders)
+        roundedCornerDecorProviderFactory.providers.let { providers ->
+            Assert.assertEquals(2, providers.size)
+            Assert.assertEquals(1, providers.count {
+                ((it.viewId == R.id.rounded_corner_bottom_left)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_BOTTOM)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_LEFT))
+            })
+            Assert.assertEquals(1, providers.count {
+                ((it.viewId == R.id.rounded_corner_bottom_right)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_BOTTOM)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_RIGHT))
+            })
+        }
+    }
+
+    @Test
+    fun test4CornerDecorProvidersInfo() {
+        Mockito.doReturn(Size(10, 10)).`when`(roundedCornerResDelegate).topRoundedSize
+        Mockito.doReturn(Size(10, 10)).`when`(roundedCornerResDelegate).bottomRoundedSize
+        Mockito.doReturn(true).`when`(roundedCornerResDelegate).isMultipleRadius
+
+        roundedCornerDecorProviderFactory =
+                RoundedCornerDecorProviderFactory(roundedCornerResDelegate)
+
+        Assert.assertEquals(true, roundedCornerDecorProviderFactory.hasProviders)
+        roundedCornerDecorProviderFactory.providers.let { providers ->
+            Assert.assertEquals(4, providers.size)
+            Assert.assertEquals(1, providers.count {
+                ((it.viewId == R.id.rounded_corner_top_left)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_LEFT))
+            })
+            Assert.assertEquals(1, providers.count {
+                ((it.viewId == R.id.rounded_corner_top_right)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_RIGHT))
+            })
+            Assert.assertEquals(1, providers.count {
+                ((it.viewId == R.id.rounded_corner_bottom_left)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_BOTTOM)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_LEFT))
+            })
+            Assert.assertEquals(1, providers.count {
+                ((it.viewId == R.id.rounded_corner_bottom_right)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_BOTTOM)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_RIGHT))
+            })
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
index b536bfd..e89ed30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
@@ -45,7 +45,7 @@
     }
 
     @Test
-    fun testReloadAllAndDefaultRadius() {
+    fun testUpdateDisplayUniqueId() {
         mContext.orCreateTestableResources.addOverrides(
                 mockTypeArray = mockTypedArray,
                 radius = 3,
@@ -65,7 +65,34 @@
                 radiusTop = 6,
                 radiusBottom = 0)
 
-        roundedCornerResDelegate.reloadAll("test")
+        roundedCornerResDelegate.updateDisplayUniqueId("test", null)
+
+        assertEquals(Size(6, 6), roundedCornerResDelegate.topRoundedSize)
+        assertEquals(Size(5, 5), roundedCornerResDelegate.bottomRoundedSize)
+    }
+
+    @Test
+    fun testNotUpdateDisplayUniqueIdButChangeRefreshToken() {
+        mContext.orCreateTestableResources.addOverrides(
+                mockTypeArray = mockTypedArray,
+                radius = 3,
+                radiusTop = 0,
+                radiusBottom = 4,
+                multipleRadius = false)
+
+        roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null)
+
+        assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize)
+        assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize)
+        assertEquals(false, roundedCornerResDelegate.isMultipleRadius)
+
+        mContext.orCreateTestableResources.addOverrides(
+                mockTypeArray = mockTypedArray,
+                radius = 5,
+                radiusTop = 6,
+                radiusBottom = 0)
+
+        roundedCornerResDelegate.updateDisplayUniqueId(null, 1)
 
         assertEquals(Size(6, 6), roundedCornerResDelegate.topRoundedSize)
         assertEquals(Size(5, 5), roundedCornerResDelegate.bottomRoundedSize)
@@ -75,18 +102,24 @@
     fun testUpdateTuningSizeFactor() {
         mContext.orCreateTestableResources.addOverrides(
                 mockTypeArray = mockTypedArray,
+                radius = 1,
                 radiusTop = 0,
-                radiusBottom = 0,
+                radiusBottom = 2,
                 multipleRadius = false)
 
         roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null)
 
         val factor = 5
-        roundedCornerResDelegate.updateTuningSizeFactor(factor)
+        roundedCornerResDelegate.updateTuningSizeFactor(factor, 1)
         val length = (factor * mContext.resources.displayMetrics.density).toInt()
 
         assertEquals(Size(length, length), roundedCornerResDelegate.topRoundedSize)
         assertEquals(Size(length, length), roundedCornerResDelegate.bottomRoundedSize)
+
+        roundedCornerResDelegate.updateTuningSizeFactor(null, 2)
+
+        assertEquals(Size(1, 1), roundedCornerResDelegate.topRoundedSize)
+        assertEquals(Size(2, 2), roundedCornerResDelegate.bottomRoundedSize)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 3657192..f567b55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -38,6 +38,7 @@
 import androidx.lifecycle.LifecycleRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dreams.complication.DreamPreviewComplication;
@@ -104,6 +105,9 @@
     @Mock
     ViewGroup mDreamOverlayContainerViewParent;
 
+    @Mock
+    UiEventLogger mUiEventLogger;
+
     DreamOverlayService mService;
 
     @Before
@@ -129,7 +133,22 @@
                 mDreamOverlayComponentFactory,
                 mStateController,
                 mKeyguardUpdateMonitor,
-                mPreviewComplication);
+                mPreviewComplication,
+                mUiEventLogger);
+    }
+
+    @Test
+    public void testOnStartMetricsLogged() throws Exception {
+        final IBinder proxy = mService.onBind(new Intent());
+        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+        // Inform the overlay service of dream starting.
+        overlay.startDream(mWindowParams, mDreamOverlayCallback);
+        mMainExecutor.runAllReady();
+
+        verify(mUiEventLogger).log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
+        verify(mUiEventLogger).log(
+                DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
     }
 
     @Test
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 1484c9d..fcfef4a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
@@ -52,8 +52,6 @@
     private lateinit var statusBarStateController: SysuiStatusBarStateController
     @Mock
     private lateinit var configurationController: ConfigurationController
-    @Mock
-    private lateinit var mediaFlags: MediaFlags
 
     @Mock
     private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager
@@ -73,15 +71,13 @@
                 .thenReturn(true)
         whenever(mediaHost.hostView).thenReturn(hostView)
         hostView.layoutParams = FrameLayout.LayoutParams(100, 100)
-        whenever(mediaFlags.useMediaSessionLayout()).thenReturn(false)
         keyguardMediaController = KeyguardMediaController(
             mediaHost,
             bypassController,
             statusBarStateController,
             notificationLockscreenUserManager,
             context,
-            configurationController,
-            mediaFlags
+            configurationController
         )
         keyguardMediaController.attachSinglePaneContainer(mediaContainerView)
         keyguardMediaController.useSplitShade = false
@@ -157,22 +153,7 @@
     }
 
     @Test
-    fun testNotificationLayout_collapsedPlayer() {
-        verify(mediaHost).expansion = MediaHostState.COLLAPSED
-    }
-
-    @Test
-    fun testSessionLayout_expandedPlayer() {
-        whenever(mediaFlags.useMediaSessionLayout()).thenReturn(true)
-        keyguardMediaController = KeyguardMediaController(
-            mediaHost,
-            bypassController,
-            statusBarStateController,
-            notificationLockscreenUserManager,
-            context,
-            configurationController,
-            mediaFlags
-        )
+    fun testMediaHost_expandedPlayer() {
         verify(mediaHost).expansion = MediaHostState.EXPANDED
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 90eff1a..1c7e3a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -89,8 +89,7 @@
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var broadcastSender: BroadcastSender
 
-    @Mock private lateinit var holder: PlayerViewHolder
-    @Mock private lateinit var sessionHolder: PlayerSessionViewHolder
+    @Mock private lateinit var viewHolder: MediaViewHolder
     @Mock private lateinit var view: TransitionLayout
     @Mock private lateinit var seekBarViewModel: SeekBarViewModel
     @Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
@@ -123,8 +122,7 @@
     private lateinit var actionPrev: ImageButton
     @Mock private lateinit var longPressText: TextView
     @Mock private lateinit var handler: Handler
-    private lateinit var settings: View
-    private lateinit var settingsText: TextView
+    private lateinit var settings: ImageButton
     private lateinit var cancel: View
     private lateinit var cancelText: TextView
     private lateinit var dismiss: FrameLayout
@@ -162,8 +160,7 @@
         seekBar = SeekBar(context)
         elapsedTimeView = TextView(context)
         totalTimeView = TextView(context)
-        settings = View(context)
-        settingsText = TextView(context)
+        settings = ImageButton(context)
         cancel = View(context)
         cancelText = TextView(context)
         dismiss = FrameLayout(context)
@@ -179,8 +176,7 @@
         actionPrev = ImageButton(context).also { it.setId(R.id.actionPrev) }
         actionNext = ImageButton(context).also { it.setId(R.id.actionNext) }
 
-        initPlayerHolderMocks()
-        initSessionHolderMocks()
+        initMediaViewHolderMocks()
 
         // Create media session
         val metadataBuilder = MediaMetadata.Builder().apply {
@@ -217,9 +213,9 @@
     }
 
     /**
-     * Initialize elements common to both view holders
+     * Initialize elements in media view holder
      */
-    private fun initMediaViewHolderMocks(viewHolder: MediaViewHolder) {
+    private fun initMediaViewHolderMocks() {
         whenever(viewHolder.player).thenReturn(view)
         whenever(viewHolder.appIcon).thenReturn(appIcon)
         whenever(viewHolder.albumView).thenReturn(albumView)
@@ -233,6 +229,12 @@
         whenever(viewHolder.seekBar).thenReturn(seekBar)
 
         // Action buttons
+        whenever(viewHolder.actionPlayPause).thenReturn(actionPlayPause)
+        whenever(viewHolder.getAction(R.id.actionPlayPause)).thenReturn(actionPlayPause)
+        whenever(viewHolder.actionNext).thenReturn(actionNext)
+        whenever(viewHolder.getAction(R.id.actionNext)).thenReturn(actionNext)
+        whenever(viewHolder.actionPrev).thenReturn(actionPrev)
+        whenever(viewHolder.getAction(R.id.actionPrev)).thenReturn(actionPrev)
         whenever(viewHolder.action0).thenReturn(action0)
         whenever(viewHolder.getAction(R.id.action0)).thenReturn(action0)
         whenever(viewHolder.action1).thenReturn(action1)
@@ -248,34 +250,12 @@
         whenever(viewHolder.longPressText).thenReturn(longPressText)
         whenever(longPressText.handler).thenReturn(handler)
         whenever(viewHolder.settings).thenReturn(settings)
-        whenever(viewHolder.settingsText).thenReturn(settingsText)
         whenever(viewHolder.cancel).thenReturn(cancel)
         whenever(viewHolder.cancelText).thenReturn(cancelText)
         whenever(viewHolder.dismiss).thenReturn(dismiss)
         whenever(viewHolder.dismissText).thenReturn(dismissText)
     }
 
-    /** Mock view holder for the notification player */
-    private fun initPlayerHolderMocks() {
-        initMediaViewHolderMocks(holder)
-
-        whenever(holder.elapsedTimeView).thenReturn(elapsedTimeView)
-        whenever(holder.totalTimeView).thenReturn(totalTimeView)
-    }
-
-    /** Mock view holder for session player */
-    private fun initSessionHolderMocks() {
-        initMediaViewHolderMocks(sessionHolder)
-
-        // Semantic action buttons
-        whenever(sessionHolder.actionPlayPause).thenReturn(actionPlayPause)
-        whenever(sessionHolder.getAction(R.id.actionPlayPause)).thenReturn(actionPlayPause)
-        whenever(sessionHolder.actionNext).thenReturn(actionNext)
-        whenever(sessionHolder.getAction(R.id.actionNext)).thenReturn(actionNext)
-        whenever(sessionHolder.actionPrev).thenReturn(actionPrev)
-        whenever(sessionHolder.getAction(R.id.actionPrev)).thenReturn(actionPrev)
-    }
-
     @After
     fun tearDown() {
         session.release()
@@ -290,41 +270,7 @@
     }
 
     @Test
-    fun bindSemanticActionsOldLayout() {
-        val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
-        val semanticActions = MediaButton(
-            playOrPause = MediaAction(icon, Runnable {}, "play"),
-            nextOrCustom = MediaAction(icon, Runnable {}, "next"),
-            custom0 = MediaAction(icon, null, "custom 0"),
-            custom1 = MediaAction(icon, null, "custom 1")
-        )
-        val state = mediaData.copy(semanticActions = semanticActions)
-
-        player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
-        player.bindPlayer(state, PACKAGE)
-
-        verify(expandedSet).setVisibility(R.id.action0, ConstraintSet.VISIBLE)
-        assertThat(action0.contentDescription).isEqualTo("custom 0")
-        assertThat(action0.isEnabled()).isFalse()
-
-        verify(expandedSet).setVisibility(R.id.action1, ConstraintSet.INVISIBLE)
-        assertThat(action1.isEnabled()).isFalse()
-
-        verify(expandedSet).setVisibility(R.id.action2, ConstraintSet.VISIBLE)
-        assertThat(action2.isEnabled()).isTrue()
-        assertThat(action2.contentDescription).isEqualTo("play")
-
-        verify(expandedSet).setVisibility(R.id.action3, ConstraintSet.VISIBLE)
-        assertThat(action3.isEnabled()).isTrue()
-        assertThat(action3.contentDescription).isEqualTo("next")
-
-        verify(expandedSet).setVisibility(R.id.action4, ConstraintSet.VISIBLE)
-        assertThat(action4.contentDescription).isEqualTo("custom 1")
-        assertThat(action4.isEnabled()).isFalse()
-    }
-
-    @Test
-    fun bindSemanticActionsNewLayout() {
+    fun bindSemanticActions() {
         val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
         val semanticActions = MediaButton(
                 playOrPause = MediaAction(icon, Runnable {}, "play"),
@@ -334,7 +280,7 @@
         )
         val state = mediaData.copy(semanticActions = semanticActions)
 
-        player.attachPlayer(sessionHolder, MediaViewController.TYPE.PLAYER_SESSION)
+        player.attachPlayer(viewHolder)
         player.bindPlayer(state, PACKAGE)
 
         assertThat(actionPrev.isEnabled()).isFalse()
@@ -370,7 +316,7 @@
     }
 
     @Test
-    fun bindNotificationActionsNewLayout() {
+    fun bindNotificationActions() {
         val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
         val actions = listOf(
             MediaAction(icon, Runnable {}, "previous"),
@@ -383,7 +329,7 @@
             actionsToShowInCompact = listOf(1, 2),
             semanticActions = null)
 
-        player.attachPlayer(sessionHolder, MediaViewController.TYPE.PLAYER_SESSION)
+        player.attachPlayer(viewHolder)
         player.bindPlayer(state, PACKAGE)
 
         // Verify semantic actions are hidden
@@ -420,7 +366,7 @@
 
     @Test
     fun bindText() {
-        player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
+        player.attachPlayer(viewHolder)
         player.bindPlayer(mediaData, PACKAGE)
         assertThat(titleText.getText()).isEqualTo(TITLE)
         assertThat(artistText.getText()).isEqualTo(ARTIST)
@@ -428,7 +374,7 @@
 
     @Test
     fun bindDevice() {
-        player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
+        player.attachPlayer(viewHolder)
         player.bindPlayer(mediaData, PACKAGE)
         assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
         assertThat(seamless.contentDescription).isEqualTo(DEVICE_NAME)
@@ -438,7 +384,7 @@
     @Test
     fun bindDisabledDevice() {
         seamless.id = 1
-        player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
+        player.attachPlayer(viewHolder)
         val state = mediaData.copy(device = disabledDevice)
         player.bindPlayer(state, PACKAGE)
         assertThat(seamless.isEnabled()).isFalse()
@@ -449,7 +395,7 @@
     @Test
     fun bindNullDevice() {
         val fallbackString = context.getResources().getString(R.string.media_seamless_other_device)
-        player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
+        player.attachPlayer(viewHolder)
         val state = mediaData.copy(device = null)
         player.bindPlayer(state, PACKAGE)
         assertThat(seamless.isEnabled()).isTrue()
@@ -459,7 +405,7 @@
 
     @Test
     fun bindDeviceResumptionPlayer() {
-        player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
+        player.attachPlayer(viewHolder)
         val state = mediaData.copy(resumption = true)
         player.bindPlayer(state, PACKAGE)
         assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
@@ -468,32 +414,32 @@
 
     @Test
     fun longClick_gutsClosed() {
-        player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
+        player.attachPlayer(viewHolder)
         whenever(mediaViewController.isGutsVisible).thenReturn(false)
 
         val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
-        verify(holder.player).setOnLongClickListener(captor.capture())
+        verify(viewHolder.player).setOnLongClickListener(captor.capture())
 
-        captor.value.onLongClick(holder.player)
+        captor.value.onLongClick(viewHolder.player)
         verify(mediaViewController).openGuts()
     }
 
     @Test
     fun longClick_gutsOpen() {
-        player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
+        player.attachPlayer(viewHolder)
         whenever(mediaViewController.isGutsVisible).thenReturn(true)
 
         val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
-        verify(holder.player).setOnLongClickListener(captor.capture())
+        verify(viewHolder.player).setOnLongClickListener(captor.capture())
 
-        captor.value.onLongClick(holder.player)
+        captor.value.onLongClick(viewHolder.player)
         verify(mediaViewController, never()).openGuts()
         verify(mediaViewController).closeGuts(false)
     }
 
     @Test
     fun cancelButtonClick_animation() {
-        player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
+        player.attachPlayer(viewHolder)
 
         cancel.callOnClick()
 
@@ -502,7 +448,7 @@
 
     @Test
     fun settingsButtonClick() {
-        player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
+        player.attachPlayer(viewHolder)
 
         settings.callOnClick()
 
@@ -515,7 +461,7 @@
     @Test
     fun dismissButtonClick() {
         val mediaKey = "key for dismissal"
-        player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
+        player.attachPlayer(viewHolder)
         val state = mediaData.copy(notificationKey = KEY)
         player.bindPlayer(state, mediaKey)
 
@@ -527,7 +473,7 @@
     @Test
     fun dismissButtonDisabled() {
         val mediaKey = "key for dismissal"
-        player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
+        player.attachPlayer(viewHolder)
         val state = mediaData.copy(isClearable = false, notificationKey = KEY)
         player.bindPlayer(state, mediaKey)
 
@@ -539,7 +485,7 @@
         val mediaKey = "key for dismissal"
         whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong())).thenReturn(false)
 
-        player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
+        player.attachPlayer(viewHolder)
         val state = mediaData.copy(notificationKey = KEY)
         player.bindPlayer(state, mediaKey)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt
deleted file mode 100644
index d6849bf..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt
+++ /dev/null
@@ -1,62 +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.systemui.media
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import android.widget.FrameLayout
-
-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
-
-/**
- * Tests for PlayerViewHolder.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-class PlayerViewHolderTest : SysuiTestCase() {
-
-    private lateinit var inflater: LayoutInflater
-    private lateinit var parent: ViewGroup
-
-    @Before
-    fun setUp() {
-        inflater = LayoutInflater.from(context)
-        parent = FrameLayout(context)
-    }
-
-    @Test
-    fun create() {
-        val holder = PlayerViewHolder.create(inflater, parent)
-        assertThat(holder.player).isNotNull()
-    }
-
-    @Test
-    fun backgroundIsIlluminationDrawable() {
-        val holder = PlayerViewHolder.create(inflater, parent)
-        assertThat(holder.player.background as IlluminationDrawable).isNotNull()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
index 7ac15125..99901a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
@@ -43,7 +43,7 @@
     private val enabledHeight = 2
 
     private lateinit var observer: SeekBarObserver
-    @Mock private lateinit var mockHolder: PlayerViewHolder
+    @Mock private lateinit var mockHolder: MediaViewHolder
     @Mock private lateinit var mockSquigglyProgress: SquigglyProgress
     private lateinit var seekBarView: SeekBar
     private lateinit var elapsedTimeView: TextView
@@ -64,10 +64,8 @@
         elapsedTimeView = TextView(context)
         totalTimeView = TextView(context)
         whenever(mockHolder.seekBar).thenReturn(seekBarView)
-        whenever(mockHolder.elapsedTimeView).thenReturn(elapsedTimeView)
-        whenever(mockHolder.totalTimeView).thenReturn(totalTimeView)
 
-        observer = SeekBarObserver(mockHolder, false /* useSessionLayout */)
+        observer = SeekBarObserver(mockHolder)
     }
 
     @Test
@@ -79,8 +77,6 @@
         // THEN seek bar shows just a thin line with no text
         assertThat(seekBarView.isEnabled()).isFalse()
         assertThat(seekBarView.getThumb().getAlpha()).isEqualTo(0)
-        assertThat(elapsedTimeView.getText()).isEqualTo("")
-        assertThat(totalTimeView.getText()).isEqualTo("")
         assertThat(seekBarView.contentDescription).isEqualTo("")
         assertThat(seekBarView.maxHeight).isEqualTo(disabledHeight)
     }
@@ -93,8 +89,6 @@
         observer.onChanged(data)
         // THEN seek bar is visible and thick
         assertThat(seekBarView.getVisibility()).isEqualTo(View.VISIBLE)
-        assertThat(elapsedTimeView.getVisibility()).isEqualTo(View.VISIBLE)
-        assertThat(totalTimeView.getVisibility()).isEqualTo(View.VISIBLE)
         assertThat(seekBarView.maxHeight).isEqualTo(enabledHeight)
     }
 
@@ -106,8 +100,6 @@
         // THEN seek bar shows the progress
         assertThat(seekBarView.progress).isEqualTo(3000)
         assertThat(seekBarView.max).isEqualTo(120000)
-        assertThat(elapsedTimeView.getText()).isEqualTo("00:03")
-        assertThat(totalTimeView.getText()).isEqualTo("02:00")
 
         val desc = context.getString(R.string.controls_media_seekbar_description, "00:03", "02:00")
         assertThat(seekBarView.contentDescription).isEqualTo(desc)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index 634d9e4..edcf479 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -16,8 +16,18 @@
 
 package com.android.systemui.navigationbar;
 
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
+
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -45,10 +55,15 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Optional;
 
 import dagger.Lazy;
 
+/**
+ * Tests for {@link NavBarHelper}.
+ */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class NavBarHelperTest extends SysuiTestCase {
@@ -77,7 +92,11 @@
     DumpManager mDumpManager;
     @Mock
     NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater;
+    private AccessibilityManager.AccessibilityServicesStateChangeListener
+            mAccessibilityServicesStateChangeListener;
 
+    private static final int ACCESSIBILITY_BUTTON_CLICKABLE_STATE =
+            SYSUI_STATE_A11Y_BUTTON_CLICKABLE | SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
     private NavBarHelper mNavBarHelper;
 
     @Before
@@ -87,6 +106,9 @@
         when(mAssistManager.getAssistInfoForUser(anyInt())).thenReturn(mAssistantComponent);
         when(mUserTracker.getUserId()).thenReturn(1);
 
+        doAnswer((invocation) -> mAccessibilityServicesStateChangeListener =
+                invocation.getArgument(0)).when(
+                mAccessibilityManager).addAccessibilityServicesStateChangeListener(any());
         mNavBarHelper = new NavBarHelper(mContext, mAccessibilityManager,
                 mAccessibilityButtonModeObserver, mAccessibilityButtonTargetObserver,
                 mSystemActions, mOverviewProxyService, mAssistManagerLazy,
@@ -183,4 +205,42 @@
         verify(mNavbarTaskbarStateUpdater, times(1))
                 .updateAssistantAvailable(anyBoolean());
     }
+
+    @Test
+    public void initNavBarHelper_buttonModeNavBar_a11yButtonClickableState() {
+        when(mAccessibilityManager.getAccessibilityShortcutTargets(
+                AccessibilityManager.ACCESSIBILITY_BUTTON)).thenReturn(createFakeShortcutTargets());
+
+        mNavBarHelper.init();
+
+        assertThat(mNavBarHelper.getA11yButtonState()).isEqualTo(
+                ACCESSIBILITY_BUTTON_CLICKABLE_STATE);
+    }
+
+    @Test
+    public void initAccessibilityStateWithFloatingMenuModeAndTargets_disableClickableState() {
+        when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn(
+                ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
+        mNavBarHelper.init();
+
+        assertThat(mNavBarHelper.getA11yButtonState()).isEqualTo(/* disable_clickable_state */ 0);
+    }
+
+    @Test
+    public void onA11yServicesStateChangedWithMultipleServices_a11yButtonClickableState() {
+        when(mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()).thenReturn(
+                ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
+
+        when(mAccessibilityManager.getAccessibilityShortcutTargets(
+                AccessibilityManager.ACCESSIBILITY_BUTTON)).thenReturn(createFakeShortcutTargets());
+        mAccessibilityServicesStateChangeListener.onAccessibilityServicesStateChanged(
+                mAccessibilityManager);
+
+        assertThat(mNavBarHelper.getA11yButtonState()).isEqualTo(
+                ACCESSIBILITY_BUTTON_CLICKABLE_STATE);
+    }
+
+    private List<String> createFakeShortcutTargets() {
+        return new ArrayList<>(List.of("a", "b", "c", "d"));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index bb42c12..4e3bdea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -75,7 +75,7 @@
     @Mock
     private CommandQueue mCommandQueue;
     @Mock
-    private NavigationBar.Factory mNavigationBarFactory;
+    private NavigationBarComponent.Factory mNavigationBarFactory;
 
     @Before
     public void setUp() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index a0aa267..f5b006d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -29,8 +29,6 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS;
 import static com.android.systemui.navigationbar.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -54,7 +52,6 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
-import android.provider.Settings;
 import android.telecom.TelecomManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -82,26 +79,31 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
+import com.android.systemui.navigationbar.buttons.DeadZone;
 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shared.rotation.RotationButtonController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.LightBarController;
+import com.android.systemui.statusbar.phone.LightBarTransitionsController;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.DeviceConfigProxyFake;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.pip.Pip;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -122,12 +124,34 @@
 
     private SysuiTestableContext mSysuiTestableContextExternal;
     @Mock
+    NavigationBarFrame mNavigationBarFrame;
+    @Mock
+    NavigationBarView mNavigationBarView;
+    @Mock
+    ButtonDispatcher mHomeButton;
+    @Mock
+    ButtonDispatcher mRecentsButton;
+    @Mock
+    ButtonDispatcher mAccessibilityButton;
+    @Mock
+    ButtonDispatcher mImeSwitchButton;
+    @Mock
+    ButtonDispatcher mBackButton;
+    @Mock
+    NavigationBarTransitions mNavigationBarTransitions;
+    @Mock
+    RotationButtonController mRotationButtonController;
+    @Mock
+    LightBarTransitionsController mLightBarTransitionsController;
+    @Mock
     private SystemActions mSystemActions;
     @Mock
     private OverviewProxyService mOverviewProxyService;
     @Mock
     private StatusBarStateController mStatusBarStateController;
     @Mock
+    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    @Mock
     private NavigationModeController mNavigationModeController;
     @Mock
     private CommandQueue mCommandQueue;
@@ -160,7 +184,10 @@
     @Mock
     private AssistManager mAssistManager;
     @Mock
+    private DeadZone mDeadZone;
+    @Mock
     private CentralSurfaces mCentralSurfaces;
+    private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake();
 
     @Rule
     public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
@@ -173,6 +200,17 @@
                 .thenReturn(mEdgeBackGestureHandler);
         when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController);
         when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController);
+        when(mNavigationBarView.getHomeButton()).thenReturn(mHomeButton);
+        when(mNavigationBarView.getRecentsButton()).thenReturn(mRecentsButton);
+        when(mNavigationBarView.getAccessibilityButton()).thenReturn(mAccessibilityButton);
+        when(mNavigationBarView.getImeSwitchButton()).thenReturn(mImeSwitchButton);
+        when(mNavigationBarView.getBackButton()).thenReturn(mBackButton);
+        when(mNavigationBarView.getBarTransitions()).thenReturn(mNavigationBarTransitions);
+        when(mNavigationBarView.getRotationButtonController())
+                .thenReturn(mRotationButtonController);
+        when(mNavigationBarView.getLightTransitionsController())
+                .thenReturn(mLightBarTransitionsController);
+        when(mStatusBarKeyguardViewManager.isNavBarVisible()).thenReturn(true);
         setupSysuiDependency();
         // This class inflates views that call Dependency.get, thus these injections are still
         // necessary.
@@ -197,12 +235,6 @@
         });
     }
 
-    @After
-    public void tearDown() throws Exception {
-        DeviceConfig.resetToDefaults(
-                Settings.RESET_MODE_PACKAGE_DEFAULTS, DeviceConfig.NAMESPACE_SYSTEMUI);
-    }
-
     private void setupSysuiDependency() {
         Display display = new Display(DisplayManagerGlobal.getInstance(), EXTERNAL_DISPLAY_ID,
                 new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
@@ -228,8 +260,8 @@
 
     @Test
     public void testHomeLongPress() {
-        mNavigationBar.onViewAttachedToWindow(mNavigationBar
-                .createView(null, /* initialVisibility= */ true));
+        mNavigationBar.init();
+        mNavigationBar.onViewAttached();
         mNavigationBar.onHomeLongClick(mNavigationBar.getView());
 
         verify(mUiEventLogger, times(1)).log(NAVBAR_ASSIST_LONGPRESS);
@@ -237,13 +269,14 @@
 
     @Test
     public void testHomeLongPressWithCustomDuration() throws Exception {
-        DeviceConfig.setProperties(
-                new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_SYSTEMUI)
-                    .setLong(HOME_BUTTON_LONG_PRESS_DURATION_MS, 100)
-                    .build());
+        mDeviceConfigProxyFake.setProperty(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                HOME_BUTTON_LONG_PRESS_DURATION_MS,
+                "100",
+                false);
         when(mNavBarHelper.getLongPressHomeEnabled()).thenReturn(true);
-        mNavigationBar.onViewAttachedToWindow(mNavigationBar
-                .createView(null, /* initialVisibility= */ true));
+        mNavigationBar.init();
+        mNavigationBar.onViewAttached();
 
         mNavigationBar.onHomeTouch(mNavigationBar.getView(), MotionEvent.obtain(
                 /*downTime=*/SystemClock.uptimeMillis(),
@@ -265,8 +298,8 @@
 
     @Test
     public void testRegisteredWithDispatcher() {
-        mNavigationBar.onViewAttachedToWindow(mNavigationBar
-                .createView(null, /* initialVisibility= */ true));
+        mNavigationBar.init();
+        mNavigationBar.onViewAttached();
         verify(mBroadcastDispatcher).registerReceiverWithHandler(
                 any(BroadcastReceiver.class),
                 any(IntentFilter.class),
@@ -286,8 +319,8 @@
         doReturn(true).when(mockShadeWindowView).isAttachedToWindow();
         doNothing().when(defaultNavBar).checkNavBarModes();
         doNothing().when(externalNavBar).checkNavBarModes();
-        defaultNavBar.createView(null, /* initialVisibility= */ true);
-        externalNavBar.createView(null, /* initialVisibility= */ true);
+        defaultNavBar.init();
+        externalNavBar.init();
 
         defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
                 BACK_DISPOSITION_DEFAULT, true);
@@ -321,7 +354,7 @@
         doReturn(mockShadeWindowView).when(mCentralSurfaces).getNotificationShadeWindowView();
         doReturn(true).when(mockShadeWindowView).isAttachedToWindow();
         doNothing().when(mNavigationBar).checkNavBarModes();
-        mNavigationBar.createView(null, /* initialVisibility= */ true);
+        mNavigationBar.init();
         WindowInsets windowInsets = new WindowInsets.Builder().setVisible(ime(), false).build();
         doReturn(windowInsets).when(mockShadeWindowView).getRootWindowInsets();
 
@@ -357,11 +390,11 @@
 
     @Test
     public void testA11yEventAfterDetach() {
-        View v = mNavigationBar.createView(null, /* initialVisibility= */ true);
-        mNavigationBar.onViewAttachedToWindow(v);
+        mNavigationBar.init();
+        mNavigationBar.onViewAttached();
         verify(mNavBarHelper).registerNavTaskStateUpdater(any(
                 NavBarHelper.NavbarTaskbarStateUpdater.class));
-        mNavigationBar.onViewDetachedFromWindow(v);
+        mNavigationBar.onViewDetached();
         verify(mNavBarHelper).removeNavTaskStateUpdater(any(
                 NavBarHelper.NavbarTaskbarStateUpdater.class));
 
@@ -371,23 +404,31 @@
 
     @Test
     public void testCreateView_initiallyVisible_viewIsVisible() {
-        mNavigationBar.createView(null, /* initialVisibility= */ true);
+        when(mStatusBarKeyguardViewManager.isNavBarVisible()).thenReturn(true);
+        mNavigationBar.init();
+        mNavigationBar.onViewAttached();
 
-        assertThat(mNavigationBar.getView().getVisibility()).isEqualTo(View.VISIBLE);
+        verify(mNavigationBarView).setVisibility(View.VISIBLE);
     }
 
     @Test
     public void testCreateView_initiallyNotVisible_viewIsNotVisible() {
-        mNavigationBar.createView(null, /* initialVisibility= */ false);
+        when(mStatusBarKeyguardViewManager.isNavBarVisible()).thenReturn(false);
+        mNavigationBar.init();
+        mNavigationBar.onViewAttached();
 
-        assertThat(mNavigationBar.getView().getVisibility()).isEqualTo(View.INVISIBLE);
+        verify(mNavigationBarView).setVisibility(View.INVISIBLE);
     }
 
     private NavigationBar createNavBar(Context context) {
         DeviceProvisionedController deviceProvisionedController =
                 mock(DeviceProvisionedController.class);
         when(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
-        return spy(new NavigationBar(context,
+        return spy(new NavigationBar(
+                mNavigationBarView,
+                mNavigationBarFrame,
+                null,
+                context,
                 mWindowManager,
                 () -> mAssistManager,
                 mock(AccessibilityManager.class),
@@ -396,6 +437,7 @@
                 mOverviewProxyService,
                 mNavigationModeController,
                 mStatusBarStateController,
+                mStatusBarKeyguardViewManager,
                 mMockSysUiState,
                 mBroadcastDispatcher,
                 mCommandQueue,
@@ -415,6 +457,8 @@
                 mAutoHideControllerFactory,
                 Optional.of(mTelecomManager),
                 mInputMethodManager,
+                mDeadZone,
+                mDeviceConfigProxyFake,
                 Optional.of(mock(BackAnimation.class))));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt
new file mode 100644
index 0000000..bf82e90
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt
@@ -0,0 +1,80 @@
+package com.android.systemui.qs
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.customize.QSCustomizer
+import com.android.systemui.util.mockito.eq
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@SmallTest
+class QSContainerImplTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var quickStatusBarHeader: QuickStatusBarHeader
+    @Mock
+    private lateinit var qsCustomizer: QSCustomizer
+    @Mock
+    private lateinit var qsPanelContainer: NonInterceptingScrollView
+    @Mock
+    private lateinit var qsPanelController: QSPanelController
+    @Mock
+    private lateinit var quickStatusBarHeaderController: QuickStatusBarHeaderController
+
+    private lateinit var qsContainer: QSContainerImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        qsContainer = QSContainerImpl(mContext, null)
+
+        setUpMockView(quickStatusBarHeader, R.id.header)
+        setUpMockView(qsCustomizer, R.id.qs_customize)
+        setUpMockView(qsPanelContainer, R.id.expanded_qs_scroll_view)
+
+        qsContainer.onFinishInflate()
+    }
+
+    private fun setUpMockView(view: View, id: Int) {
+        whenever(view.findViewById<View>(id)).thenReturn(view)
+        whenever(view.layoutParams).thenReturn(FrameLayout.LayoutParams(0, 0))
+        qsContainer.addView(view)
+    }
+
+    @Test
+    fun testContainerBottomPadding() {
+        qsContainer.updateResources(
+            qsPanelController,
+            quickStatusBarHeaderController,
+            /* newFooter */ false
+        )
+        verify(qsPanelContainer).setPaddingRelative(anyInt(), anyInt(), anyInt(), eq(0))
+
+        qsContainer.updateResources(
+            qsPanelController,
+            quickStatusBarHeaderController,
+            /* newFooter */ true
+        )
+        verify(qsPanelContainer)
+            .setPaddingRelative(
+                anyInt(),
+                anyInt(),
+                anyInt(),
+                eq(mContext.resources.getDimensionPixelSize(R.dimen.new_footer_height))
+            )
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index ac1e86f..1b48a16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -8,7 +8,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.media.MediaFlags
 import com.android.systemui.media.MediaHost
 import com.android.systemui.media.MediaHostState
 import com.android.systemui.plugins.FalsingManager
@@ -49,7 +48,6 @@
     @Mock private lateinit var brightnessSliderFactory: BrightnessSliderController.Factory
     @Mock private lateinit var falsingManager: FalsingManager
     @Mock private lateinit var featureFlags: FeatureFlags
-    @Mock private lateinit var mediaFlags: MediaFlags
     @Mock private lateinit var mediaHost: MediaHost
 
     private lateinit var controller: QSPanelController
@@ -79,8 +77,7 @@
             brightnessControllerFactory,
             brightnessSliderFactory,
             falsingManager,
-            featureFlags,
-            mediaFlags
+            featureFlags
         )
     }
 
@@ -90,45 +87,12 @@
     }
 
     @Test
-    fun onInit_notSplitShade_newMediaLayoutAvailable_setsMediaAsExpanded() {
-        setSplitShadeEnabled(false)
-        whenever(mediaFlags.useMediaSessionLayout()).thenReturn(true)
-
+    fun onInit_setsMediaAsExpanded() {
         controller.onInit()
 
         verify(mediaHost).expansion = MediaHostState.EXPANDED
     }
 
-    @Test
-    fun onInit_notSplitShade_newMediaLayoutNotAvailable_setsMediaAsExpanded() {
-        setSplitShadeEnabled(false)
-        whenever(mediaFlags.useMediaSessionLayout()).thenReturn(false)
-
-        controller.onInit()
-
-        verify(mediaHost).expansion = MediaHostState.EXPANDED
-    }
-
-    @Test
-    fun onInit_inSplitShade_newMediaLayoutAvailable_setsMediaAsExpanded() {
-        setSplitShadeEnabled(true)
-        whenever(mediaFlags.useMediaSessionLayout()).thenReturn(true)
-
-        controller.onInit()
-
-        verify(mediaHost).expansion = MediaHostState.EXPANDED
-    }
-
-    @Test
-    fun onInit_inSplitShade_newMediaLayoutNotAvailable_setsMediaAsCollapsed() {
-        setSplitShadeEnabled(true)
-        whenever(mediaFlags.useMediaSessionLayout()).thenReturn(false)
-
-        controller.onInit()
-
-        verify(mediaHost).expansion = MediaHostState.COLLAPSED
-    }
-
     private fun setSplitShadeEnabled(enabled: Boolean) {
         mContext.orCreateTestableResources
             .addOverride(R.bool.config_use_split_notification_shade, enabled)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index 5213a30..04bbd60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -137,20 +137,6 @@
         assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(-1)
     }
 
-    @Test
-    fun testBottomPadding() {
-        mQsPanel.setUseNewFooter(false)
-
-        mQsPanel.updatePadding()
-        assertThat(mQsPanel.paddingBottom).isEqualTo(0)
-
-        mQsPanel.setUseNewFooter(true)
-
-        mQsPanel.updatePadding()
-        assertThat(mQsPanel.paddingBottom)
-                .isEqualTo(mContext.resources.getDimensionPixelSize(R.dimen.new_footer_height))
-    }
-
     private fun getNewOrientationConfig(@Configuration.Orientation newOrientation: Int) =
             context.resources.configuration.apply { orientation = newOrientation }
 }
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 62915b8..1f28210 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -23,7 +23,6 @@
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.MediaFlags
 import com.android.systemui.media.MediaHost
 import com.android.systemui.media.MediaHostState
 import com.android.systemui.plugins.qs.QSTile
@@ -58,8 +57,6 @@
     @Mock
     private lateinit var mediaHost: MediaHost
     @Mock
-    private lateinit var mediaFlags: MediaFlags
-    @Mock
     private lateinit var metricsLogger: MetricsLogger
     private val uiEventLogger = UiEventLoggerFake()
     @Mock
@@ -85,7 +82,6 @@
         `when`(quickQSPanel.dumpableTag).thenReturn("")
         `when`(quickQSPanel.resources).thenReturn(mContext.resources)
         `when`(qsTileHost.createTileView(any(), any(), anyBoolean())).thenReturn(tileView)
-        `when`(mediaFlags.useMediaSessionLayout()).thenReturn(false)
 
         controller = TestQuickQSPanelController(
                 quickQSPanel,
@@ -94,7 +90,6 @@
                 false,
                 mediaHost,
                 true,
-                mediaFlags,
                 metricsLogger,
                 uiEventLogger,
                 qsLogger,
@@ -131,20 +126,17 @@
 
     @Test
     fun testMediaExpansionUpdatedWhenConfigurationChanged() {
-        `when`(mediaFlags.useMediaSessionLayout()).thenReturn(true)
-
         // times(2) because both controller and base controller are registering their listeners
         verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture())
 
-        captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) }
+        // verify that media starts in the expanded state by default
         verify(mediaHost).expansion = MediaHostState.EXPANDED
 
         // Rotate device, verify media size updated
         controller.setRotation(RotationUtils.ROTATION_LANDSCAPE)
         captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) }
 
-        // times(2) because init will have set to collapsed because the flag was off
-        verify(mediaHost, times(2)).expansion = MediaHostState.COLLAPSED
+        verify(mediaHost).expansion = MediaHostState.COLLAPSED
     }
 
     class TestQuickQSPanelController(
@@ -154,13 +146,12 @@
         usingMediaPlayer: Boolean,
         mediaHost: MediaHost,
         usingCollapsedLandscapeMedia: Boolean,
-        mediaFlags: MediaFlags,
         metricsLogger: MetricsLogger,
         uiEventLogger: UiEventLoggerFake,
         qsLogger: QSLogger,
         dumpManager: DumpManager
     ) : QuickQSPanelController(view, qsTileHost, qsCustomizerController, usingMediaPlayer,
-        mediaHost, usingCollapsedLandscapeMedia, mediaFlags, metricsLogger, uiEventLogger, qsLogger,
+        mediaHost, usingCollapsedLandscapeMedia, metricsLogger, uiEventLogger, qsLogger,
         dumpManager) {
 
         private var rotation = RotationUtils.ROTATION_NONE
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index ed144fa..142c2c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -408,7 +408,7 @@
     @Test
     public void onFinishedGoingToSleep_authenticatesWhenPending() {
         when(mUpdateMonitor.isGoingToSleep()).thenReturn(true);
-        mBiometricUnlockController.onFinishedGoingToSleep(-1);
+        mBiometricUnlockController.mWakefulnessObserver.onFinishedGoingToSleep();
         verify(mHandler, never()).post(any());
 
         ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
@@ -416,7 +416,7 @@
         // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(1 /* userId */,
                 BiometricSourceType.FACE, true /* isStrongBiometric */);
-        mBiometricUnlockController.onFinishedGoingToSleep(-1);
+        mBiometricUnlockController.mWakefulnessObserver.onFinishedGoingToSleep();
         verify(mHandler).post(captor.capture());
         captor.getValue().run();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
index 589aa03..7c05c69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
@@ -14,10 +14,12 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.content.BroadcastReceiver;
@@ -43,7 +45,6 @@
 @SmallTest
 public class SystemUIDialogTest extends SysuiTestCase {
 
-    private SystemUIDialog mDialog;
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
 
@@ -52,12 +53,11 @@
         MockitoAnnotations.initMocks(this);
 
         mDependency.injectTestDependency(BroadcastDispatcher.class, mBroadcastDispatcher);
-
-        mDialog = new SystemUIDialog(mContext);
     }
 
     @Test
     public void testRegisterReceiver() {
+        final SystemUIDialog mDialog = new SystemUIDialog(mContext);
         final ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
         final ArgumentCaptor<IntentFilter> intentFilterCaptor =
@@ -66,10 +66,24 @@
         mDialog.show();
         verify(mBroadcastDispatcher).registerReceiver(broadcastReceiverCaptor.capture(),
                 intentFilterCaptor.capture(), eq(null), any());
-
+        assertTrue(intentFilterCaptor.getValue().hasAction(Intent.ACTION_SCREEN_OFF));
         assertTrue(intentFilterCaptor.getValue().hasAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
 
         mDialog.dismiss();
         verify(mBroadcastDispatcher).unregisterReceiver(eq(broadcastReceiverCaptor.getValue()));
     }
+
+
+    @Test
+    public void testNoRegisterReceiver() {
+        final SystemUIDialog mDialog = new SystemUIDialog(mContext, false);
+
+        mDialog.show();
+        verify(mBroadcastDispatcher, never()).registerReceiver(any(), any(), eq(null), any());
+        assertTrue(mDialog.isShowing());
+
+        mDialog.dismiss();
+        verify(mBroadcastDispatcher, never()).unregisterReceiver(any());
+        assertFalse(mDialog.isShowing());
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index e20b15a..6846b2e 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -1006,12 +1006,10 @@
                 }
                 break;
             case ACTION_POINTER_UP:
-                if (event.getPointerId(GestureUtils.getActionIndex(event)) == mDraggingPointerId) {
                     mDraggingPointerId = INVALID_POINTER_ID;
                     // Send an event to the end of the drag gesture.
                     mDispatcher.sendMotionEvent(
                             event, ACTION_UP, rawEvent, pointerIdBits, policyFlags);
-                }
                 break;
             case ACTION_UP:
                 if (event.getPointerId(GestureUtils.getActionIndex(event)) == mDraggingPointerId) {
@@ -1146,6 +1144,10 @@
      * closet to an edge of the screen.
      */
     private void computeDraggingPointerIdIfNeeded(MotionEvent event) {
+        if (event.getPointerCount() != 2) {
+            mDraggingPointerId = INVALID_POINTER_ID;
+            return;
+        }
         if (mDraggingPointerId != INVALID_POINTER_ID) {
             // If we have a valid pointer ID, we should be good
             final int pointerIndex = event.findPointerIndex(mDraggingPointerId);
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index a65d5b3..312105a 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -1617,14 +1617,17 @@
             final int providerCount = mProviders.size();
             for (int i = 0; i < providerCount; i++) {
                 Provider provider = mProviders.get(i);
-                AppWidgetProviderInfo info = provider.getInfoLocked(mContext);
                 final String providerPackageName = provider.id.componentName.getPackageName();
 
-                // Ignore an invalid provider, one not matching the filter,
-                // or one that isn't in the given package, if any.
-                boolean inPackage = packageName == null
-                        || providerPackageName.equals(packageName);
-                if (provider.zombie || (info.widgetCategory & categoryFilter) == 0 || !inPackage) {
+                // Ignore an invalid provider or one that isn't in the given package, if any.
+                boolean inPackage = packageName == null || providerPackageName.equals(packageName);
+                if (provider.zombie || !inPackage) {
+                    continue;
+                }
+
+                // Ignore the ones not matching the filter.
+                AppWidgetProviderInfo info = provider.getInfoLocked(mContext);
+                if ((info.widgetCategory & categoryFilter) == 0) {
                     continue;
                 }
 
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index a2cfe49..af7dcd1 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -38,7 +38,6 @@
 import static android.os.storage.OnObbStateChangeListener.ERROR_ALREADY_MOUNTED;
 import static android.os.storage.OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT;
 import static android.os.storage.OnObbStateChangeListener.ERROR_COULD_NOT_UNMOUNT;
-import static android.os.storage.OnObbStateChangeListener.ERROR_INTERNAL;
 import static android.os.storage.OnObbStateChangeListener.ERROR_NOT_MOUNTED;
 import static android.os.storage.OnObbStateChangeListener.ERROR_PERMISSION_DENIED;
 import static android.os.storage.OnObbStateChangeListener.MOUNTED;
@@ -598,12 +597,6 @@
         }
     }
 
-    /** List of crypto types.
-      * These must match CRYPT_TYPE_XXX in cryptfs.h AND their
-      * corresponding commands in CommandListener.cpp */
-    public static final String[] CRYPTO_TYPES
-        = { "password", "default", "pattern", "pin" };
-
     private final Context mContext;
     private final ContentResolver mResolver;
 
@@ -622,18 +615,6 @@
     private final Callbacks mCallbacks;
     private final LockPatternUtils mLockPatternUtils;
 
-    /**
-     * The size of the crypto algorithm key in bits for OBB files. Currently
-     * Twofish is used which takes 128-bit keys.
-     */
-    private static final int CRYPTO_ALGORITHM_KEY_SIZE = 128;
-
-    /**
-     * The number of times to run SHA1 in the PBKDF2 function for OBB files.
-     * 1024 is reasonably secure and not too slow.
-     */
-    private static final int PBKDF2_HASH_ROUNDS = 1024;
-
     private static final String ANR_DELAY_MILLIS_DEVICE_CONFIG_KEY =
             "anr_delay_millis";
 
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index ed545a6..206a310 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -32,6 +32,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.pm.UserManagerService;
 
 import java.io.PrintWriter;
@@ -261,13 +262,23 @@
         public @interface EventTypesFlag {
         }
 
-        private @EventTypesFlag int mEventType;
+        private final @EventTypesFlag int mEventType;
 
         /** @hide */
         UserCompletedEventType(@EventTypesFlag int eventType) {
             mEventType = eventType;
         }
 
+        /**
+         * Creates a new instance of {@link UserCompletedEventType}.
+         * @hide
+         */
+        @VisibleForTesting
+        public static UserCompletedEventType newUserCompletedEventTypeForTest(
+                @EventTypesFlag int eventType) {
+            return new UserCompletedEventType(eventType);
+        }
+
         /** Returns whether one of the events is {@link #onUserStarting}. */
         public boolean includesOnUserStarting() {
             return (mEventType & EVENT_TYPE_USER_STARTING) != 0;
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 2dfe947..2f84ec5 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -362,11 +362,9 @@
         getContext().getContentResolver().unregisterContentObserver(mSetupWizardObserver);
         verifySetupWizardCompleted();
         synchronized (mLock) {
-            // only update if the value is actually changed
-            if (updateNightModeFromSettingsLocked(getContext(), getContext().getResources(),
-                    to.getUserIdentifier())) {
-                updateLocked(0, 0);
-            }
+            updateNightModeFromSettingsLocked(getContext(), getContext().getResources(),
+                    to.getUserIdentifier());
+            updateLocked(0, 0);
         }
     }
 
@@ -534,19 +532,16 @@
     }
 
     /**
-     * Updates the night mode setting in Settings.Secure and returns if the value was successfully
-     * changed.
+     * Updates the night mode setting in Settings.Secure
      *
      * @param context A valid context
      * @param res     A valid resource object
      * @param userId  The user to update the setting for
-     * @return True if the new value is different from the old value. False otherwise.
      */
-    private boolean updateNightModeFromSettingsLocked(Context context, Resources res, int userId) {
+    private void updateNightModeFromSettingsLocked(Context context, Resources res, int userId) {
         if (mCarModeEnabled || mCar) {
-            return false;
+            return;
         }
-        int oldNightMode = mNightMode;
         if (mSetupWizardComplete) {
             mNightMode = Secure.getIntForUser(context.getContentResolver(),
                     Secure.UI_NIGHT_MODE, res.getInteger(
@@ -570,8 +565,6 @@
                         Secure.UI_NIGHT_MODE_LAST_COMPUTED, 0, userId) != 0;
             }
         }
-
-        return oldNightMode != mNightMode;
     }
 
     private static long toMilliSeconds(LocalTime t) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 309a4ff..a08ef4b5 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -226,6 +226,7 @@
 
     private final AudioSystemAdapter mAudioSystem;
     private final SystemServerAdapter mSystemServer;
+    private final SettingsAdapter mSettings;
 
     /** Debug audio mode */
     protected static final boolean DEBUG_MODE = false;
@@ -876,11 +877,12 @@
     /** @hide */
     public AudioService(Context context) {
         this(context, AudioSystemAdapter.getDefaultAdapter(),
-                SystemServerAdapter.getDefaultAdapter(context));
+                SystemServerAdapter.getDefaultAdapter(context),
+                SettingsAdapter.getDefaultAdapter());
     }
 
     public AudioService(Context context, AudioSystemAdapter audioSystem,
-            SystemServerAdapter systemServer) {
+            SystemServerAdapter systemServer, SettingsAdapter settings) {
         sLifecycleLogger.log(new AudioEventLogger.StringEvent("AudioService()"));
         mContext = context;
         mContentResolver = context.getContentResolver();
@@ -888,6 +890,7 @@
 
         mAudioSystem = audioSystem;
         mSystemServer = systemServer;
+        mSettings = settings;
 
         mPlatformType = AudioSystem.getPlatformType(context);
 
@@ -1024,7 +1027,7 @@
                 new String("AudioService ctor"),
                 0);
 
-        mSafeMediaVolumeState = Settings.Global.getInt(mContentResolver,
+        mSafeMediaVolumeState = mSettings.getGlobalInt(mContentResolver,
                                             Settings.Global.AUDIO_SAFE_VOLUME_STATE,
                                             SAFE_MEDIA_VOLUME_NOT_CONFIGURED);
         // The default safe volume index read here will be replaced by the actual value when
@@ -2009,7 +2012,7 @@
 
     private void readDockAudioSettings(ContentResolver cr)
     {
-        mDockAudioMediaEnabled = Settings.Global.getInt(
+        mDockAudioMediaEnabled = mSettings.getGlobalInt(
                                         cr, Settings.Global.DOCK_AUDIO_MEDIA_ENABLED, 0) == 1;
 
         sendMsg(mAudioHandler,
@@ -2025,7 +2028,7 @@
 
     private void updateMasterMono(ContentResolver cr)
     {
-        final boolean masterMono = System.getIntForUser(
+        final boolean masterMono = mSettings.getSystemIntForUser(
                 cr, System.MASTER_MONO, 0 /* default */, UserHandle.USER_CURRENT) == 1;
         if (DEBUG_VOL) {
             Log.d(TAG, String.format("Master mono %b", masterMono));
@@ -2046,7 +2049,7 @@
 
     private void sendEncodedSurroundMode(ContentResolver cr, String eventSource)
     {
-        final int encodedSurroundMode = Settings.Global.getInt(
+        final int encodedSurroundMode = mSettings.getGlobalInt(
                 cr, Settings.Global.ENCODED_SURROUND_OUTPUT,
                 Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO);
         sendEncodedSurroundMode(encodedSurroundMode, eventSource);
@@ -2162,7 +2165,7 @@
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mSettingsLock) {
-                Settings.Global.putString(mContentResolver,
+                mSettings.putGlobalString(mContentResolver,
                         Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS,
                         TextUtils.join(",", enabledFormats));
             }
@@ -2183,7 +2186,7 @@
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mSettingsLock) {
-                Settings.Global.putInt(mContentResolver,
+                mSettings.putGlobalInt(mContentResolver,
                         Settings.Global.ENCODED_SURROUND_OUTPUT,
                         toEncodedSurroundSetting(mode));
             }
@@ -2204,7 +2207,7 @@
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mSettingsLock) {
-                int encodedSurroundSetting = Settings.Global.getInt(mContentResolver,
+                int encodedSurroundSetting = mSettings.getGlobalInt(mContentResolver,
                         Settings.Global.ENCODED_SURROUND_OUTPUT,
                         AudioManager.ENCODED_SURROUND_OUTPUT_AUTO);
                 return toEncodedSurroundOutputMode(encodedSurroundSetting, targetSdkVersion);
@@ -2217,7 +2220,7 @@
     /** @return the formats that are enabled in global settings */
     private HashSet<Integer> getEnabledFormats() {
         HashSet<Integer> formats = new HashSet<>();
-        String enabledFormats = Settings.Global.getString(mContentResolver,
+        String enabledFormats = mSettings.getGlobalString(mContentResolver,
                 Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS);
         if (enabledFormats != null) {
             try {
@@ -2280,7 +2283,7 @@
             // Manually enable surround formats only when the setting is in manual mode.
             return;
         }
-        String enabledSurroundFormats = Settings.Global.getString(
+        String enabledSurroundFormats = mSettings.getGlobalString(
                 cr, Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS);
         if (enabledSurroundFormats == null) {
             // Never allow enabledSurroundFormats as a null, which could happen when
@@ -2308,7 +2311,7 @@
         }
         // Set filtered surround formats to settings DB in case
         // there are invalid surround formats in original settings.
-        Settings.Global.putString(mContext.getContentResolver(),
+        mSettings.putGlobalString(mContext.getContentResolver(),
                 Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS,
                 TextUtils.join(",", formats));
         sendMsg(mAudioHandler, MSG_ENABLE_SURROUND_FORMATS, SENDMSG_QUEUE, 0, 0, formats, 0);
@@ -2336,11 +2339,11 @@
             packageName = mRoleObserver.getAssistantRoleHolder();
         }
         if (TextUtils.isEmpty(packageName)) {
-            String assistantName = Settings.Secure.getStringForUser(
+            String assistantName = mSettings.getSecureStringForUser(
                             mContentResolver,
                             Settings.Secure.VOICE_INTERACTION_SERVICE, UserHandle.USER_CURRENT);
             if (TextUtils.isEmpty(assistantName)) {
-                assistantName = Settings.Secure.getStringForUser(
+                assistantName = mSettings.getSecureStringForUser(
                         mContentResolver,
                         Settings.Secure.ASSISTANT, UserHandle.USER_CURRENT);
             }
@@ -2383,7 +2386,7 @@
         final ContentResolver cr = mContentResolver;
 
         int ringerModeFromSettings =
-                Settings.Global.getInt(
+                mSettings.getGlobalInt(
                         cr, Settings.Global.MODE_RINGER, AudioManager.RINGER_MODE_NORMAL);
         int ringerMode = ringerModeFromSettings;
         // validity check in case the settings are restored from a device with incompatible
@@ -2395,7 +2398,7 @@
             ringerMode = AudioManager.RINGER_MODE_SILENT;
         }
         if (ringerMode != ringerModeFromSettings) {
-            Settings.Global.putInt(cr, Settings.Global.MODE_RINGER, ringerMode);
+            mSettings.putGlobalInt(cr, Settings.Global.MODE_RINGER, ringerMode);
         }
         if (mUseFixedVolume || mIsSingleVolume) {
             ringerMode = AudioManager.RINGER_MODE_NORMAL;
@@ -2427,7 +2430,7 @@
             AudioSystem.setRttEnabled(mRttEnabled);
         }
 
-        mMuteAffectedStreams = System.getIntForUser(cr,
+        mMuteAffectedStreams = mSettings.getSystemIntForUser(cr,
                 System.MUTE_STREAMS_AFFECTED, AudioSystem.DEFAULT_MUTE_STREAMS_AFFECTED,
                 UserHandle.USER_CURRENT);
 
@@ -4480,7 +4483,7 @@
         int silenceRingerSetting = Settings.Secure.VOLUME_HUSH_OFF;
         if (mContext.getResources()
                 .getBoolean(com.android.internal.R.bool.config_volumeHushGestureEnabled)) {
-            silenceRingerSetting = Settings.Secure.getIntForUser(mContentResolver,
+            silenceRingerSetting = mSettings.getSecureIntForUser(mContentResolver,
                     Settings.Secure.VOLUME_HUSH_GESTURE, VOLUME_HUSH_OFF,
                     UserHandle.USER_CURRENT);
         }
@@ -5241,7 +5244,7 @@
      * Settings has an in memory cache, so this is fast.
      */
     private boolean querySoundEffectsEnabled(int user) {
-        return Settings.System.getIntForUser(getContentResolver(),
+        return mSettings.getSystemIntForUser(getContentResolver(),
                 Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0;
     }
 
@@ -5326,7 +5329,7 @@
         checkMuteAffectedStreams();
 
         synchronized (mSafeMediaVolumeStateLock) {
-            mMusicActiveMs = MathUtils.constrain(Settings.Secure.getIntForUser(mContentResolver,
+            mMusicActiveMs = MathUtils.constrain(mSettings.getSecureIntForUser(mContentResolver,
                     Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, 0, UserHandle.USER_CURRENT),
                     0, UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX);
             if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) {
@@ -5968,7 +5971,7 @@
     @GuardedBy("mSettingsLock")
     private boolean updateRingerAndZenModeAffectedStreams() {
         boolean updatedZenModeAffectedStreams = updateZenModeAffectedStreams();
-        int ringerModeAffectedStreams = Settings.System.getIntForUser(mContentResolver,
+        int ringerModeAffectedStreams = mSettings.getSystemIntForUser(mContentResolver,
                 Settings.System.MODE_RINGER_STREAMS_AFFECTED,
                 ((1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_NOTIFICATION)|
                  (1 << AudioSystem.STREAM_SYSTEM)|(1 << AudioSystem.STREAM_SYSTEM_ENFORCED)),
@@ -5992,7 +5995,7 @@
         }
 
         if (ringerModeAffectedStreams != mRingerModeAffectedStreams) {
-            Settings.System.putIntForUser(mContentResolver,
+            mSettings.putSystemIntForUser(mContentResolver,
                     Settings.System.MODE_RINGER_STREAMS_AFFECTED,
                     ringerModeAffectedStreams,
                     UserHandle.USER_CURRENT);
@@ -6969,7 +6972,7 @@
                         + ", device " + AudioSystem.getOutputDeviceName(device)
                         + " and User=" + ActivityManager.getCurrentUser());
             }
-            boolean success = Settings.System.putIntForUser(mContentResolver,
+            boolean success = mSettings.putSystemIntForUser(mContentResolver,
                     getSettingNameForDevice(device),
                     getIndex(device),
                     UserHandle.USER_CURRENT);
@@ -6995,7 +6998,7 @@
                             ? AudioSystem.DEFAULT_STREAM_VOLUME[mPublicStreamType] : -1;
                     int index;
                     String name = getSettingNameForDevice(device);
-                    index = Settings.System.getIntForUser(
+                    index = mSettings.getSystemIntForUser(
                             mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
                     if (index == -1) {
                         continue;
@@ -7244,7 +7247,7 @@
                         index = defaultIndex;
                     } else {
                         String name = getSettingNameForDevice(device);
-                        index = Settings.System.getIntForUser(
+                        index = mSettings.getSystemIntForUser(
                                 mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
                     }
                     if (index == -1) {
@@ -7781,7 +7784,7 @@
                 return;
             }
             if (streamState.hasValidSettingsName()) {
-                System.putIntForUser(mContentResolver,
+                mSettings.putSystemIntForUser(mContentResolver,
                         streamState.getSettingNameForDevice(device),
                         (streamState.getIndex(device) + 5)/ 10,
                         UserHandle.USER_CURRENT);
@@ -7792,11 +7795,11 @@
             if (mUseFixedVolume) {
                 return;
             }
-            Settings.Global.putInt(mContentResolver, Settings.Global.MODE_RINGER, ringerMode);
+            mSettings.putGlobalInt(mContentResolver, Settings.Global.MODE_RINGER, ringerMode);
         }
 
         private void onPersistSafeVolumeState(int state) {
-            Settings.Global.putInt(mContentResolver,
+            mSettings.putGlobalInt(mContentResolver,
                     Settings.Global.AUDIO_SAFE_VOLUME_STATE,
                     state);
         }
@@ -7940,7 +7943,7 @@
 
                 case MSG_PERSIST_MUSIC_ACTIVE_MS:
                     final int musicActiveMs = msg.arg1;
-                    Settings.Secure.putIntForUser(mContentResolver,
+                    mSettings.putSecureIntForUser(mContentResolver,
                             Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, musicActiveMs,
                             UserHandle.USER_CURRENT);
                     break;
@@ -8081,12 +8084,12 @@
             mContentResolver.registerContentObserver(Settings.System.getUriFor(
                     Settings.System.MASTER_BALANCE), false, this);
 
-            mEncodedSurroundMode = Settings.Global.getInt(
+            mEncodedSurroundMode = mSettings.getGlobalInt(
                     mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT,
                     Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO);
             mContentResolver.registerContentObserver(Settings.Global.getUriFor(
                     Settings.Global.ENCODED_SURROUND_OUTPUT), false, this);
-            mEnabledSurroundFormats = Settings.Global.getString(
+            mEnabledSurroundFormats = mSettings.getGlobalString(
                     mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS);
             mContentResolver.registerContentObserver(Settings.Global.getUriFor(
                     Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS), false, this);
@@ -8120,7 +8123,7 @@
         }
 
         private void updateEncodedSurroundOutput() {
-            int newSurroundMode = Settings.Global.getInt(
+            int newSurroundMode = mSettings.getGlobalInt(
                 mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT,
                 Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO);
             // Did it change?
@@ -8663,13 +8666,13 @@
     }
 
     void onPersistSpatialAudioEnabled(boolean enabled) {
-        Settings.Secure.putIntForUser(mContentResolver,
+        mSettings.putSecureIntForUser(mContentResolver,
                 Settings.Secure.SPATIAL_AUDIO_ENABLED, enabled ? 1 : 0,
                 UserHandle.USER_CURRENT);
     }
 
     boolean isSpatialAudioEnabled() {
-        return Settings.Secure.getIntForUser(mContentResolver,
+        return mSettings.getSecureIntForUser(mContentResolver,
                 Settings.Secure.SPATIAL_AUDIO_ENABLED, SPATIAL_AUDIO_ENABLED_DEFAULT ? 1 : 0,
                 UserHandle.USER_CURRENT) == 1;
     }
@@ -9806,7 +9809,7 @@
         }
 
         public void loadSettings(ContentResolver cr) {
-            mLongPressTimeout = Settings.Secure.getIntForUser(cr,
+            mLongPressTimeout = mSettings.getSecureIntForUser(cr,
                     Settings.Secure.LONG_PRESS_TIMEOUT, 500, UserHandle.USER_CURRENT);
         }
 
@@ -11430,7 +11433,7 @@
         }
         final long callingIdentity = Binder.clearCallingIdentity();
         try {
-            System.putIntForUser(mContentResolver,
+            mSettings.putSystemIntForUser(mContentResolver,
                     getSettingsNameForDeviceVolumeBehavior(deviceType),
                     deviceVolumeBehavior,
                     UserHandle.USER_CURRENT);
@@ -11441,7 +11444,7 @@
 
     @AudioManager.DeviceVolumeBehaviorState
     private int retrieveStoredDeviceVolumeBehavior(int deviceType) {
-        return System.getIntForUser(mContentResolver,
+        return mSettings.getSystemIntForUser(mContentResolver,
                 getSettingsNameForDeviceVolumeBehavior(deviceType),
                 AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET,
                 UserHandle.USER_CURRENT);
diff --git a/services/core/java/com/android/server/audio/SettingsAdapter.java b/services/core/java/com/android/server/audio/SettingsAdapter.java
new file mode 100644
index 0000000..dbc4d6d
--- /dev/null
+++ b/services/core/java/com/android/server/audio/SettingsAdapter.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.audio;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+
+/**
+ * Adapter for methods that read and write settings in android.provider.Settings.
+ */
+public class SettingsAdapter {
+    public static SettingsAdapter getDefaultAdapter() {
+        return new SettingsAdapter();
+    }
+
+    /**
+     * Wrapper methods for Settings.Global
+     */
+
+    /** Wraps {@link Settings.Global#getInt(ContentResolver, String, int)} */
+    public int getGlobalInt(ContentResolver cr, String name, int def) {
+        return Settings.Global.getInt(cr, name, def);
+    }
+
+    /** Wraps {@link Settings.Global#getString(ContentResolver, String)} */
+    public String getGlobalString(ContentResolver resolver, String name) {
+        return Settings.Global.getString(resolver, name);
+    }
+
+    /** Wraps {@link Settings.Global#putInt(ContentResolver, String, int)} */
+    public boolean putGlobalInt(ContentResolver cr, String name, int value) {
+        return Settings.Global.putInt(cr, name, value);
+    }
+
+    /** Wraps {@link Settings.Global#putString(ContentResolver, String, String)} */
+    public boolean putGlobalString(ContentResolver resolver, String name, String value) {
+        return Settings.Global.putString(resolver, name, value);
+    }
+
+    /**
+     * Wrapper methods for Settings.System
+     */
+
+    /** Wraps {@link Settings.System#getIntForUser(ContentResolver, String, int, int)} */
+    public int getSystemIntForUser(ContentResolver cr, String name, int def, int userHandle) {
+        return Settings.System.getIntForUser(cr, name, def, userHandle);
+    }
+
+    /** Wraps {@link Settings.System#putIntForUser(ContentResolver, String, int, int)} */
+    public boolean putSystemIntForUser(ContentResolver cr, String name, int value, int userHandle) {
+        return Settings.System.putIntForUser(cr, name, value, userHandle);
+    }
+
+    /**
+     * Wrapper methods for Settings.Secure
+     */
+
+    /** Wraps {@link Settings.Secure#getIntForUser(ContentResolver, String, int, int)} */
+    public int getSecureIntForUser(ContentResolver cr, String name, int def, int userHandle) {
+        return Settings.Secure.getIntForUser(cr, name, def, userHandle);
+    }
+
+    /** Wraps {@link Settings.Secure#getStringForUser(ContentResolver, String, int)} */
+    public String getSecureStringForUser(ContentResolver resolver, String name, int userHandle) {
+        return Settings.Secure.getStringForUser(resolver, name, userHandle);
+    }
+
+    /** Wraps {@link Settings.Secure#putIntForUser(ContentResolver, String, int, int)} */
+    public boolean putSecureIntForUser(ContentResolver cr, String name, int value, int userHandle) {
+        return Settings.Secure.putIntForUser(cr, name, value, userHandle);
+    }
+
+    /** Wraps {@link Settings.Secure#putStringForUser(ContentResolver, String, String, int)} */
+    public boolean putSecureStringForUser(ContentResolver cr, String name, String value,
+            int userHandle) {
+        return Settings.Secure.putStringForUser(cr, name, value, userHandle);
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index 108e7bc..b9efdf5 100644
--- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -71,7 +71,6 @@
 import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
 import java.time.temporal.ChronoUnit;
-import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
@@ -222,8 +221,10 @@
                         "Can't get TelephonyManager for subId %d", mSubId));
             }
 
-            subscriberId = Objects.requireNonNull(tele.getSubscriberId(),
-                    "Null subscriber Id for subId " + mSubId);
+            subscriberId = tele.getSubscriberId();
+            if (subscriberId == null) {
+                throw new IllegalStateException("Null subscriber Id for subId " + mSubId);
+            }
             mNetworkTemplate = new NetworkTemplate.Builder(NetworkTemplate.MATCH_MOBILE)
                     .setSubscriberIds(Set.of(subscriberId))
                     .setMeteredness(NetworkStats.METERED_YES)
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index c25e608..589b8f1 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -162,6 +162,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.database.ContentObserver;
@@ -4539,9 +4540,7 @@
         }
         try {
             // update rules for all installed applications
-            final PackageManager pm = mContext.getPackageManager();
             final List<UserInfo> users;
-            final List<ApplicationInfo> apps;
 
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "list-users");
             try {
@@ -4549,26 +4548,30 @@
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
             }
-            Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "list-uids");
+            Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "iterate-uids");
             try {
-                apps = pm.getInstalledApplications(
-                        PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DISABLED_COMPONENTS
-                                | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+                final PackageManagerInternal packageManagerInternal = LocalServices.getService(
+                        PackageManagerInternal.class);
+                final int usersSize = users.size();
+                for (int i = 0; i < usersSize; ++i) {
+                    final int userId = users.get(i).id;
+                    final SparseBooleanArray sharedAppIdsHandled = new SparseBooleanArray();
+                    packageManagerInternal.forEachInstalledPackage(androidPackage -> {
+                        final int appId = androidPackage.getUid();
+                        if (androidPackage.getSharedUserId() != null) {
+                            if (sharedAppIdsHandled.indexOfKey(appId) < 0) {
+                                sharedAppIdsHandled.put(appId, true);
+                            } else {
+                                return;
+                            }
+                        }
+                        final int uid = UserHandle.getUid(userId, appId);
+                        consumer.accept(uid);
+                    }, userId);
+                }
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
             }
-
-            final int usersSize = users.size();
-            final int appsSize = apps.size();
-            for (int i = 0; i < usersSize; i++) {
-                final UserInfo user = users.get(i);
-                for (int j = 0; j < appsSize; j++) {
-                    final ApplicationInfo app = apps.get(j);
-                    final int uid = UserHandle.getUid(user.id, app.uid);
-                    consumer.accept(uid);
-                }
-            }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
         }
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 76d3d23..a91c55f 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -520,7 +520,7 @@
         @Override
         public List<ActiveApexInfo> getActiveApexInfos() {
             final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
-                    Trace.TRACE_TAG_APEX_MANAGER);
+                    Trace.TRACE_TAG_PACKAGE_MANAGER);
             synchronized (mLock) {
                 if (mActiveApexInfosCache == null) {
                     t.traceBegin("getActiveApexInfos_noCache");
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 54a10395..4abfd34 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -1429,7 +1429,8 @@
         if (userId == UserHandle.USER_SYSTEM) {
             return resolveInfos;
         }
-        for (int i = resolveInfos.size() - 1; i >= 0; i--) {
+
+        for (int i = CollectionUtils.size(resolveInfos) - 1; i >= 0; i--) {
             ResolveInfo info = resolveInfos.get(i);
             if ((info.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
                 resolveInfos.remove(i);
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 45c5116..fff6662 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -284,6 +284,17 @@
     }
 
     /**
+     * Sets in Installd that it is first boot after data wipe
+     */
+    public void setFirstBoot() throws InstallerException {
+        try {
+            mInstalld.setFirstBoot();
+        } catch (RemoteException e) {
+            throw InstallerException.from(e);
+        }
+    }
+
+    /**
      * Class that collects multiple {@code installd} operations together in an
      * attempt to more efficiently execute them in bulk.
      * <p>
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c7a101e..4c7243d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1900,6 +1900,16 @@
                     /* excludePreCreated= */ false));
             t.traceEnd();
 
+            if (mFirstBoot) {
+                t.traceBegin("setFirstBoot: ");
+                try {
+                    mInstaller.setFirstBoot();
+                } catch (InstallerException e) {
+                    Slog.w(TAG, "Could not set First Boot: ", e);
+                }
+                t.traceEnd();
+            }
+
             mPermissionManager.readLegacyPermissionsTEMP(mSettings.mPermissions);
             mPermissionManager.readLegacyPermissionStateTEMP();
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 15753cd2..581e4e2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -188,6 +188,8 @@
                     return runDump();
                 case "list":
                     return runList();
+                case "gc":
+                    return runGc();
                 case "resolve-activity":
                     return runResolveActivity();
                 case "query-activities":
@@ -687,6 +689,13 @@
         return -1;
     }
 
+    private int runGc() throws RemoteException {
+        Runtime.getRuntime().gc();
+        final PrintWriter pw = getOutPrintWriter();
+        pw.println("Ok");
+        return 0;
+    }
+
     private int runListFeatures() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
         final List<FeatureInfo> list = mInterface.getSystemAvailableFeatures().getList();
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 7a73412..b2b59f1 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -26,7 +26,6 @@
 import android.app.appsearch.BatchResultCallback;
 import android.app.appsearch.GenericDocument;
 import android.app.appsearch.GetByDocumentIdRequest;
-import android.app.appsearch.PackageIdentifier;
 import android.app.appsearch.PutDocumentsRequest;
 import android.app.appsearch.RemoveByDocumentIdRequest;
 import android.app.appsearch.ReportUsageRequest;
@@ -184,11 +183,6 @@
     private final ArrayList<ShareTargetInfo> mShareTargets = new ArrayList<>(0);
 
     /**
-     * All external packages that have gained access to the shortcuts from this package
-     */
-    private final Map<String, PackageIdentifier> mPackageIdentifiers = new ArrayMap<>(0);
-
-    /**
      * # of times the package has called rate-limited APIs.
      */
     private int mApiCallCount;
@@ -2316,14 +2310,15 @@
         }
         SetSchemaRequest.Builder schemaBuilder = new SetSchemaRequest.Builder()
                 .addSchemas(AppSearchShortcutPerson.SCHEMA, AppSearchShortcutInfo.SCHEMA)
-                .setForceOverride(true);
-        for (PackageIdentifier pi : mPackageIdentifiers.values()) {
-            schemaBuilder = schemaBuilder
-                    .setSchemaTypeVisibilityForPackage(
-                            AppSearchShortcutPerson.SCHEMA_TYPE, true, pi)
-                    .setSchemaTypeVisibilityForPackage(
-                            AppSearchShortcutInfo.SCHEMA_TYPE, true, pi);
-        }
+                .setForceOverride(true)
+                .addRequiredPermissionsForSchemaTypeVisibility(AppSearchShortcutInfo.SCHEMA_TYPE,
+                        Collections.singleton(SetSchemaRequest.READ_HOME_APP_SEARCH_DATA))
+                .addRequiredPermissionsForSchemaTypeVisibility(AppSearchShortcutInfo.SCHEMA_TYPE,
+                        Collections.singleton(SetSchemaRequest.READ_ASSISTANT_APP_SEARCH_DATA))
+                .addRequiredPermissionsForSchemaTypeVisibility(AppSearchShortcutPerson.SCHEMA_TYPE,
+                        Collections.singleton(SetSchemaRequest.READ_HOME_APP_SEARCH_DATA))
+                .addRequiredPermissionsForSchemaTypeVisibility(AppSearchShortcutPerson.SCHEMA_TYPE,
+                        Collections.singleton(SetSchemaRequest.READ_ASSISTANT_APP_SEARCH_DATA));
         final AndroidFuture<AppSearchSession> future = new AndroidFuture<>();
         session.setSchema(
                 schemaBuilder.build(), mExecutor, mShortcutUser.mExecutor, result -> {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 5ec406b..d340561 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1387,7 +1387,7 @@
      */
     @Override
     public boolean isUserOfType(@UserIdInt int userId, String userType) {
-        checkManageUsersPermission("check user type");
+        checkQueryOrCreateUsersPermission("check user type");
         return userType != null && userType.equals(getUserTypeNoChecks(userId));
     }
 
@@ -1643,7 +1643,7 @@
         if (!hasQueryOrCreateUsersPermission()
                 && !hasPermissionGranted(
                         android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED, callingUid)) {
-            throw new SecurityException("You need MANAGE_USERS or CREATE_USERS or "
+            throw new SecurityException("You need MANAGE_USERS, CREATE_USERS, QUERY_USERS, or "
                     + "GET_ACCOUNTS_PRIVILEGED permissions to: get user name");
         }
         final int userId = UserHandle.getUserId(callingUid);
@@ -5064,9 +5064,13 @@
 
     @Override
     public boolean isUserNameSet(@UserIdInt int userId) {
-        if (!hasManageUsersOrPermission(android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED)) {
-            throw new SecurityException("You need MANAGE_USERS or GET_ACCOUNTS_PRIVILEGED "
-                    + "permissions to: get whether user name is set");
+        final int callingUid = Binder.getCallingUid();
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        if (!hasQueryOrCreateUsersPermission()
+                && !(callingUserId == userId && hasPermissionGranted(
+                android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED, callingUid))) {
+            throw new SecurityException("You need MANAGE_USERS, CREATE_USERS, QUERY_USERS, or "
+                    + "GET_ACCOUNTS_PRIVILEGED permissions to: get whether user name is set");
         }
         synchronized (mUsersLock) {
             final UserInfo userInfo = getUserInfoLU(userId);
diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
index 6060233..881f870 100644
--- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
+++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
@@ -317,9 +317,21 @@
             synchronized (mInnerLock) {
                 mIsFinished = true;
                 cancelAlarmLocked();
-                mActivityManager.removeOnUidImportanceListener(mStartTimerListener);
-                mActivityManager.removeOnUidImportanceListener(mSessionKillableListener);
-                mActivityManager.removeOnUidImportanceListener(mGoneListener);
+                try {
+                    mActivityManager.removeOnUidImportanceListener(mStartTimerListener);
+                } catch (IllegalArgumentException e) {
+                    Log.e(LOG_TAG, "Could not remove start timer listener", e);
+                }
+                try {
+                    mActivityManager.removeOnUidImportanceListener(mSessionKillableListener);
+                } catch (IllegalArgumentException e) {
+                    Log.e(LOG_TAG, "Could not remove session killable listener", e);
+                }
+                try {
+                    mActivityManager.removeOnUidImportanceListener(mGoneListener);
+                } catch (IllegalArgumentException e) {
+                    Log.e(LOG_TAG, "Could not remove gone listener", e);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 1ac373f..5aa81ac 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5446,8 +5446,7 @@
         VibrationAttributes attrs = getVibrationAttributes(effectId);
         if (always) {
             attrs = new VibrationAttributes.Builder(attrs)
-                    .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF,
-                            VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
+                    .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
                     .build();
         }
         mVibrator.vibrate(uid, packageName, effect, reason, attrs);
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index f02f9f9..8ecc51b 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -47,12 +47,16 @@
         FINISHED,
         FINISHED_UNEXPECTED,  // Didn't terminate in the usual way.
         FORWARDED_TO_INPUT_DEVICES,
-        CANCELLED,
+        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,
         IGNORED_APP_OPS,
         IGNORED_BACKGROUND,
         IGNORED_UNKNOWN_VIBRATION,
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 77da751..e9535e0 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -121,6 +121,11 @@
                     USAGE_PHYSICAL_EMULATION,
                     USAGE_HARDWARE_FEEDBACK));
 
+    private static final IntentFilter USER_SWITCHED_INTENT_FILTER =
+            new IntentFilter(Intent.ACTION_USER_SWITCHED);
+    private static final IntentFilter INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER =
+            new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
+
     /** Listener for changes on vibration settings. */
     interface OnVibratorSettingsChanged {
         /** Callback triggered when any of the vibrator settings change. */
@@ -130,11 +135,11 @@
     private final Object mLock = new Object();
     private final Context mContext;
     private final String mSystemUiPackage;
-    private final SettingsObserver mSettingObserver;
+    private final SettingsContentObserver mSettingObserver;
     @VisibleForTesting
     final UidObserver mUidObserver;
     @VisibleForTesting
-    final UserObserver mUserReceiver;
+    final SettingsBroadcastReceiver mSettingChangeReceiver;
 
     @GuardedBy("mLock")
     private final List<OnVibratorSettingsChanged> mListeners = new ArrayList<>();
@@ -154,6 +159,8 @@
     private boolean mBatterySaverMode;
     @GuardedBy("mLock")
     private boolean mVibrateOn;
+    @GuardedBy("mLock")
+    private int mRingerMode;
 
     VibrationSettings(Context context, Handler handler) {
         this(context, handler, new VibrationConfig(context.getResources()));
@@ -163,9 +170,9 @@
     VibrationSettings(Context context, Handler handler, VibrationConfig config) {
         mContext = context;
         mVibrationConfig = config;
-        mSettingObserver = new SettingsObserver(handler);
+        mSettingObserver = new SettingsContentObserver(handler);
         mUidObserver = new UidObserver();
-        mUserReceiver = new UserObserver();
+        mSettingChangeReceiver = new SettingsBroadcastReceiver();
 
         mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
                 .getSystemUiServiceComponent().getPackageName();
@@ -188,12 +195,13 @@
                 VibrationEffect.get(VibrationEffect.EFFECT_TICK, false));
 
         // Update with current values from settings.
-        updateSettings();
+        update();
     }
 
     public void onSystemReady() {
         synchronized (mLock) {
             mAudioManager = mContext.getSystemService(AudioManager.class);
+            mRingerMode = mAudioManager.getRingerModeInternal();
         }
         try {
             ActivityManager.getService().registerUidObserver(mUidObserver,
@@ -224,8 +232,8 @@
                     }
                 });
 
-        IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
-        mContext.registerReceiver(mUserReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
+        registerSettingsChangeReceiver(USER_SWITCHED_INTENT_FILTER);
+        registerSettingsChangeReceiver(INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER);
 
         // Listen to all settings that might affect the result of Vibrator.getVibrationIntensity.
         registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES));
@@ -248,7 +256,7 @@
                 Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY));
 
         // Update with newly loaded services.
-        updateSettings();
+        update();
     }
 
     /**
@@ -396,16 +404,17 @@
             // Only ringtone and notification vibrations are disabled when phone is on silent mode.
             return true;
         }
-        // If audio manager was not loaded yet then assume most restrictive mode.
-        int ringerMode = (mAudioManager == null)
-                ? AudioManager.RINGER_MODE_SILENT
-                : mAudioManager.getRingerModeInternal();
-        return ringerMode != AudioManager.RINGER_MODE_SILENT;
+        return mRingerMode != AudioManager.RINGER_MODE_SILENT;
     }
 
-    /** Updates all vibration settings and triggers registered listeners. */
-    @VisibleForTesting
-    void updateSettings() {
+    /** Update all cached settings and triggers registered listeners. */
+    void update() {
+        updateSettings();
+        updateRingerMode();
+        notifyListeners();
+    }
+
+    private void updateSettings() {
         synchronized (mLock) {
             mVibrateInputDevices = loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0;
             mVibrateOn = loadSystemSetting(Settings.System.VIBRATE_ON, 1) > 0;
@@ -435,7 +444,6 @@
                     loadSystemSetting(Settings.System.RING_VIBRATION_INTENSITY, -1),
                     getDefaultIntensity(USAGE_RINGTONE));
 
-
             mCurrentVibrationIntensities.clear();
             mCurrentVibrationIntensities.put(USAGE_ALARM, alarmIntensity);
             mCurrentVibrationIntensities.put(USAGE_NOTIFICATION, notificationIntensity);
@@ -469,7 +477,16 @@
             // A11y is not disabled by any haptic feedback setting.
             mCurrentVibrationIntensities.put(USAGE_ACCESSIBILITY, positiveHapticFeedbackIntensity);
         }
-        notifyListeners();
+    }
+
+    private void updateRingerMode() {
+        synchronized (mLock) {
+            // If audio manager was not loaded yet then assume most restrictive mode.
+            // This will be loaded again as soon as the audio manager is loaded in onSystemReady.
+            mRingerMode = (mAudioManager == null)
+                    ? AudioManager.RINGER_MODE_SILENT
+                    : mAudioManager.getRingerModeInternal();
+        }
     }
 
     @Override
@@ -586,6 +603,11 @@
                 UserHandle.USER_ALL);
     }
 
+    private void registerSettingsChangeReceiver(IntentFilter intentFilter) {
+        mContext.registerReceiver(mSettingChangeReceiver, intentFilter,
+                Context.RECEIVER_NOT_EXPORTED);
+    }
+
     @Nullable
     private VibrationEffect createEffectFromResource(int resId) {
         long[] timings = getLongIntArray(mContext.getResources(), resId);
@@ -616,24 +638,33 @@
     }
 
     /** Implementation of {@link ContentObserver} to be registered to a setting {@link Uri}. */
-    private final class SettingsObserver extends ContentObserver {
-        SettingsObserver(Handler handler) {
+    private final class SettingsContentObserver extends ContentObserver {
+        SettingsContentObserver(Handler handler) {
             super(handler);
         }
 
         @Override
         public void onChange(boolean selfChange) {
             updateSettings();
+            notifyListeners();
         }
     }
 
-    /** Implementation of {@link BroadcastReceiver} to update settings on current user change. */
+    /**
+     * Implementation of {@link BroadcastReceiver} to update settings on current user or ringer
+     * mode change.
+     */
     @VisibleForTesting
-    final class UserObserver extends BroadcastReceiver {
+    final class SettingsBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
-                updateSettings();
+            String action = intent.getAction();
+            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+                // Reload all settings, as they are user-based.
+                update();
+            } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
+                updateRingerMode();
+                notifyListeners();
             }
         }
     }
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index e12426b..e3d8067 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -79,12 +79,14 @@
     private final Object mLock = new Object();
     @GuardedBy("mLock")
     private final IntArray mSignalVibratorsComplete;
+    @Nullable
     @GuardedBy("mLock")
-    private boolean mSignalCancel = false;
+    private Vibration.Status mSignalCancelStatus = null;
     @GuardedBy("mLock")
     private boolean mSignalCancelImmediate = false;
 
-    private boolean mCancelled = false;
+    @Nullable
+    private Vibration.Status mCancelStatus = null;
     private boolean mCancelledImmediately = false;  // hard stop
     private int mPendingVibrateSteps;
     private int mRemainingStartSequentialEffectSteps;
@@ -185,8 +187,8 @@
             expectIsVibrationThread(true);
         }
 
-        if (mCancelled) {
-            return Vibration.Status.CANCELLED;
+        if (mCancelStatus != null) {
+            return mCancelStatus;
         }
         if (mPendingVibrateSteps > 0
                 || mRemainingStartSequentialEffectSteps > 0) {
@@ -303,7 +305,7 @@
         if (DEBUG) {
             Slog.d(TAG, "Binder died, cancelling vibration...");
         }
-        notifyCancelled(/* immediate= */ false);
+        notifyCancelled(Vibration.Status.CANCELLED_BINDER_DIED, /* immediate= */ false);
     }
 
     /**
@@ -312,22 +314,41 @@
      *
      * @param immediate indicates whether cancellation should abort urgently and skip cleanup steps.
      */
-    public void notifyCancelled(boolean immediate) {
+    public void notifyCancelled(@NonNull Vibration.Status cancelStatus, boolean immediate) {
         if (Build.IS_DEBUGGABLE) {
             expectIsVibrationThread(false);
         }
+        if (DEBUG) {
+            Slog.d(TAG, "Vibration cancel requested with status=" + cancelStatus
+                    + ", immediate=" + immediate);
+        }
+        if ((cancelStatus == null) || !cancelStatus.name().startsWith("CANCEL")) {
+            Slog.w(TAG, "Vibration cancel requested with bad status=" + cancelStatus
+                    + ", using CANCELLED_UNKNOWN_REASON to ensure cancellation.");
+            cancelStatus = Vibration.Status.CANCELLED_BY_UNKNOWN_REASON;
+        }
         synchronized (mLock) {
-            if (immediate && mSignalCancelImmediate || mSignalCancel) {
-                // Nothing to update: already cancelled previously.
+            if (immediate && mSignalCancelImmediate || (mSignalCancelStatus != null)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Vibration cancel request ignored as the vibration "
+                            + mVibration.id + "is already being cancelled with status="
+                            + mSignalCancelStatus + ", immediate=" + mSignalCancelImmediate);
+                }
                 return;
             }
             mSignalCancelImmediate |= immediate;
-            mSignalCancel = true;
+            if (mSignalCancelStatus == null) {
+                mSignalCancelStatus = cancelStatus;
+            } 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 "
+                            + mSignalCancelImmediate);
+                }
+            }
             mLock.notify();
         }
-        if (DEBUG) {
-            Slog.d(TAG, "Vibration cancel requested, immediate=" + immediate);
-        }
     }
 
     /**
@@ -380,7 +401,7 @@
         if (Build.IS_DEBUGGABLE) {
             expectIsVibrationThread(true);  // Reads VibrationThread variables as well as signals.
         }
-        return (mSignalCancel && !mCancelled)
+        return (mSignalCancelStatus != mCancelStatus)
             || (mSignalCancelImmediate && !mCancelledImmediately)
             || (mSignalVibratorsComplete.size() > 0);
     }
@@ -395,7 +416,7 @@
         }
 
         int[] vibratorsToProcess = null;
-        boolean doCancel = false;
+        Vibration.Status doCancelStatus = null;
         boolean doCancelImmediate = false;
         // Collect signals to process, but don't keep the lock while processing them.
         synchronized (mLock) {
@@ -405,9 +426,10 @@
                 }
                 // This should only happen once.
                 doCancelImmediate = true;
+                doCancelStatus = mSignalCancelStatus;
             }
-            if (mSignalCancel && !mCancelled) {
-                doCancel = true;
+            if (mSignalCancelStatus != mCancelStatus) {
+                doCancelStatus = mSignalCancelStatus;
             }
             if (!doCancelImmediate && mSignalVibratorsComplete.size() > 0) {
                 // Swap out the queue of completions to process.
@@ -421,11 +443,11 @@
         // completion signals that were collected in this call, but we won't process them
         // anyway as all steps are cancelled.
         if (doCancelImmediate) {
-            processCancelImmediately();
+            processCancelImmediately(doCancelStatus);
             return;
         }
-        if (doCancel) {
-            processCancel();
+        if (doCancelStatus != null) {
+            processCancel(doCancelStatus);
         }
         if (vibratorsToProcess != null) {
             processVibratorsComplete(vibratorsToProcess);
@@ -438,12 +460,12 @@
      * <p>This will remove all steps and replace them with respective results of
      * {@link Step#cancel()}.
      */
-    public void processCancel() {
+    public void processCancel(Vibration.Status cancelStatus) {
         if (Build.IS_DEBUGGABLE) {
             expectIsVibrationThread(true);
         }
 
-        mCancelled = true;
+        mCancelStatus = cancelStatus;
         // 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<>();
@@ -461,13 +483,13 @@
      *
      * <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order.
      */
-    public void processCancelImmediately() {
+    public void processCancelImmediately(Vibration.Status cancelStatus) {
         if (Build.IS_DEBUGGABLE) {
             expectIsVibrationThread(true);
         }
 
         mCancelledImmediately = true;
-        mCancelled = true;
+        mCancelStatus = cancelStatus;
         Step step;
         while ((step = pollNext()) != null) {
             step.cancelImmediately();
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 1260e5d..f749773 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -162,10 +162,11 @@
                     // 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);
+                        clearNextVibrationLocked(Vibration.Status.CANCELLED_BY_SCREEN_OFF);
                     }
                     if (shouldCancelOnScreenOffLocked(mCurrentVibration)) {
-                        mCurrentVibration.notifyCancelled(/* immediate= */ false);
+                        mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_SCREEN_OFF,
+                                /* immediate= */ false);
                     }
                 }
             }
@@ -401,6 +402,12 @@
                     uid, opPkg, reason);
             fillVibrationFallbacks(vib, effect);
 
+            if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
+                // Force update of user settings before checking if this vibration effect should
+                // be ignored or scaled.
+                mVibrationSettings.update();
+            }
+
             synchronized (mLock) {
                 if (DEBUG) {
                     Slog.d(TAG, "Starting vibrate for vibration  " + vib.id);
@@ -420,7 +427,8 @@
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     if (mCurrentVibration != null) {
-                        mCurrentVibration.notifyCancelled(/* immediate= */ false);
+                        mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED,
+                                /* immediate= */ false);
                     }
                     Vibration.Status status = startVibrationLocked(vib);
                     if (status != Vibration.Status.RUNNING) {
@@ -453,19 +461,20 @@
                     if (mNextVibration != null
                             && shouldCancelVibration(mNextVibration.getVibration(),
                             usageFilter, token)) {
-                        clearNextVibrationLocked(Vibration.Status.CANCELLED);
+                        clearNextVibrationLocked(Vibration.Status.CANCELLED_BY_USER);
                     }
                     if (mCurrentVibration != null
                             && shouldCancelVibration(mCurrentVibration.getVibration(),
                             usageFilter, token)) {
-                        mCurrentVibration.notifyCancelled(/* immediate= */false);
+                        mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_USER,
+                                /* immediate= */false);
                     }
                     if (mCurrentExternalVibration != null
                             && shouldCancelVibration(
                             mCurrentExternalVibration.externalVibration.getVibrationAttributes(),
                             usageFilter)) {
                         mCurrentExternalVibration.externalVibration.mute();
-                        endExternalVibrateLocked(Vibration.Status.CANCELLED,
+                        endExternalVibrateLocked(Vibration.Status.CANCELLED_BY_USER,
                                 /* continueExternalControl= */ false);
                     }
                 } finally {
@@ -594,7 +603,8 @@
                     Slog.d(TAG, "Canceling vibration because settings changed: "
                             + (inputDevicesChanged ? "input devices changed" : ignoreStatus));
                 }
-                mCurrentVibration.notifyCancelled(/* immediate= */ false);
+                mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE,
+                        /* immediate= */ false);
             }
         }
     }
@@ -1313,7 +1323,7 @@
                     if (DEBUG) {
                         Slog.d(TAG, "External vibration finished because binder died");
                     }
-                    endExternalVibrateLocked(Vibration.Status.CANCELLED,
+                    endExternalVibrateLocked(Vibration.Status.CANCELLED_BINDER_DIED,
                             /* continueExternalControl= */ false);
                 }
             }
@@ -1506,12 +1516,20 @@
                 return IExternalVibratorService.SCALE_MUTE;
             }
 
+            VibrationAttributes attrs = fixupVibrationAttributes(vib.getVibrationAttributes(),
+                    /* effect= */ null);
+            if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
+                // Force update of user settings before checking if this vibration effect should
+                // be ignored or scaled.
+                mVibrationSettings.update();
+            }
+
             boolean alreadyUnderExternalControl = false;
             boolean waitForCompletion = false;
             int scale;
             synchronized (mLock) {
                 Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
-                        vib.getUid(), vib.getPackage(), vib.getVibrationAttributes());
+                        vib.getUid(), vib.getPackage(), attrs);
                 if (ignoreStatus != null) {
                     ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
                     vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
@@ -1529,7 +1547,8 @@
                     // vibration that may be playing and ready the vibrator for external control.
                     if (mCurrentVibration != null) {
                         clearNextVibrationLocked(Vibration.Status.IGNORED_FOR_EXTERNAL);
-                        mCurrentVibration.notifyCancelled(/* immediate= */ true);
+                        mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED,
+                                /* immediate= */ true);
                         waitForCompletion = true;
                     }
                 } else {
@@ -1543,13 +1562,13 @@
                     // would need to mute the old one still if it came from a different controller.
                     alreadyUnderExternalControl = true;
                     mCurrentExternalVibration.externalVibration.mute();
-                    endExternalVibrateLocked(Vibration.Status.CANCELLED,
+                    endExternalVibrateLocked(Vibration.Status.CANCELLED_SUPERSEDED,
                             /* continueExternalControl= */ true);
                 }
                 mCurrentExternalVibration = new ExternalVibrationHolder(vib);
                 vib.linkToDeath(mCurrentExternalVibration);
                 mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale(
-                        vib.getVibrationAttributes().getUsage());
+                        attrs.getUsage());
                 scale = mCurrentExternalVibration.scale;
             }
 
@@ -1908,7 +1927,7 @@
             final int flags =
                     commonOptions.force ? VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY : 0;
             return new VibrationAttributes.Builder()
-                    .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED)
+                    .setFlags(flags)
                     // Used to apply Settings.System.HAPTIC_FEEDBACK_INTENSITY to scale effects.
                     .setUsage(VibrationAttributes.USAGE_TOUCH)
                     .build();
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index 11ddac6..799d59c 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -811,8 +811,8 @@
                 RectF windowFrame = TEMP_RECTF;
                 windowFrame.set(rect);
 
-                inverseMatrix.mapRect(windowFrame);
                 displayMatrix.mapRect(windowFrame);
+                inverseMatrix.mapRect(windowFrame);
                 // Union all rects.
                 outRegion.union(new Rect((int) windowFrame.left, (int) windowFrame.top,
                         (int) windowFrame.right, (int) windowFrame.bottom));
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 4bef126..2c9372e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -116,6 +116,7 @@
 import static android.view.WindowManager.TRANSIT_OLD_UNSET;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
@@ -216,7 +217,6 @@
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowContainerChildProto.ACTIVITY;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
@@ -7237,11 +7237,9 @@
 
         getDisplayContent().computeImeTargetIfNeeded(this);
 
-        if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + this
-                + ": reportedVisible=" + reportedVisible
-                + " okToDisplay=" + okToDisplay()
-                + " okToAnimate=" + okToAnimate()
-                + " startingDisplayed=" + startingDisplayed);
+        ProtoLog.v(WM_DEBUG_ANIM, "Animation done in %s"
+                + ": reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b",
+                this, reportedVisible, okToDisplay(), okToAnimate(), startingDisplayed);
 
         // clean up thumbnail window
         if (mThumbnail != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index 8622bd3..ce49a86 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -18,20 +18,11 @@
 
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
-import android.os.IBinder;
 import android.os.InputConfig;
-import android.os.InputConstants;
-import android.os.Looper;
 import android.os.Process;
-import android.util.Slog;
-import android.view.InputChannel;
-import android.view.InputEvent;
-import android.view.InputEventReceiver;
 import android.view.InputWindowHandle;
-import android.view.MotionEvent;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
-import android.widget.Toast;
 
 /**
  * Creates a InputWindowHandle that catches all touches that would otherwise pass through an
@@ -45,20 +36,12 @@
     @ChangeId
     static final long ENABLE_TOUCH_OPAQUE_ACTIVITIES = 194480991L;
 
-    private static final String TAG = "ActivityRecordInputSink";
-    private static final int NUMBER_OF_TOUCHES_TO_DISABLE = 3;
-    private static final long TOAST_COOL_DOWN_MILLIS = 3000L;
-
     private final ActivityRecord mActivityRecord;
     private final boolean mIsCompatEnabled;
     private final String mName;
 
-    // Hold on to InputEventReceiver to prevent it from getting GCd.
-    private InputEventReceiver mInputEventReceiver;
     private InputWindowHandleWrapper mInputWindowHandleWrapper;
     private SurfaceControl mSurfaceControl;
-    private int mRapidTouchCount = 0;
-    private IBinder mToken;
     private boolean mDisabled = false;
 
     ActivityRecordInputSink(ActivityRecord activityRecord) {
@@ -66,7 +49,7 @@
         mIsCompatEnabled = CompatChanges.isChangeEnabled(ENABLE_TOUCH_OPAQUE_ACTIVITIES,
                 mActivityRecord.getUid());
         mName = Integer.toHexString(System.identityHashCode(this)) + " ActivityRecordInputSink "
-                + mActivityRecord.mActivityComponent.getShortClassName();
+                + mActivityRecord.mActivityComponent.flattenToShortString();
     }
 
     public void applyChangesToSurfaceIfChanged(SurfaceControl.Transaction transaction) {
@@ -93,16 +76,13 @@
     private InputWindowHandleWrapper getInputWindowHandleWrapper() {
         if (mInputWindowHandleWrapper == null) {
             mInputWindowHandleWrapper = new InputWindowHandleWrapper(createInputWindowHandle());
-            InputChannel inputChannel =
-                    mActivityRecord.mWmService.mInputManager.createInputChannel(mName);
-            mToken = inputChannel.getToken();
-            mInputEventReceiver = createInputEventReceiver(inputChannel);
         }
         if (mDisabled || !mIsCompatEnabled || mActivityRecord.isInTransition()) {
             // TODO(b/208662670): Investigate if we can have feature active during animations.
-            mInputWindowHandleWrapper.setToken(null);
+            mInputWindowHandleWrapper.setInputConfigMasked(InputConfig.NOT_TOUCHABLE,
+                    InputConfig.NOT_TOUCHABLE);
         } else {
-            mInputWindowHandleWrapper.setToken(mToken);
+            mInputWindowHandleWrapper.setInputConfigMasked(0, InputConfig.NOT_TOUCHABLE);
         }
         return mInputWindowHandleWrapper;
     }
@@ -115,58 +95,8 @@
         inputWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
         inputWindowHandle.ownerUid = Process.myUid();
         inputWindowHandle.ownerPid = Process.myPid();
-        inputWindowHandle.dispatchingTimeoutMillis =
-                InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
-        inputWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE;
+        inputWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE | InputConfig.NO_INPUT_CHANNEL;
         return inputWindowHandle;
     }
 
-    private InputEventReceiver createInputEventReceiver(InputChannel inputChannel) {
-        return new SinkInputEventReceiver(inputChannel,
-                mActivityRecord.mAtmService.mUiHandler.getLooper());
-    }
-
-    private void showAsToastAndLog(String message) {
-        Toast.makeText(mActivityRecord.mAtmService.mUiContext, message,
-                Toast.LENGTH_LONG).show();
-        Slog.wtf(TAG, message + " " + mActivityRecord.mActivityComponent);
-    }
-
-    private class SinkInputEventReceiver extends InputEventReceiver {
-        private long mLastToast = 0;
-
-        SinkInputEventReceiver(InputChannel inputChannel, Looper looper) {
-            super(inputChannel, looper);
-        }
-
-        public void onInputEvent(InputEvent event) {
-            if (!(event instanceof MotionEvent)) {
-                Slog.wtf(TAG,
-                        "Received InputEvent that was not a MotionEvent");
-                finishInputEvent(event, true);
-                return;
-            }
-            MotionEvent motionEvent = (MotionEvent) event;
-            if (motionEvent.getAction() != MotionEvent.ACTION_DOWN) {
-                finishInputEvent(event, true);
-                return;
-            }
-
-            if (event.getEventTime() - mLastToast > TOAST_COOL_DOWN_MILLIS) {
-                String message = "go/activity-touch-opaque - "
-                        + mActivityRecord.mActivityComponent.getPackageName()
-                        + " blocked the touch!";
-                showAsToastAndLog(message);
-                mLastToast = event.getEventTime();
-                mRapidTouchCount = 1;
-            } else if (++mRapidTouchCount >= NUMBER_OF_TOUCHES_TO_DISABLE && !mDisabled) {
-                // Disable touch blocking until Activity Record is recreated.
-                String message = "Disabled go/activity-touch-opaque - "
-                        + mActivityRecord.mActivityComponent.getPackageName();
-                showAsToastAndLog(message);
-                mDisabled = true;
-            }
-            finishInputEvent(event, true);
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 7abcc4b..7b60ea7 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -135,6 +135,7 @@
 
 import java.io.PrintWriter;
 import java.text.DateFormat;
+import java.util.ArrayList;
 import java.util.Date;
 
 /**
@@ -2010,6 +2011,27 @@
             return START_PERMISSION_DENIED;
         }
 
+        // Do not start the activity if target display's DWPC does not allow it.
+        // We can't return fatal error code here because it will crash the caller of
+        // startActivity() if they don't catch the exception. We don't expect 3P apps to make
+        // changes.
+        if (mPreferredTaskDisplayArea != null) {
+            final DisplayContent displayContent = mRootWindowContainer.getDisplayContentOrCreate(
+                    mPreferredTaskDisplayArea.getDisplayId());
+            if (displayContent != null && displayContent.mDwpcHelper.hasController()) {
+                final ArrayList<ActivityInfo> activities = new ArrayList<>();
+                activities.add(r.info);
+                final int targetWindowingMode = (targetTask != null)
+                        ? targetTask.getWindowingMode() : displayContent.getWindowingMode();
+                if (!displayContent.mDwpcHelper
+                        .canContainActivities(activities, targetWindowingMode)) {
+                    Slog.w(TAG, "Abort to launch " + r.info.getComponentName()
+                            + " on display area " + mPreferredTaskDisplayArea);
+                    return START_ABORTED;
+                }
+            }
+        }
+
         return START_SUCCESS;
     }
 
@@ -2687,11 +2709,8 @@
                 // launched into the same root task.
                 mTargetRootTask = Task.fromWindowContainerToken(mSourceRecord.mLaunchRootTask);
             } else {
-                final Task rootTask =
-                        getOrCreateRootTask(mStartActivity, mLaunchFlags, intentTask, mOptions);
-                // TODO(b/184806710): #getOrCreateRootTask should never return null?
-                mTargetRootTask =
-                        rootTask != null ? rootTask : intentActivity.getRootTask();
+                mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, intentTask,
+                        mOptions);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 8b6262f..ad6f354 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5303,14 +5303,19 @@
 
     /**
      * Returns {@code true} if the process represented by the pid passed as argument is
-     * instrumented.
+     * instrumented and the instrumentation source was granted with the permission also
+     * passed as argument.
      */
-    boolean isInstrumenting(int pid) {
+    boolean instrumentationSourceHasPermission(int pid, String permission) {
         final WindowProcessController process;
         synchronized (mGlobalLock) {
             process = mProcessMap.getProcess(pid);
         }
-        return process != null ? process.isInstrumenting() : false;
+        if (process == null || !process.isInstrumenting()) {
+            return false;
+        }
+        final int sourceUid = process.getInstrumentationSourceUid();
+        return checkPermission(permission, -1, sourceUid) == PackageManager.PERMISSION_GRANTED;
     }
 
     final class H extends Handler {
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 05efb29..5c09f09 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -81,11 +81,11 @@
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
 import static com.android.server.wm.AppTransitionProto.APP_TRANSITION_STATE;
 import static com.android.server.wm.AppTransitionProto.LAST_USED_APP_TRANSITION;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
@@ -130,6 +130,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.TransitionAnimation;
+import com.android.internal.protolog.ProtoLogImpl;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.DumpUtils.Dump;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -237,7 +238,8 @@
         mService = service;
         mHandler = new Handler(service.mH.getLooper());
         mDisplayContent = displayContent;
-        mTransitionAnimation = new TransitionAnimation(context, DEBUG_ANIM, TAG);
+        mTransitionAnimation = new TransitionAnimation(
+                context, ProtoLogImpl.isEnabled(WM_DEBUG_ANIM), TAG);
 
         mGridLayoutRecentsEnabled = SystemProperties.getBoolean("ro.recents.grid", false);
 
@@ -1305,6 +1307,8 @@
             pw.print(Integer.toHexString(mNextAppTransitionEnter));
             pw.print(" mNextAppTransitionExit=0x");
             pw.println(Integer.toHexString(mNextAppTransitionExit));
+            pw.print(" mNextAppTransitionBackgroundColor=0x");
+            pw.println(Integer.toHexString(mNextAppTransitionBackgroundColor));
         }
         switch (mNextAppTransitionType) {
             case NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE:
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f431457..61cd221 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3973,7 +3973,7 @@
             boolean nonAppImeTargetAnimatingExit = mImeLayeringTarget.mAnimatingExit
                     && mImeLayeringTarget.mAttrs.type != TYPE_BASE_APPLICATION
                     && mImeLayeringTarget.isSelfAnimating(0, ANIMATION_TYPE_WINDOW_ANIMATION);
-            if (mImeLayeringTarget.inAppOrRecentsTransition() || nonAppImeTargetAnimatingExit) {
+            if (mImeLayeringTarget.inTransitionSelfOrParent() || nonAppImeTargetAnimatingExit) {
                 showImeScreenshot();
             }
         }
@@ -4044,11 +4044,8 @@
         private SurfaceControl createImeSurface(SurfaceControl.ScreenshotHardwareBuffer b,
                 Transaction t) {
             final HardwareBuffer buffer = b.getHardwareBuffer();
-            if (DEBUG_INPUT_METHOD) {
-                Slog.d(TAG, "create IME snapshot for "
-                        + mImeTarget + ", buff width=" + buffer.getWidth()
-                        + ", height=" + buffer.getHeight());
-            }
+            ProtoLog.i(WM_DEBUG_IME, "create IME snapshot for %s, buff width=%s, height=%s",
+                    mImeTarget, buffer.getWidth(), buffer.getHeight());
             final WindowState imeWindow = mImeTarget.getDisplayContent().mInputMethodWindow;
             final ActivityRecord activity = mImeTarget.mActivityRecord;
             final SurfaceControl imeParent = mImeTarget.mAttrs.type == TYPE_BASE_APPLICATION
@@ -4084,12 +4081,14 @@
                         mImeTarget.mAttrs.surfaceInsets.top);
                 t.setPosition(imeSurface, surfacePosition.x, surfacePosition.y);
             }
+            ProtoLog.i(WM_DEBUG_IME, "Set IME snapshot position: (%d, %d)", surfacePosition.x,
+                    surfacePosition.y);
             return imeSurface;
         }
 
         private void removeImeSurface(Transaction t) {
             if (mImeSurface != null) {
-                if (DEBUG_INPUT_METHOD) Slog.d(TAG, "remove IME snapshot");
+                ProtoLog.i(WM_DEBUG_IME, "remove IME snapshot, caller=%s", Debug.getCallers(6));
                 t.remove(mImeSurface);
                 mImeSurface = null;
             }
@@ -4119,9 +4118,8 @@
             // to reflect the true IME insets visibility and the app task layout as possible.
             if (isValidSnapshot
                     && dc.getInsetsStateController().getImeSourceProvider().isImeShowing()) {
-                if (DEBUG_INPUT_METHOD) {
-                    Slog.d(TAG, "show IME snapshot, ime target=" + mImeTarget);
-                }
+                ProtoLog.i(WM_DEBUG_IME, "show IME snapshot, ime target=%s, callers=%s",
+                        mImeTarget, Debug.getCallers(6));
                 t.show(mImeSurface);
             } else if (!isValidSnapshot) {
                 removeImeSurface(t);
@@ -4167,7 +4165,7 @@
     void removeImeScreenshotIfPossible() {
         if (mImeLayeringTarget == null
                 || mImeLayeringTarget.mAttrs.type != TYPE_APPLICATION_STARTING
-                && !mImeLayeringTarget.inAppOrRecentsTransition()) {
+                && !mImeLayeringTarget.inTransitionSelfOrParent()) {
             removeImeSurfaceImmediately();
         }
     }
@@ -4488,6 +4486,12 @@
      * hierarchy.
      */
     void onWindowAnimationFinished(@NonNull WindowContainer wc, int type) {
+        if (mImeScreenshot != null) {
+            ProtoLog.i(WM_DEBUG_IME,
+                    "onWindowAnimationFinished, wc=%s, type=%s, imeSnapshot=%s, target=%s",
+                    wc, SurfaceAnimator.animationTypeToString(type), mImeScreenshot,
+                    mImeScreenshot.getImeTarget());
+        }
         if (mImeScreenshot != null && (wc == mImeScreenshot.getImeTarget()
                 || wc.getWindow(w -> w == mImeScreenshot.getImeTarget()) != null)
                 && (type & WindowState.EXIT_ANIMATING_TYPES) != 0) {
@@ -4875,6 +4879,23 @@
         void setOrganizer(IDisplayAreaOrganizer organizer, boolean skipDisplayAreaAppeared) {
             super.setOrganizer(organizer, skipDisplayAreaAppeared);
             mDisplayContent.updateImeParent();
+
+            // If the ImeContainer was previously unorganized then the framework might have
+            // reparented its surface control under an activity so we need to reparent it back
+            // under its parent.
+            if (organizer != null) {
+                final SurfaceControl imeParentSurfaceControl = getParentSurfaceControl();
+                if (mSurfaceControl != null && imeParentSurfaceControl != null) {
+                    ProtoLog.i(WM_DEBUG_IME, "ImeContainer just became organized. Reparenting "
+                            + "under parent. imeParentSurfaceControl=%s", imeParentSurfaceControl);
+                    getPendingTransaction().reparent(mSurfaceControl, imeParentSurfaceControl);
+                } else {
+                    ProtoLog.e(WM_DEBUG_IME, "ImeContainer just became organized but it doesn't "
+                            + "have a parent or the parent doesn't have a surface control."
+                            + " mSurfaceControl=%s imeParentSurfaceControl=%s",
+                            mSurfaceControl, imeParentSurfaceControl);
+                }
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 88d7dff..4573ede 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -75,6 +75,7 @@
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
 import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
 import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
 import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
@@ -83,7 +84,6 @@
 import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
 import static com.android.server.policy.WindowManagerPolicy.TRANSIT_SHOW;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -1366,8 +1366,7 @@
      * @return Resource ID of the actual animation to use, or {@link #ANIMATION_NONE} for none.
      */
     int selectAnimation(WindowState win, int transit) {
-        if (DEBUG_ANIM) Slog.i(TAG, "selectAnimation in " + win
-                + ": transit=" + transit);
+        ProtoLog.i(WM_DEBUG_ANIM, "selectAnimation in %s: transit=%d", win, transit);
         if (win == mStatusBar) {
             if (transit == TRANSIT_EXIT
                     || transit == TRANSIT_HIDE) {
@@ -1460,7 +1459,7 @@
                     // with old content because home is easier to have different UI states.
                     return ANIMATION_NONE;
                 }
-                if (DEBUG_ANIM) Slog.i(TAG, "**** STARTING EXIT");
+                ProtoLog.i(WM_DEBUG_ANIM, "**** STARTING EXIT");
                 return R.anim.app_starting_exit;
             }
         }
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 262ddae..43ff580 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -24,6 +24,7 @@
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
 import static com.android.server.wm.DisplayRotationProto.FIXED_TO_USER_ROTATION_MODE;
@@ -32,7 +33,6 @@
 import static com.android.server.wm.DisplayRotationProto.LAST_ORIENTATION;
 import static com.android.server.wm.DisplayRotationProto.ROTATION;
 import static com.android.server.wm.DisplayRotationProto.USER_ROTATION;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE;
@@ -747,10 +747,11 @@
         final boolean forceJumpcut = !mDisplayPolicy.isScreenOnFully()
                 || !mService.mPolicy.okToAnimate(false /* ignoreScreenOn */);
         final WindowState topFullscreen = mDisplayPolicy.getTopFullscreenOpaqueWindow();
-        if (DEBUG_ANIM) Slog.i(TAG, "selectRotationAnimation topFullscreen="
-                + topFullscreen + " rotationAnimation="
-                + (topFullscreen == null ? 0 : topFullscreen.getAttrs().rotationAnimation)
-                + " forceJumpcut=" + forceJumpcut);
+        ProtoLog.i(WM_DEBUG_ANIM, "selectRotationAnimation topFullscreen=%s"
+                + " rotationAnimation=%d forceJumpcut=%b",
+                topFullscreen,
+                topFullscreen == null ? 0 : topFullscreen.getAttrs().rotationAnimation,
+                forceJumpcut);
         if (forceJumpcut) {
             mTmpRotationAnim.mExit = R.anim.rotation_animation_jump_exit;
             mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
index 61f9fe2..d7e725b 100644
--- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
@@ -174,5 +174,9 @@
         }
 
         void dumpDebugInner(ProtoOutputStream proto);
+
+        default WindowAnimationSpec asWindowAnimationSpec() {
+            return null;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index eeac230..9927787 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -110,7 +110,7 @@
             ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS,
                     "goodToGo(): Animation canceled already");
             onAnimationFinished();
-            invokeAnimationCancelled();
+            invokeAnimationCancelled("already_cancelled");
             return;
         }
 
@@ -129,7 +129,7 @@
                     "goodToGo(): No apps to animate, mPendingAnimations=%d",
                     mPendingAnimations.size());
             onAnimationFinished();
-            invokeAnimationCancelled();
+            invokeAnimationCancelled("no_app_targets");
             return;
         }
 
@@ -169,7 +169,7 @@
             mCanceled = true;
         }
         onAnimationFinished();
-        invokeAnimationCancelled();
+        invokeAnimationCancelled(reason);
     }
 
     private void writeStartDebugStatement() {
@@ -296,7 +296,8 @@
         ProtoLog.i(WM_DEBUG_REMOTE_ANIMATIONS, "Finishing remote animation");
     }
 
-    private void invokeAnimationCancelled() {
+    private void invokeAnimationCancelled(String reason) {
+        ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "cancelAnimation(): reason=%s", reason);
         try {
             mRemoteAnimationAdapter.getRunner().onAnimationCancelled();
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 94fc51d..cc99f37 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2754,7 +2754,7 @@
         // First preference goes to the launch root task set in the activity options.
         if (options != null) {
             final Task candidateRoot = Task.fromWindowContainerToken(options.getLaunchRootTask());
-            if (canLaunchOnDisplay(r, candidateRoot)) {
+            if (candidateRoot != null && canLaunchOnDisplay(r, candidateRoot)) {
                 return candidateRoot;
             }
         }
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 92e2ee6..b576709 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -26,13 +26,22 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
+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;
 import android.os.Handler;
 import android.os.PowerManagerInternal;
 import android.util.ArrayMap;
+import android.util.Log;
 import android.view.Choreographer;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -40,6 +49,8 @@
 import com.android.server.AnimationThread;
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.function.Supplier;
 
 /**
@@ -73,6 +84,10 @@
 
     @GuardedBy("mLock")
     @VisibleForTesting
+    final ArrayMap<SurfaceControl, RunningAnimation> mPreProcessingAnimations = new ArrayMap<>();
+
+    @GuardedBy("mLock")
+    @VisibleForTesting
     final ArrayMap<SurfaceControl, RunningAnimation> mRunningAnimations = new ArrayMap<>();
 
     @GuardedBy("mLock")
@@ -136,23 +151,64 @@
         synchronized (mLock) {
             final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash,
                     finishCallback);
-            mPendingAnimations.put(animationLeash, runningAnim);
-            if (!mAnimationStartDeferred) {
-                mChoreographer.postFrameCallback(this::startAnimations);
+            boolean requiresEdgeExtension = requiresEdgeExtension(a);
+
+            if (requiresEdgeExtension) {
+                mPreProcessingAnimations.put(animationLeash, runningAnim);
+
+                // We must wait for t to be committed since otherwise the leash doesn't have the
+                // windows we want to screenshot and extend as children.
+                t.addTransactionCommittedListener(Runnable::run, () -> {
+                    final WindowAnimationSpec animationSpec = a.asWindowAnimationSpec();
+                    final Runnable cleanUpEdgeExtension = edgeExtendWindow(animationLeash,
+                            animationSpec.getRootTaskBounds(), animationSpec.getAnimation(),
+                            mFrameTransaction);
+
+                    runningAnim.mFinishCallback = () -> {
+                        cleanUpEdgeExtension.run();
+                        finishCallback.run();
+                    };
+
+                    synchronized (mLock) {
+                        // only run if animation is not yet canceled by this point
+                        if (mPreProcessingAnimations.get(animationLeash) == runningAnim) {
+                            mPreProcessingAnimations.remove(animationLeash);
+                            mPendingAnimations.put(animationLeash, runningAnim);
+                            if (!mAnimationStartDeferred) {
+                                mChoreographer.postFrameCallback(this::startAnimations);
+                            }
+                        }
+                    }
+                });
             }
 
-            // Some animations (e.g. move animations) require the initial transform to be applied
-            // immediately.
-            applyTransformation(runningAnim, t, 0 /* currentPlayTime */);
+            if (!requiresEdgeExtension) {
+                mPendingAnimations.put(animationLeash, runningAnim);
+                if (!mAnimationStartDeferred) {
+                    mChoreographer.postFrameCallback(this::startAnimations);
+                }
+
+                // Some animations (e.g. move animations) require the initial transform to be
+                // applied immediately.
+                applyTransformation(runningAnim, t, 0 /* currentPlayTime */);
+            }
         }
     }
 
+    private boolean requiresEdgeExtension(AnimationSpec a) {
+        return a.asWindowAnimationSpec() != null && a.asWindowAnimationSpec().hasExtension();
+    }
+
     void onAnimationCancelled(SurfaceControl leash) {
         synchronized (mLock) {
             if (mPendingAnimations.containsKey(leash)) {
                 mPendingAnimations.remove(leash);
                 return;
             }
+            if (mPreProcessingAnimations.containsKey(leash)) {
+                mPreProcessingAnimations.remove(leash);
+                return;
+            }
             final RunningAnimation anim = mRunningAnimations.get(leash);
             if (anim != null) {
                 mRunningAnimations.remove(leash);
@@ -264,10 +320,133 @@
         mApplyScheduled = false;
     }
 
+    private Runnable edgeExtendWindow(SurfaceControl leash, Rect bounds, Animation a,
+            Transaction transaction) {
+        final Transformation transformationAtStart = new Transformation();
+        a.getTransformationAt(0, transformationAtStart);
+        final Transformation transformationAtEnd = new Transformation();
+        a.getTransformationAt(1, transformationAtEnd);
+
+        // We want to create an extension surface that is the maximal size and the animation will
+        // take care of cropping any part that overflows.
+        final Insets maxExtensionInsets = Insets.min(
+                transformationAtStart.getInsets(), transformationAtEnd.getInsets());
+
+        final int targetSurfaceHeight = bounds.height();
+        final int targetSurfaceWidth = bounds.width();
+
+        final List<SurfaceControl> extensionSurfaces = new ArrayList<>();
+
+        if (maxExtensionInsets.left < 0) {
+            final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight);
+            final Rect extensionRect = new Rect(0, 0,
+                    -maxExtensionInsets.left, targetSurfaceHeight);
+            final int xPos = maxExtensionInsets.left;
+            final int yPos = 0;
+            final SurfaceControl extensionSurface = createExtensionSurface(leash, edgeBounds,
+                    extensionRect, xPos, yPos, "Left Edge Extension", transaction);
+            extensionSurfaces.add(extensionSurface);
+        }
+
+        if (maxExtensionInsets.top < 0) {
+            final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1);
+            final Rect extensionRect = new Rect(0, 0,
+                    targetSurfaceWidth, -maxExtensionInsets.top);
+            final int xPos = 0;
+            final int yPos = maxExtensionInsets.top;
+            final SurfaceControl extensionSurface = createExtensionSurface(leash, edgeBounds,
+                    extensionRect, xPos, yPos, "Top Edge Extension", transaction);
+            extensionSurfaces.add(extensionSurface);
+        }
+
+        if (maxExtensionInsets.right < 0) {
+            final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0,
+                    targetSurfaceWidth, targetSurfaceHeight);
+            final Rect extensionRect = new Rect(0, 0,
+                    -maxExtensionInsets.right, targetSurfaceHeight);
+            final int xPos = targetSurfaceWidth;
+            final int yPos = 0;
+            final SurfaceControl extensionSurface = createExtensionSurface(leash, edgeBounds,
+                    extensionRect, xPos, yPos, "Right Edge Extension", transaction);
+            extensionSurfaces.add(extensionSurface);
+        }
+
+        if (maxExtensionInsets.bottom < 0) {
+            final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1,
+                    targetSurfaceWidth, targetSurfaceHeight);
+            final Rect extensionRect = new Rect(0, 0,
+                    targetSurfaceWidth, -maxExtensionInsets.bottom);
+            final int xPos = maxExtensionInsets.left;
+            final int yPos = targetSurfaceHeight;
+            final SurfaceControl extensionSurface = createExtensionSurface(leash, edgeBounds,
+                    extensionRect, xPos, yPos, "Bottom Edge Extension", transaction);
+            extensionSurfaces.add(extensionSurface);
+        }
+
+        Runnable cleanUp = () -> {
+            for (final SurfaceControl extensionSurface : extensionSurfaces) {
+                if (extensionSurface != null) {
+                    transaction.remove(extensionSurface);
+                }
+            }
+        };
+
+        return cleanUp;
+    }
+
+    private SurfaceControl createExtensionSurface(SurfaceControl surfaceToExtend, Rect edgeBounds,
+            Rect extensionRect, int xPos, int yPos, String layerName,
+            Transaction startTransaction) {
+        final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
+                .setName(layerName)
+                .setParent(surfaceToExtend)
+                .setHidden(true)
+                .setCallsite("DefaultTransitionHandler#startAnimation")
+                .setOpaque(true)
+                .setBufferSize(extensionRect.width(), extensionRect.height())
+                .build();
+
+        SurfaceControl.LayerCaptureArgs captureArgs =
+                new SurfaceControl.LayerCaptureArgs.Builder(surfaceToExtend)
+                        .setSourceCrop(edgeBounds)
+                        .setFrameScale(1)
+                        .setPixelFormat(PixelFormat.RGBA_8888)
+                        .setChildrenOnly(true)
+                        .setAllowProtected(true)
+                        .build();
+        final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer =
+                SurfaceControl.captureLayers(captureArgs);
+
+        if (edgeBuffer == null) {
+            Log.e("SurfaceAnimationRunner", "Failed to create edge extension - "
+                    + "edge buffer is null");
+            return null;
+        }
+
+        android.graphics.BitmapShader shader =
+                new android.graphics.BitmapShader(edgeBuffer.asBitmap(),
+                        android.graphics.Shader.TileMode.CLAMP,
+                        android.graphics.Shader.TileMode.CLAMP);
+        final Paint paint = new Paint();
+        paint.setShader(shader);
+
+        final Surface surface = new Surface(edgeExtensionLayer);
+        Canvas c = surface.lockHardwareCanvas();
+        c.drawRect(extensionRect, paint);
+        surface.unlockCanvasAndPost(c);
+        surface.release();
+
+        startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
+        startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
+        startTransaction.setVisibility(edgeExtensionLayer, true);
+
+        return edgeExtensionLayer;
+    }
+
     private static final class RunningAnimation {
         final AnimationSpec mAnimSpec;
         final SurfaceControl mLeash;
-        final Runnable mFinishCallback;
+        Runnable mFinishCallback;
         ValueAnimator mAnim;
 
         @GuardedBy("mCancelLock")
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 53e3378..fbf0426 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -16,10 +16,10 @@
 
 package com.android.server.wm;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
 import static com.android.server.wm.SurfaceAnimatorProto.ANIMATION_ADAPTER;
 import static com.android.server.wm.SurfaceAnimatorProto.ANIMATION_START_DELAYED;
 import static com.android.server.wm.SurfaceAnimatorProto.LEASH;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -32,8 +32,11 @@
 import android.view.SurfaceControl.Transaction;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLogImpl;
+import com.android.internal.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.function.Supplier;
@@ -185,10 +188,16 @@
         }
         mAnimatable.onLeashAnimationStarting(t, mLeash);
         if (mAnimationStartDelayed) {
-            if (DEBUG_ANIM) Slog.i(TAG, "Animation start delayed");
+            ProtoLog.i(WM_DEBUG_ANIM, "Animation start delayed for %s", mAnimatable);
             return;
         }
         mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
+        if (ProtoLogImpl.isEnabled(WM_DEBUG_ANIM)) {
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw);
+            mAnimation.dump(pw, "");
+            ProtoLog.d(WM_DEBUG_ANIM, "Animation start for %s, anim=%s", mAnimatable, sw);
+        }
         if (snapshotAnim != null) {
             mSnapshot = freezer.takeSnapshotForAnimation();
             if (mSnapshot == null) {
@@ -340,7 +349,8 @@
      *                      to another animator.
      */
     private void cancelAnimation(Transaction t, boolean restarting, boolean forwardCancel) {
-        if (DEBUG_ANIM) Slog.i(TAG, "Cancelling animation restarting=" + restarting);
+        ProtoLog.i(WM_DEBUG_ANIM, "Cancelling animation restarting=%b for %s",
+                restarting, mAnimatable);
         final SurfaceControl leash = mLeash;
         final AnimationAdapter animation = mAnimation;
         final @AnimationType int animationType = mAnimationType;
@@ -419,7 +429,8 @@
         final boolean reparent = surface != null && (curAnimationLeash == null
                 || curAnimationLeash.equals(leash));
         if (reparent) {
-            if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent: " + parent);
+            ProtoLog.i(WM_DEBUG_ANIM, "Reparenting to original parent: %s for %s",
+                    parent, animatable);
             // We shouldn't really need these isValid checks but we do
             // b/130364451
             if (surface.isValid() && parent != null && parent.isValid()) {
@@ -444,7 +455,7 @@
     static SurfaceControl createAnimationLeash(Animatable animatable, SurfaceControl surface,
             Transaction t, @AnimationType int type, int width, int height, int x, int y,
             boolean hidden, Supplier<Transaction> transactionFactory) {
-        if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to leash");
+        ProtoLog.i(WM_DEBUG_ANIM, "Reparenting to leash for %s", animatable);
         final SurfaceControl.Builder builder = animatable.makeAnimationLeash()
                 .setParent(animatable.getAnimationLeashParent())
                 .setName(surface + " - animation-leash of " + animationTypeToString(type))
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c0bc019..23df429 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -405,6 +405,12 @@
             Slog.d(TAG, "setResumedActivity taskFrag:" + this + " + from: "
                     + mResumedActivity + " to:" + r + " reason:" + reason);
         }
+
+        if (r != null && mResumedActivity == null) {
+            // Task is becoming active.
+            getTask().touchActiveTime();
+        }
+
         final ActivityRecord prevR = mResumedActivity;
         mResumedActivity = r;
         mTaskSupervisor.updateTopResumedActivityIfNeeded();
@@ -518,7 +524,16 @@
                 || isAllowedToEmbedActivityInTrustedMode(a);
     }
 
+    /**
+     * Checks if the organized task fragment is allowed to embed activity in untrusted mode.
+     */
     boolean isAllowedToEmbedActivityInUntrustedMode(@NonNull ActivityRecord a) {
+        final WindowContainer parent = getParent();
+        if (parent == null || !parent.getBounds().contains(getBounds())) {
+            // Without full trust between the host and the embedded activity, we don't allow
+            // TaskFragment to have bounds outside of the parent bounds.
+            return false;
+        }
         return (a.info.flags & FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING)
                 == FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
     }
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index ff5bfbe..b8b151f 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -67,7 +67,8 @@
 class TaskOrganizerController extends ITaskOrganizerController.Stub {
     private static final String TAG = "TaskOrganizerController";
 
-    private class DeathRecipient implements IBinder.DeathRecipient {
+    @VisibleForTesting
+    class DeathRecipient implements IBinder.DeathRecipient {
         ITaskOrganizer mTaskOrganizer;
 
         DeathRecipient(ITaskOrganizer organizer) {
@@ -77,7 +78,7 @@
         @Override
         public void binderDied() {
             synchronized (mGlobalLock) {
-                final TaskOrganizerState state = mTaskOrganizerStates.remove(
+                final TaskOrganizerState state = mTaskOrganizerStates.get(
                         mTaskOrganizer.asBinder());
                 if (state != null) {
                     state.dispose();
@@ -170,7 +171,8 @@
         }
     }
 
-    private class TaskOrganizerState {
+    @VisibleForTesting
+    class TaskOrganizerState {
         private final TaskOrganizerCallbacks mOrganizer;
         private final DeathRecipient mDeathRecipient;
         private final ArrayList<Task> mOrganizedTasks = new ArrayList<>();
@@ -191,6 +193,11 @@
             mUid = uid;
         }
 
+        @VisibleForTesting
+        DeathRecipient getDeathRecipient() {
+            return mDeathRecipient;
+        }
+
         /**
          * Register this task with this state, but doesn't trigger the task appeared callback to
          * the organizer.
@@ -265,7 +272,7 @@
 
             // Remove organizer state after removing tasks so we get a chance to send
             // onTaskVanished.
-            mTaskOrganizerStates.remove(asBinder());
+            mTaskOrganizerStates.remove(mOrganizer.getBinder());
         }
 
         void unlinkDeath() {
@@ -596,7 +603,7 @@
     private void onTaskVanishedInternal(ITaskOrganizer organizer, Task task) {
         for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) {
             PendingTaskEvent entry = mPendingTaskEvents.get(i);
-            if (task.mTaskId == entry.mTask.mTaskId) {
+            if (task.mTaskId == entry.mTask.mTaskId && entry.mTaskOrg == organizer) {
                 // This task is vanished so remove all pending event of it.
                 mPendingTaskEvents.remove(i);
                 if (entry.mEventType == PendingTaskEvent.EVENT_APPEARED) {
@@ -693,9 +700,16 @@
                     }
                     break;
                 case PendingTaskEvent.EVENT_VANISHED:
-                    state = mTaskOrganizerStates.get(event.mTaskOrg.asBinder());
-                    if (state != null) {
-                        state.mOrganizer.onTaskVanished(task);
+                    // TaskOrganizerState cannot be used here because it might have already been
+                    // removed.
+                    // The state is removed when an organizer dies or is unregistered. In order to
+                    // send the pending vanished task events, the mTaskOrg from event is used.
+                    // These events should not ideally be sent and will be removed as part of
+                    // b/224812558.
+                    try {
+                        event.mTaskOrg.onTaskVanished(task.getTaskInfo());
+                    } catch (RemoteException ex) {
+                        Slog.e(TAG, "Exception sending onTaskVanished callback", ex);
                     }
                     mLastSentTaskInfos.remove(task);
                     break;
@@ -812,7 +826,7 @@
                 }
 
                 // Avoid WindowState#getRootTask() so we don't attribute system windows to a task.
-                final Task task = imeLayeringTarget.getWindow().asTask();
+                final Task task = imeLayeringTarget.getWindow().getTask();
                 if (task == null) {
                     return null;
                 }
@@ -1045,4 +1059,9 @@
         }
         pw.println();
     }
+
+    @VisibleForTesting
+    TaskOrganizerState getTaskOrganizerState(IBinder taskOrganizer) {
+        return mTaskOrganizerStates.get(taskOrganizer);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 21ca4bb..a0d68a1 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -520,7 +520,6 @@
                     // Legacy dispatch relies on this (for now).
                     ar.mEnteringAnimation = visibleAtTransitionEnd;
                 }
-                mController.dispatchLegacyAppTransitionFinished(ar);
             }
             final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
             if (wt != null) {
@@ -532,6 +531,15 @@
                 }
             }
         }
+        // dispatch legacy callback in a different loop. This is because multiple legacy handlers
+        // (fixed-rotation/displaycontent) make global changes, so we want to ensure that we've
+        // processed all the participants first (in particular, we want to trigger pip-enter first)
+        for (int i = 0; i < mParticipants.size(); ++i) {
+            final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
+            if (ar != null) {
+                mController.dispatchLegacyAppTransitionFinished(ar);
+            }
+        }
         if (activitiesWentInvisible) {
             // Always schedule stop processing when transition finishes because activities don't
             // stop while they are in a transition thus their stop could still be pending.
@@ -1127,7 +1135,6 @@
                 continue;
             }
             targets.add(wc);
-            targets.mValidParticipants.add(wc);
         }
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Initial targets: %s",
                 targets.mArray);
@@ -1145,15 +1152,17 @@
     private static void populateParentChanges(Targets targets,
             ArrayMap<WindowContainer, ChangeInfo> changes) {
         final ArrayList<WindowContainer<?>> intermediates = new ArrayList<>();
-        for (int i = targets.mValidParticipants.size() - 1; i >= 0; --i) {
-            WindowContainer<?> wc = targets.mValidParticipants.get(i);
-            // Go up if the participant has been represented by its parent.
-            while (targets.mArray.indexOfValue(wc) < 0 && wc.getParent() != null) {
-                wc = wc.getParent();
-            }
+        // Make a copy to iterate because the original array may be modified.
+        final ArrayList<WindowContainer<?>> targetList = new ArrayList<>(targets.mArray.size());
+        for (int i = targets.mArray.size() - 1; i >= 0; --i) {
+            targetList.add(targets.mArray.valueAt(i));
+        }
+        for (int i = targetList.size() - 1; i >= 0; --i) {
+            final WindowContainer<?> wc = targetList.get(i);
             // Wallpaper must belong to the top (regardless of how nested it is in DisplayAreas).
             final boolean skipIntermediateReports = isWallpaper(wc);
             intermediates.clear();
+            boolean foundParentInTargets = false;
             // Collect the intermediate parents between target and top changed parent.
             for (WindowContainer<?> p = wc.getParent(); p != null; p = p.getParent()) {
                 final ChangeInfo parentChange = changes.get(p);
@@ -1167,19 +1176,19 @@
                     // The chain above the parent was processed.
                     break;
                 }
-                if (targets.mValidParticipants.contains(p)) {
+                if (targetList.contains(p)) {
                     if (skipIntermediateReports) {
                         changes.get(wc).mParent = p;
                     } else {
                         intermediates.add(p);
                     }
-                    // The parent reaches a participant.
+                    foundParentInTargets = true;
                     break;
                 } else if (reportIfNotTop(p) && !skipIntermediateReports) {
                     intermediates.add(p);
                 }
             }
-            if (intermediates.isEmpty()) continue;
+            if (!foundParentInTargets || intermediates.isEmpty()) continue;
             // Add any always-report parents along the way.
             changes.get(wc).mParent = intermediates.get(0);
             for (int j = 0; j < intermediates.size() - 1; j++) {
@@ -1657,8 +1666,6 @@
     private static class Targets {
         /** All targets. Its keys (depth) are sorted in ascending order naturally. */
         final SparseArray<WindowContainer<?>> mArray = new SparseArray<>();
-        /** The initial participants which have changes. */
-        final ArrayList<WindowContainer<?>> mValidParticipants = new ArrayList<>();
         /** The targets which were represented by their parent. */
         private ArrayList<WindowContainer<?>> mRemovedTargets;
         private int mDepthFactor;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 8840cd5..18851b3 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -243,9 +243,13 @@
         return isCollecting() || isPlaying();
     }
 
-    /** @return {@code true} if wc is in a participant subtree */
+    /** @return {@code true} if a transition is running in a participant subtree of wc */
     boolean inTransition(@NonNull WindowContainer wc) {
-        if (isCollecting(wc)) return true;
+        if (isCollecting()) {
+            for (WindowContainer p = wc; p != null; p = p.getParent()) {
+                if (isCollecting(p)) return true;
+            }
+        }
         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)) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index 5bfd546..b6d668d 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -21,6 +21,7 @@
 import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;
 import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE;
 
+import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.SystemClock;
@@ -75,6 +76,11 @@
     }
 
     @Override
+    public WindowAnimationSpec asWindowAnimationSpec() {
+        return this;
+    }
+
+    @Override
     public boolean getShowWallpaper() {
         return mAnimation.getShowWallpaper();
     }
@@ -89,11 +95,27 @@
         return mAnimation.getBackgroundColor();
     }
 
+    /**
+     * @return If a window animation has outsets applied to it.
+     * @see Animation#hasExtension()
+     */
+    public boolean hasExtension() {
+        return mAnimation.hasExtension();
+    }
+
     @Override
     public long getDuration() {
         return mAnimation.computeDurationHint();
     }
 
+    public Rect getRootTaskBounds() {
+        return mRootTaskBounds;
+    }
+
+    public Animation getAnimation() {
+        return mAnimation;
+    }
+
     @Override
     public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
         final TmpValues tmp = mThreadLocalTmps.get();
@@ -106,7 +128,9 @@
         boolean cropSet = false;
         if (mRootTaskClipMode == ROOT_TASK_CLIP_NONE) {
             if (tmp.transformation.hasClipRect()) {
-                t.setWindowCrop(leash, tmp.transformation.getClipRect());
+                final Rect clipRect = tmp.transformation.getClipRect();
+                accountForExtension(tmp.transformation, clipRect);
+                t.setWindowCrop(leash, clipRect);
                 cropSet = true;
             }
         } else {
@@ -114,6 +138,7 @@
             if (tmp.transformation.hasClipRect()) {
                 mTmpRect.intersect(tmp.transformation.getClipRect());
             }
+            accountForExtension(tmp.transformation, mTmpRect);
             t.setWindowCrop(leash, mTmpRect);
             cropSet = true;
         }
@@ -125,6 +150,14 @@
         }
     }
 
+    private void accountForExtension(Transformation transformation, Rect clipRect) {
+        Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE);
+        if (!extensionInsets.equals(Insets.NONE)) {
+            // Extend the surface to allow for the edge extension to be visible
+            clipRect.inset(extensionInsets);
+        }
+    }
+
     @Override
     public long calculateStatusBarTransitionStartTime() {
         TranslateAnimation openTranslateAnimation = findTranslateAnimation(mAnimation);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index e7b4e83..99e39f1 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -33,6 +33,7 @@
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
@@ -47,7 +48,6 @@
 import static com.android.server.wm.IdentifierProto.USER_ID;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -59,10 +59,8 @@
 import static com.android.server.wm.WindowContainerProto.SURFACE_CONTROL;
 import static com.android.server.wm.WindowContainerProto.VISIBLE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.logWithStack;
 import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
 
 import android.annotation.CallSuper;
@@ -107,6 +105,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
+import com.android.internal.protolog.ProtoLogImpl;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.server.wm.SurfaceAnimator.Animatable;
@@ -1175,27 +1174,6 @@
         return mTransitionController.inTransition(this);
     }
 
-    boolean inAppOrRecentsTransition() {
-        if (!mTransitionController.isShellTransitionsEnabled()) {
-            return isAnimating(PARENTS | TRANSITION,
-                    ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
-        }
-        for (WindowContainer p = this; p != null; p = p.getParent()) {
-            if (mTransitionController.isCollecting(p)) {
-                return true;
-            }
-        }
-        if (inTransition() || mTransitionController.inRecentsTransition(this)) return true;
-
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            WindowContainer child = mChildren.get(i);
-            if (child.inAppOrRecentsTransition()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     boolean isExitAnimationRunningSelfOrChild() {
         if (!mTransitionController.isShellTransitionsEnabled()) {
             return isAnimating(TRANSITION | CHILDREN, WindowState.EXIT_ANIMATING_TYPES);
@@ -2748,9 +2726,8 @@
             @Nullable OnAnimationFinishedCallback animationFinishedCallback,
             @Nullable Runnable animationCancelledCallback,
             @Nullable AnimationAdapter snapshotAnim) {
-        if (DEBUG_ANIM) {
-            Slog.v(TAG, "Starting animation on " + this + ": type=" + type + ", anim=" + anim);
-        }
+        ProtoLog.v(WM_DEBUG_ANIM, "Starting animation on %s: type=%d, anim=%s",
+                this, type, anim);
 
         // TODO: This should use isVisible() but because isVisible has a really weird meaning at
         // the moment this doesn't work for all animatable window containers.
@@ -3119,9 +3096,9 @@
                 // ActivityOption#makeCustomAnimation or WindowManager#overridePendingTransition.
                 a.restrictDuration(MAX_APP_TRANSITION_DURATION);
             }
-            if (DEBUG_ANIM) {
-                logWithStack(TAG, "Loaded animation " + a + " for " + this
-                        + ", duration: " + ((a != null) ? a.getDuration() : 0));
+            if (ProtoLogImpl.isEnabled(WM_DEBUG_ANIM)) {
+                ProtoLog.i(WM_DEBUG_ANIM, "Loaded animation %s for %s, duration: %d, stack=%s",
+                        a, this, ((a != null) ? a.getDuration() : 0), Debug.getCallers(20));
             }
             final int containingWidth = frame.width();
             final int containingHeight = frame.height();
@@ -3893,7 +3870,8 @@
         try {
             overlay.getRemoteInterface().onConfigurationChanged(getConfiguration());
         } catch (Exception e) {
-            Slog.e(TAG, "Error sending initial configuration change to WindowContainer overlay");
+            ProtoLog.e(WM_DEBUG_ANIM,
+                    "Error sending initial configuration change to WindowContainer overlay");
             removeTrustedOverlay(overlay);
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index c954700..42b556f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -34,7 +34,6 @@
     static final String TAG_WM = "WindowManager";
 
     static final boolean DEBUG = false;
-    static final boolean DEBUG_ANIM = false;
     static final boolean DEBUG_LAYOUT = false;
     static final boolean DEBUG_LAYERS = false;
     static final boolean DEBUG_INPUT = false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 43e84ae..7165854 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -97,6 +97,7 @@
 import static android.window.WindowProviderService.isWindowProviderService;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
@@ -2560,7 +2561,9 @@
             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()) {
@@ -2570,6 +2573,7 @@
         } 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
@@ -2585,6 +2589,10 @@
             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);
         }
@@ -3760,7 +3768,8 @@
      * Sets the touch mode state.
      *
      * To be able to change touch mode state, the caller must either own the focused window, or must
-     * have the MODIFY_TOUCH_MODE_STATE permission. Instrumented processes are allowed to switch
+     * have the {@link android.Manifest.permission#MODIFY_TOUCH_MODE_STATE} permission. Instrumented
+     * process, sourced with {@link android.Manifest.permission#MODIFY_TOUCH_MODE_STATE}, may switch
      * touch mode at any time.
      *
      * @param mode the touch mode to set
@@ -3773,9 +3782,10 @@
             }
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
-
-            final boolean hasPermission = mAtmService.isInstrumenting(pid)
-                    || checkCallingPermission(MODIFY_TOUCH_MODE_STATE, "setInTouchMode()");
+            final boolean hasPermission =
+                    mAtmService.instrumentationSourceHasPermission(pid, MODIFY_TOUCH_MODE_STATE)
+                            || checkCallingPermission(MODIFY_TOUCH_MODE_STATE, "setInTouchMode()",
+                                                      /* printlog= */ false);
             final long token = Binder.clearCallingIdentity();
             try {
                 if (mInputManager.setInTouchMode(mode, pid, uid, hasPermission)) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 044da39..fe5f675 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -19,8 +19,6 @@
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.app.ActivityManager.isStartResultSuccessful;
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.window.WindowContainerTransaction.Change.CHANGE_BOUNDS_TRANSACTION;
-import static android.window.WindowContainerTransaction.Change.CHANGE_BOUNDS_TRANSACTION_RECT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
@@ -681,7 +679,7 @@
                         break;
                     }
                 }
-                effects |= deleteTaskFragment(taskFragment, errorCallbackToken);
+                effects |= deleteTaskFragment(taskFragment, organizer, errorCallbackToken);
                 break;
             }
             case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: {
@@ -699,8 +697,7 @@
                         .startActivityInTaskFragment(tf, activityIntent, activityOptions,
                                 hop.getCallingActivity(), caller.mUid, caller.mPid);
                 if (!isStartResultSuccessful(result)) {
-                    sendTaskFragmentOperationFailure(tf.getTaskFragmentOrganizer(),
-                            errorCallbackToken,
+                    sendTaskFragmentOperationFailure(organizer, errorCallbackToken,
                             convertStartFailureToThrowable(result, activityIntent));
                 } else {
                     effects |= TRANSACT_EFFECTS_LIFECYCLE;
@@ -710,13 +707,20 @@
             case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: {
                 final IBinder fragmentToken = hop.getNewParent();
                 final ActivityRecord activity = ActivityRecord.forTokenLocked(hop.getContainer());
-                if (!mLaunchTaskFragments.containsKey(fragmentToken) || activity == null) {
+                final TaskFragment parent = mLaunchTaskFragments.get(fragmentToken);
+                if (parent == null || activity == null) {
                     final Throwable exception = new IllegalArgumentException(
                             "Not allowed to operate with invalid fragment token or activity.");
                     sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
                     break;
                 }
-                activity.reparent(mLaunchTaskFragments.get(fragmentToken), POSITION_TOP);
+                if (!parent.isAllowedToEmbedActivity(activity)) {
+                    final Throwable exception = new SecurityException(
+                            "The task fragment is not trusted to embed the given activity.");
+                    sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+                    break;
+                }
+                activity.reparent(parent, POSITION_TOP);
                 effects |= TRANSACT_EFFECTS_LIFECYCLE;
                 break;
             }
@@ -860,12 +864,14 @@
                 final WindowContainer newParent = hop.getNewParent() != null
                         ? WindowContainer.fromBinder(hop.getNewParent())
                         : null;
-                if (oldParent == null || !oldParent.isAttached()) {
+                if (oldParent == null || oldParent.asTaskFragment() == null
+                        || !oldParent.isAttached()) {
                     Slog.e(TAG, "Attempt to operate on unknown or detached container: "
                             + oldParent);
                     break;
                 }
-                reparentTaskFragment(oldParent, newParent, errorCallbackToken);
+                reparentTaskFragment(oldParent.asTaskFragment(), newParent, organizer,
+                        errorCallbackToken);
                 effects |= TRANSACT_EFFECTS_LIFECYCLE;
                 break;
             }
@@ -1246,7 +1252,7 @@
         mService.enforceTaskPermission(func);
     }
 
-    private void enforceTaskPermission(String func, WindowContainerTransaction t) {
+    private void enforceTaskPermission(String func, @Nullable WindowContainerTransaction t) {
         if (t == null || t.getTaskFragmentOrganizer() == null) {
             enforceTaskPermission(func);
             return;
@@ -1271,14 +1277,11 @@
         while (entries.hasNext()) {
             final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next();
             // Only allow to apply changes to TaskFragment that is created by this organizer.
-            WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
+            final WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
             enforceTaskFragmentOrganized(func, wc, organizer);
             enforceTaskFragmentConfigChangeAllowed(func, wc, entry.getValue(), organizer);
         }
 
-        // TODO(b/197364677): Enforce safety of hierarchy operations in untrusted mode. E.g. one
-        // could first change a trusted TF, and then start/reparent untrusted activity there.
-
         // Hierarchy changes
         final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
         for (int i = hops.size() - 1; i >= 0; i--) {
@@ -1352,8 +1355,6 @@
      * Makes sure that SurfaceControl transactions and the ability to set bounds outside of the
      * parent bounds are not allowed for embedding without full trust between the host and the
      * target.
-     * TODO(b/197364677): Allow SC transactions when the client-driven animations are protected from
-     * tapjacking.
      */
     private void enforceTaskFragmentConfigChangeAllowed(String func, @Nullable WindowContainer wc,
             WindowContainerTransaction.Change change, ITaskFragmentOrganizer organizer) {
@@ -1361,35 +1362,48 @@
             Slog.e(TAG, "Attempt to operate on task fragment that no longer exists");
             return;
         }
-        // Check if TaskFragment is embedded in fully trusted mode
-        if (wc.asTaskFragment().isAllowedToBeEmbeddedInTrustedMode()) {
-            // Fully trusted, no need to check further
-            return;
-        }
-
         if (change == null) {
             return;
         }
         final int changeMask = change.getChangeMask();
-        if ((changeMask & (CHANGE_BOUNDS_TRANSACTION | CHANGE_BOUNDS_TRANSACTION_RECT)) != 0) {
+        if (changeMask != 0) {
+            // None of the change should be requested from a TaskFragment organizer.
             String msg = "Permission Denial: " + func + " from pid="
                     + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
-                    + " trying to apply SurfaceControl changes to TaskFragment in non-trusted "
-                    + "embedding mode, TaskFragmentOrganizer=" + organizer;
+                    + " trying to apply changes of " + changeMask + " to TaskFragment"
+                    + " TaskFragmentOrganizer=" + organizer;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        // Check if TaskFragment is embedded in fully trusted mode.
+        if (wc.asTaskFragment().isAllowedToBeEmbeddedInTrustedMode()) {
+            // Fully trusted, no need to check further
+            return;
+        }
+        final WindowContainer wcParent = wc.getParent();
+        if (wcParent == null) {
+            Slog.e(TAG, "Attempt to apply config change on task fragment that has no parent");
+            return;
+        }
+        final Configuration requestedConfig = change.getConfiguration();
+        final Configuration parentConfig = wcParent.getConfiguration();
+        if (parentConfig.screenWidthDp < requestedConfig.screenWidthDp
+                || parentConfig.screenHeightDp < requestedConfig.screenHeightDp
+                || parentConfig.smallestScreenWidthDp < requestedConfig.smallestScreenWidthDp) {
+            String msg = "Permission Denial: " + func + " from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                    + " trying to apply screen width/height greater than parent's for non-trusted"
+                    + " host, TaskFragmentOrganizer=" + organizer;
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
         if (change.getWindowSetMask() == 0) {
-            // Nothing else to check.
+            // No bounds change.
             return;
         }
-        WindowConfiguration requestedWindowConfig = change.getConfiguration().windowConfiguration;
-        WindowContainer wcParent = wc.getParent();
-        if (wcParent == null) {
-            Slog.e(TAG, "Attempt to set bounds on task fragment that has no parent");
-            return;
-        }
-        if (!wcParent.getBounds().contains(requestedWindowConfig.getBounds())) {
+        final WindowConfiguration requestedWindowConfig = requestedConfig.windowConfiguration;
+        final WindowConfiguration parentWindowConfig = parentConfig.windowConfiguration;
+        if (!parentWindowConfig.getBounds().contains(requestedWindowConfig.getBounds())) {
             String msg = "Permission Denial: " + func + " from pid="
                     + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
                     + " trying to apply bounds outside of parent for non-trusted host,"
@@ -1397,6 +1411,17 @@
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
+        if (requestedWindowConfig.getAppBounds() != null
+                && parentWindowConfig.getAppBounds() != null
+                && !parentWindowConfig.getAppBounds().contains(
+                        requestedWindowConfig.getAppBounds())) {
+            String msg = "Permission Denial: " + func + " from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                    + " trying to apply app bounds outside of parent for non-trusted host,"
+                    + " TaskFragmentOrganizer=" + organizer;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
     }
 
     void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams,
@@ -1422,7 +1447,7 @@
         if (ownerActivity.getTask().effectiveUid != ownerActivity.getUid()
                 || ownerActivity.getTask().effectiveUid != caller.mUid) {
             final Throwable exception =
-                    new IllegalArgumentException("Not allowed to operate with the ownerToken while "
+                    new SecurityException("Not allowed to operate with the ownerToken while "
                             + "the root activity of the target task belong to the different app");
             sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
             return;
@@ -1439,33 +1464,46 @@
         mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment);
     }
 
-    void reparentTaskFragment(@NonNull WindowContainer oldParent,
-            @Nullable WindowContainer newParent,  @Nullable IBinder errorCallbackToken) {
-        WindowContainer parent = newParent;
-        if (parent == null && oldParent.asTaskFragment() != null) {
-            parent = oldParent.asTaskFragment().getTask();
+    void reparentTaskFragment(@NonNull TaskFragment oldParent, @Nullable WindowContainer newParent,
+            @Nullable ITaskFragmentOrganizer organizer, @Nullable IBinder errorCallbackToken) {
+        final TaskFragment newParentTF;
+        if (newParent == null) {
+            // Use the old parent's parent if the caller doesn't specify the new parent.
+            newParentTF = oldParent.getTask();
+        } else {
+            newParentTF = newParent.asTaskFragment();
         }
-        if (parent == null) {
+        if (newParentTF == null) {
             final Throwable exception =
                     new IllegalArgumentException("Not allowed to operate with invalid container");
-            sendTaskFragmentOperationFailure(oldParent.asTaskFragment().getTaskFragmentOrganizer(),
-                    errorCallbackToken, exception);
+            sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
             return;
         }
+        if (newParentTF.getTaskFragmentOrganizer() != null) {
+            // We are reparenting activities to a new embedded TaskFragment, this operation is only
+            // allowed if the new parent is trusted by all reparent activities.
+            final boolean isEmbeddingDisallowed = oldParent.forAllActivities(activity ->
+                    !newParentTF.isAllowedToEmbedActivity(activity));
+            if (isEmbeddingDisallowed) {
+                final Throwable exception = new SecurityException(
+                        "The new parent is not trusted to embed the activities.");
+                sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+                return;
+            }
+        }
         while (oldParent.hasChild()) {
-            oldParent.getChildAt(0).reparent(parent, POSITION_TOP);
+            oldParent.getChildAt(0).reparent(newParentTF, POSITION_TOP);
         }
     }
 
     private int deleteTaskFragment(@NonNull TaskFragment taskFragment,
-            @Nullable IBinder errorCallbackToken) {
+            @Nullable ITaskFragmentOrganizer organizer, @Nullable IBinder errorCallbackToken) {
         final int index = mLaunchTaskFragments.indexOfValue(taskFragment);
         if (index < 0) {
             final Throwable exception =
                     new IllegalArgumentException("Not allowed to operate with invalid "
                             + "taskFragment");
-            sendTaskFragmentOperationFailure(taskFragment.getTaskFragmentOrganizer(),
-                    errorCallbackToken, exception);
+            sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
             return 0;
         }
         mLaunchTaskFragments.removeAt(index);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index bb78767..0ca1058 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -106,6 +106,7 @@
 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
@@ -140,7 +141,6 @@
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowContainerChildProto.WINDOW;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER;
@@ -252,6 +252,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.KeyInterceptionInfo;
+import com.android.internal.protolog.ProtoLogImpl;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.ToBooleanFunction;
@@ -262,6 +263,7 @@
 import dalvik.annotation.optimization.NeverCompile;
 
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -2241,6 +2243,8 @@
             // Mark all relevant flags for that onExitAnimationDone will proceed all the way
             // to actually remove it.
             if (!visible && isVisibleNow && mActivityRecord.isAnimating(PARENTS | TRANSITION)) {
+                ProtoLog.d(WM_DEBUG_ANIM,
+                        "Set animatingExit: reason=onAppVisibilityChanged win=%s", this);
                 mAnimatingExit = true;
                 mRemoveOnExit = true;
                 mWindowRemovalAllowed = true;
@@ -2580,6 +2584,8 @@
                     // been removed. We probably need another flag to indicate that window removal
                     // should be deffered vs. overloading the flag that says we are playing an exit
                     // animation.
+                    ProtoLog.v(WM_DEBUG_ANIM,
+                            "Set animatingExit: reason=remove/replaceWindow win=%s", this);
                     mAnimatingExit = true;
                     mReplacingRemoveRequested = true;
                     return;
@@ -2608,6 +2614,8 @@
 
                     // Try starting an animation.
                     if (mWinAnimator.applyAnimationLocked(transit, false)) {
+                        ProtoLog.v(WM_DEBUG_ANIM,
+                                "Set animatingExit: reason=remove/applyAnimation win=%s", this);
                         mAnimatingExit = true;
 
                         // mAnimatingExit affects canAffectSystemUiFlags(). Run layout such that
@@ -2634,6 +2642,8 @@
                     // The exit animation is running or should run... wait for it!
                     ProtoLog.v(WM_DEBUG_ADD_REMOVE,
                             "Not removing %s due to exit animation", this);
+                    ProtoLog.v(WM_DEBUG_ANIM, "Set animatingExit: reason=remove/isAnimating win=%s",
+                            this);
                     setupWindowForRemoveOnExit();
                     if (mActivityRecord != null) {
                         mActivityRecord.updateReportedVisibilityLocked();
@@ -3593,6 +3603,7 @@
         // Clear animating flags now, since the surface is now gone. (Note this is true even
         // if the surface is saved, to outside world the surface is still NO_SURFACE.)
         mAnimatingExit = false;
+        ProtoLog.d(WM_DEBUG_ANIM, "Clear animatingExit: reason=destroySurface win=%s", this);
 
         if (useBLASTSync()) {
             immediatelyNotifyBlastSync();
@@ -3869,25 +3880,6 @@
             outFrames.displayFrame.scale(mInvGlobalScale);
         }
 
-        final Rect backdropFrame = outFrames.backdropFrame;
-        // When the task is docked, we send fullscreen sized backdropFrame as soon as resizing
-        // start even if we haven't received the relayout window, so that the client requests
-        // the relayout sooner. When dragging stops, backdropFrame needs to stay fullscreen
-        // until the window to small size, otherwise the multithread renderer will shift last
-        // one or more frame to wrong offset. So here we send fullscreen backdrop if either
-        // isDragResizing() or isDragResizeChanged() is true.
-        final boolean resizing = isDragResizing() || isDragResizeChanged();
-        if (!resizing || getWindowConfiguration().useWindowFrameForBackdrop()) {
-            // Surface position is now inherited from parent, and BackdropFrameRenderer uses
-            // backdrop frame to position content. Thus we just keep the size of backdrop frame,
-            // and remove the offset to avoid double offset from display origin.
-            backdropFrame.set(outFrames.frame);
-            backdropFrame.offsetTo(0, 0);
-        } else {
-            final DisplayInfo displayInfo = getDisplayInfo();
-            backdropFrame.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
-        }
-
         // Note: in the cases where the window is tied to an activity, we should not send a
         // configuration update when the window has requested to be hidden. Doing so can lead to
         // the client erroneously accepting a configuration that would have otherwise caused an
@@ -4682,8 +4674,7 @@
 
         // Force the show in the next prepareSurfaceLocked() call.
         mWinAnimator.mLastAlpha = -1;
-        if (DEBUG_ANIM) Slog.v(TAG,
-                "performShowLocked: mDrawState=HAS_DRAWN in " + this);
+        ProtoLog.v(WM_DEBUG_ANIM, "performShowLocked: mDrawState=HAS_DRAWN in %s", this);
         mWinAnimator.mDrawState = HAS_DRAWN;
         mWmService.scheduleAnimationLocked();
 
@@ -5003,7 +4994,7 @@
     }
 
     boolean isExitAnimationRunningSelfOrParent() {
-        return inAppOrRecentsTransition()
+        return inTransitionSelfOrParent()
                 || isAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION);
     }
 
@@ -5011,6 +5002,18 @@
         return isAnimating(CHILDREN, ANIMATION_TYPE_WINDOW_ANIMATION);
     }
 
+    /**
+     * @return {@code true} if self or the parent container of the window is in transition.
+     * (e.g. The app or recents transition)
+     */
+    boolean inTransitionSelfOrParent() {
+        if (!mTransitionController.isShellTransitionsEnabled()) {
+            return isAnimating(PARENTS | TRANSITION,
+                    ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
+        }
+        return mTransitionController.inTransition(this);
+    }
+
     private boolean shouldFinishAnimatingExit() {
         // Exit animation might be applied soon.
         if (inTransition()) {
@@ -5054,9 +5057,17 @@
     }
 
     void onExitAnimationDone() {
-        if (DEBUG_ANIM) Slog.v(TAG, "onExitAnimationDone in " + this
-                + ": exiting=" + mAnimatingExit + " remove=" + mRemoveOnExit
-                + " selfAnimating=" + isAnimating());
+        if (ProtoLogImpl.isEnabled(WM_DEBUG_ANIM)) {
+            final AnimationAdapter animationAdapter = mSurfaceAnimator.getAnimation();
+            StringWriter sw = new StringWriter();
+            if (animationAdapter != null) {
+                PrintWriter pw = new PrintWriter(sw);
+                animationAdapter.dump(pw, "");
+            }
+            ProtoLog.v(WM_DEBUG_ANIM, "onExitAnimationDone in %s"
+                            + ": exiting=%b remove=%b selfAnimating=%b anim=%s",
+                    this, mAnimatingExit, mRemoveOnExit, isAnimating(), sw);
+        }
 
         if (!mChildren.isEmpty()) {
             // Copying to a different list as multiple children can be removed.
@@ -5110,6 +5121,7 @@
             }
         }
         mAnimatingExit = false;
+        ProtoLog.d(WM_DEBUG_ANIM, "Clear animatingExit: reason=exitAnimationDone win=%s", this);
         getDisplayContent().mWallpaperController.hideWallpapers(this);
     }
 
@@ -5141,6 +5153,8 @@
             // show up with wrong position or scale.
             if (mAnimatingExit) {
                 mAnimatingExit = false;
+                ProtoLog.d(WM_DEBUG_ANIM, "Clear animatingExit: reason=clearAnimatingFlags win=%s",
+                        this);
                 didSomething = true;
             }
             if (mDestroying) {
@@ -5215,6 +5229,8 @@
                 cancelAnimation();
             }
             mAnimatingExit = false;
+            ProtoLog.d(WM_DEBUG_ANIM, "Clear animatingExit: reason=relayoutVisibleWindow win=%s",
+                    this);
         }
         if (mDestroying) {
             mDestroying = false;
@@ -5299,7 +5315,7 @@
             return;
         }
 
-        if (DEBUG_ANIM) Slog.v(TAG, "Setting move animation on " + this);
+        ProtoLog.v(WM_DEBUG_ANIM, "Setting move animation on %s", this);
         final Point oldPosition = new Point();
         final Point newPosition = new Point();
         transformFrameToSurfacePosition(mWindowFrames.mLastFrame.left, mWindowFrames.mLastFrame.top,
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 285a6d5..5c0557f 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -23,6 +23,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.TRANSIT_OLD_NONE;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DRAW;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
@@ -34,7 +35,6 @@
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
@@ -60,6 +60,7 @@
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 
+import com.android.internal.protolog.ProtoLogImpl;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.policy.WindowManagerPolicy;
 
@@ -178,10 +179,9 @@
 
     void onAnimationFinished() {
         // Done animating, clean up.
-        if (DEBUG_ANIM) Slog.v(
-                TAG, "Animation done in " + this + ": exiting=" + mWin.mAnimatingExit
-                        + ", reportedVisible="
-                        + (mWin.mActivityRecord != null && mWin.mActivityRecord.reportedVisible));
+        ProtoLog.v(WM_DEBUG_ANIM, "Animation done in %s: exiting=%b, reportedVisible=%b",
+                this, mWin.mAnimatingExit,
+                (mWin.mActivityRecord != null && mWin.mActivityRecord.reportedVisible));
 
         mWin.checkPolicyVisibilityChange();
         final DisplayContent displayContent = mWin.getDisplayContent();
@@ -264,9 +264,8 @@
         if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) {
             return false;
         }
-        if (DEBUG_ANIM) {
-            Slog.i(TAG, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW " + mSurfaceController);
-        }
+        ProtoLog.i(WM_DEBUG_ANIM, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s",
+                mSurfaceController);
         mDrawState = READY_TO_SHOW;
         boolean result = false;
         final ActivityRecord activity = mWin.mActivityRecord;
@@ -298,9 +297,7 @@
 
         w.setHasSurface(false);
 
-        if (DEBUG_ANIM) {
-            Slog.i(TAG, "createSurface " + this + ": mDrawState=DRAW_PENDING");
-        }
+        ProtoLog.i(WM_DEBUG_ANIM, "createSurface %s: mDrawState=DRAW_PENDING", this);
 
         resetDrawState();
 
@@ -503,8 +500,8 @@
                 }
             }
         } else {
-            if (DEBUG_ANIM && mWin.isAnimating(TRANSITION | PARENTS)) {
-                Slog.v(TAG, "prepareSurface: No changes in animation for " + this);
+            if (mWin.isAnimating(TRANSITION | PARENTS)) {
+                ProtoLog.v(WM_DEBUG_ANIM, "prepareSurface: No changes in animation for %s", this);
             }
         }
 
@@ -646,15 +643,12 @@
                             mWin.mAttrs, attr, TRANSIT_OLD_NONE);
                 }
             }
-            if (DEBUG_ANIM) Slog.v(TAG,
-                    "applyAnimation: win=" + this
-                    + " anim=" + anim + " attr=0x" + Integer.toHexString(attr)
-                    + " a=" + a
-                    + " transit=" + transit
-                    + " type=" + mAttrType
-                    + " isEntrance=" + isEntrance + " Callers " + Debug.getCallers(3));
+            if (ProtoLogImpl.isEnabled(WM_DEBUG_ANIM)) {
+                ProtoLog.v(WM_DEBUG_ANIM, "applyAnimation: win=%s"
+                        + " anim=%d attr=0x%x a=%s transit=%d type=%d isEntrance=%b Callers %s",
+                        this, anim, attr, a, transit, mAttrType, isEntrance, Debug.getCallers(20));
+            }
             if (a != null) {
-                if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + this);
                 Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#startAnimation");
                 mWin.startAnimation(a);
                 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java
index bf78c67..82fe8b9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java
@@ -16,15 +16,12 @@
 
 package com.android.server.devicepolicy;
 
-import static android.app.admin.DevicePolicyResources.Drawables.Style;
-
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.admin.DevicePolicyDrawableResource;
 import android.app.admin.DevicePolicyResources;
-import android.app.admin.DevicePolicyResources.Drawables;
 import android.app.admin.DevicePolicyStringResource;
 import android.app.admin.ParcelableResource;
 import android.os.Environment;
@@ -180,16 +177,18 @@
     @Nullable
     ParcelableResource getDrawable(
             String drawableId, String drawableStyle, String drawableSource) {
-        if (mUpdatedDrawablesForSource.containsKey(drawableId)
-                && mUpdatedDrawablesForSource.get(drawableId).containsKey(drawableSource)) {
-            return mUpdatedDrawablesForSource.get(drawableId).get(drawableSource);
-        }
-        if (!mUpdatedDrawablesForStyle.containsKey(drawableId)) {
-            Log.d(TAG, "No updated drawable found for drawable id " + drawableId);
-            return null;
-        }
-        if (mUpdatedDrawablesForStyle.get(drawableId).containsKey(drawableStyle)) {
-            return mUpdatedDrawablesForStyle.get(drawableId).get(drawableStyle);
+        synchronized (mLock) {
+            if (mUpdatedDrawablesForSource.containsKey(drawableId)
+                    && mUpdatedDrawablesForSource.get(drawableId).containsKey(drawableSource)) {
+                return mUpdatedDrawablesForSource.get(drawableId).get(drawableSource);
+            }
+            if (!mUpdatedDrawablesForStyle.containsKey(drawableId)) {
+                Log.d(TAG, "No updated drawable found for drawable id " + drawableId);
+                return null;
+            }
+            if (mUpdatedDrawablesForStyle.get(drawableId).containsKey(drawableStyle)) {
+                return mUpdatedDrawablesForStyle.get(drawableId).get(drawableStyle);
+            }
         }
         Log.d(TAG, "No updated drawable found for drawable id " + drawableId);
         return null;
@@ -249,10 +248,11 @@
 
     @Nullable
     ParcelableResource getString(String stringId) {
-        if (mUpdatedStrings.containsKey(stringId)) {
-            return mUpdatedStrings.get(stringId);
+        synchronized (mLock) {
+            if (mUpdatedStrings.containsKey(stringId)) {
+                return mUpdatedStrings.get(stringId);
+            }
         }
-
         Log.d(TAG, "No updated string found for string id " + stringId);
         return null;
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 35f8402..c5bb1bf 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -114,6 +114,8 @@
 import static android.app.admin.DevicePolicyResources.Strings.Core.NETWORK_LOGGING_MESSAGE;
 import static android.app.admin.DevicePolicyResources.Strings.Core.NETWORK_LOGGING_TITLE;
 import static android.app.admin.DevicePolicyResources.Strings.Core.NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_SOON_MESSAGE;
 import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_TITLE;
 import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE;
 import static android.app.admin.DevicePolicyResources.Strings.Core.PRINTING_DISABLED_NAMED_ADMIN;
@@ -172,7 +174,6 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityTaskManager;
-import android.app.ActivityThread;
 import android.app.AlarmManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
@@ -7030,14 +7031,13 @@
 
     private String getGenericWipeReason(
             boolean calledByProfileOwnerOnOrgOwnedDevice, boolean calledOnParentInstance) {
-        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
         return calledByProfileOwnerOnOrgOwnedDevice && !calledOnParentInstance
-                ? dpm.getResources().getString(WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE,
-                        () -> mContext.getString(
-                                R.string.device_ownership_relinquished))
-                : dpm.getResources().getString(WORK_PROFILE_DELETED_GENERIC_MESSAGE,
-                        () -> mContext.getString(
-                                R.string.work_profile_deleted_description_dpm_wipe));
+                ? getUpdatableString(
+                        WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE,
+                        R.string.device_ownership_relinquished)
+                : getUpdatableString(
+                        WORK_PROFILE_DELETED_GENERIC_MESSAGE,
+                        R.string.work_profile_deleted_description_dpm_wipe);
     }
 
     /**
@@ -7133,9 +7133,7 @@
     }
 
     private String getWorkProfileDeletedTitle() {
-        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-        return dpm.getResources().getString(WORK_PROFILE_DELETED_TITLE,
-                () -> mContext.getString(R.string.work_profile_deleted));
+        return getUpdatableString(WORK_PROFILE_DELETED_TITLE, R.string.work_profile_deleted);
     }
 
     private void clearWipeProfileNotification() {
@@ -7449,10 +7447,9 @@
     }
 
     private String getFailedPasswordAttemptWipeMessage() {
-        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-        return dpm.getResources().getString(WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE,
-                () -> mContext.getString(
-                        R.string.work_profile_deleted_reason_maximum_password_failure));
+        return getUpdatableString(
+                WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE,
+               R.string.work_profile_deleted_reason_maximum_password_failure);
     }
 
     /**
@@ -12649,15 +12646,13 @@
     }
 
     private String getLocationChangedTitle() {
-        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-        return dpm.getResources().getString(LOCATION_CHANGED_TITLE,
-                () -> mContext.getString(R.string.location_changed_notification_title));
+        return getUpdatableString(
+                LOCATION_CHANGED_TITLE, R.string.location_changed_notification_title);
     }
 
     private String getLocationChangedText() {
-        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-        return dpm.getResources().getString(LOCATION_CHANGED_MESSAGE,
-                () -> mContext.getString(R.string.location_changed_notification_text));
+        return getUpdatableString(
+                LOCATION_CHANGED_MESSAGE, R.string.location_changed_notification_text);
     }
 
     @Override
@@ -13256,19 +13251,13 @@
                     Slogf.e(LOG_TAG, "appLabel is inexplicably null");
                     return null;
                 }
-                DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-                return dpm.getResources().getString(
+                return getUpdatableString(
                         PRINTING_DISABLED_NAMED_ADMIN,
-                        () -> getDefaultPrintingDisabledMsg(appLabel),
+                        R.string.printing_disabled_by,
                         appLabel);
             }
         }
 
-        private String getDefaultPrintingDisabledMsg(CharSequence appLabel) {
-            return ((Context) ActivityThread.currentActivityThread().getSystemUiContext())
-                        .getResources().getString(R.string.printing_disabled_by, appLabel);
-        }
-
         @Override
         protected DevicePolicyCache getDevicePolicyCache() {
             return mPolicyCache;
@@ -15875,15 +15864,13 @@
     }
 
     private String getNetworkLoggingTitle() {
-        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-        return dpm.getResources().getString(NETWORK_LOGGING_TITLE,
-                () -> mContext.getString(R.string.network_logging_notification_title));
+        return getUpdatableString(
+                NETWORK_LOGGING_TITLE, R.string.network_logging_notification_title);
     }
 
     private String getNetworkLoggingText() {
-        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-        return dpm.getResources().getString(NETWORK_LOGGING_MESSAGE,
-                () -> mContext.getString(R.string.network_logging_notification_text));
+        return getUpdatableString(
+                NETWORK_LOGGING_MESSAGE, R.string.network_logging_notification_text);
     }
 
     private void handleCancelNetworkLoggingNotification() {
@@ -17470,35 +17457,31 @@
     }
 
     private String getPersonalAppSuspensionButtonText() {
-        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-        return dpm.getResources().getString(PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE,
-                () -> mContext.getString(R.string.personal_apps_suspended_turn_profile_on));
+        return getUpdatableString(
+                PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE,
+                R.string.personal_apps_suspended_turn_profile_on);
     }
 
     private String getPersonalAppSuspensionTitle() {
-        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-        return dpm.getResources().getString(PERSONAL_APP_SUSPENSION_TITLE,
-                () -> mContext.getString(R.string.personal_apps_suspension_title));
+        return getUpdatableString(
+                PERSONAL_APP_SUSPENSION_TITLE, R.string.personal_apps_suspension_title);
     }
 
     private String getPersonalAppSuspensionText() {
-        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-        return dpm.getResources().getString(PERSONAL_APP_SUSPENSION_TITLE,
-                () -> mContext.getString(R.string.personal_apps_suspension_text));
+        return getUpdatableString(
+                PERSONAL_APP_SUSPENSION_MESSAGE, R.string.personal_apps_suspension_text);
     }
 
     private String getPersonalAppSuspensionSoonText(String date, String time, int maxDays) {
-        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-        return dpm.getResources().getString(PERSONAL_APP_SUSPENSION_TITLE,
-                () -> mContext.getString(
-                        R.string.personal_apps_suspension_soon_text, date, time, maxDays),
+        return getUpdatableString(
+                PERSONAL_APP_SUSPENSION_SOON_MESSAGE, R.string.personal_apps_suspension_soon_text,
                 date, time, maxDays);
     }
 
     private String getWorkProfileContentDescription() {
-        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-        return dpm.getResources().getString(NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION,
-                () -> mContext.getString(R.string.notification_work_profile_content_description));
+        return getUpdatableString(
+                NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION,
+                R.string.notification_work_profile_content_description);
     }
 
     @Override
@@ -18802,6 +18785,18 @@
         }
     }
 
+    private String getUpdatableString(
+            String updatableStringId, int defaultStringId, Object... formatArgs) {
+        ParcelableResource resource = mDeviceManagementResourcesProvider.getString(
+                updatableStringId);
+        if (resource == null) {
+            return ParcelableResource.loadDefaultString(() ->
+                    mContext.getString(defaultStringId, formatArgs));
+        }
+        return resource.getString(
+                mContext, () -> mContext.getString(defaultStringId, formatArgs), formatArgs);
+    }
+
     public boolean isDpcDownloaded() {
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
                 android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f7c66c5..73f0a74 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1486,8 +1486,9 @@
 
             // TelecomLoader hooks into classes with defined HFP logic,
             // so check for either telephony or microphone.
-            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE) ||
-                    mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
+                    || mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELECOM)
+                    || mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
                 t.traceBegin("StartTelecomLoaderService");
                 mSystemServiceManager.startService(TelecomLoaderService.class);
                 t.traceEnd();
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index f9bdad6..296e8a3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -30,7 +30,6 @@
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -56,11 +55,6 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.platform.test.annotations.LargeTest;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseLongArray;
 
 import com.android.server.AppStateTracker;
 import com.android.server.AppStateTrackerImpl;
@@ -82,16 +76,10 @@
 import java.time.Clock;
 import java.time.Duration;
 import java.time.ZoneOffset;
-import java.util.Random;
 
 public class JobSchedulerServiceTest {
     private static final String TAG = JobSchedulerServiceTest.class.getSimpleName();
 
-    private static final int[] sRegJobPriorities = {
-            JobInfo.PRIORITY_HIGH, JobInfo.PRIORITY_DEFAULT,
-            JobInfo.PRIORITY_LOW, JobInfo.PRIORITY_MIN
-    };
-
     private JobSchedulerService mService;
 
     private MockitoSession mMockingSession;
@@ -769,7 +757,7 @@
         job.setStandbyBucket(RARE_INDEX);
 
         // Not enough RARE jobs to run.
-        mService.mPendingJobs.clear();
+        mService.mPendingJobQueue.clear();
         maybeQueueFunctor.reset();
         for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
             maybeQueueFunctor.accept(job);
@@ -778,10 +766,10 @@
             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
         }
         maybeQueueFunctor.postProcessLocked();
-        assertEquals(0, mService.mPendingJobs.size());
+        assertEquals(0, mService.mPendingJobQueue.size());
 
         // Enough RARE jobs to run.
-        mService.mPendingJobs.clear();
+        mService.mPendingJobQueue.clear();
         maybeQueueFunctor.reset();
         for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT; ++i) {
             maybeQueueFunctor.accept(job);
@@ -790,10 +778,10 @@
             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
         }
         maybeQueueFunctor.postProcessLocked();
-        assertEquals(5, mService.mPendingJobs.size());
+        assertEquals(5, mService.mPendingJobQueue.size());
 
         // Not enough RARE jobs to run, but a non-batched job saves the day.
-        mService.mPendingJobs.clear();
+        mService.mPendingJobQueue.clear();
         maybeQueueFunctor.reset();
         JobStatus activeJob = createJobStatus(
                 "testRareJobBatching",
@@ -807,10 +795,10 @@
         }
         maybeQueueFunctor.accept(activeJob);
         maybeQueueFunctor.postProcessLocked();
-        assertEquals(3, mService.mPendingJobs.size());
+        assertEquals(3, mService.mPendingJobQueue.size());
 
         // Not enough RARE jobs to run, but an old RARE job saves the day.
-        mService.mPendingJobs.clear();
+        mService.mPendingJobQueue.clear();
         maybeQueueFunctor.reset();
         JobStatus oldRareJob = createJobStatus("testRareJobBatching", createJobInfo());
         oldRareJob.setStandbyBucket(RARE_INDEX);
@@ -826,7 +814,7 @@
         maybeQueueFunctor.accept(oldRareJob);
         assertEquals(oldBatchTime, oldRareJob.getFirstForceBatchedTimeElapsed());
         maybeQueueFunctor.postProcessLocked();
-        assertEquals(3, mService.mPendingJobs.size());
+        assertEquals(3, mService.mPendingJobQueue.size());
     }
 
     /** Tests that jobs scheduled by the app itself are counted towards scheduling limits. */
@@ -914,358 +902,4 @@
                             0, ""));
         }
     }
-
-    @Test
-    public void testPendingJobSorting() {
-        // First letter in job variable name indicate regular (r) or expedited (e).
-        // Capital letters in job variable name indicate the app/UID.
-        // Numbers in job variable name indicate the enqueue time.
-        // Expected sort order:
-        //   eA7 > rA1 > eB6 > rB2 > eC3 > rD4 > eE5 > eF9 > rF8 > eC11 > rC10 > rG12 > rG13 > eE14
-        // Intentions:
-        //   * A jobs let us test skipping both regular and expedited jobs of other apps
-        //   * B jobs let us test skipping only regular job of another app without going too far
-        //   * C jobs test that regular jobs don't skip over other app's jobs and that EJs only
-        //     skip up to level of the earliest regular job
-        //   * E jobs test that expedited jobs don't skip the line when the app has no regular jobs
-        //   * F jobs test correct expedited/regular ordering doesn't push jobs too high in list
-        //   * G jobs test correct ordering for regular jobs
-        //   * H job tests correct behavior when enqueue times are the same
-        JobStatus rA1 = createJobStatus("testPendingJobSorting", createJobInfo(1), 1);
-        JobStatus rB2 = createJobStatus("testPendingJobSorting", createJobInfo(2), 2);
-        JobStatus eC3 = createJobStatus("testPendingJobSorting",
-                createJobInfo(3).setExpedited(true), 3);
-        JobStatus rD4 = createJobStatus("testPendingJobSorting", createJobInfo(4), 4);
-        JobStatus eE5 = createJobStatus("testPendingJobSorting",
-                createJobInfo(5).setExpedited(true), 5);
-        JobStatus eB6 = createJobStatus("testPendingJobSorting",
-                createJobInfo(6).setExpedited(true), 2);
-        JobStatus eA7 = createJobStatus("testPendingJobSorting",
-                createJobInfo(7).setExpedited(true), 1);
-        JobStatus rH8 = createJobStatus("testPendingJobSorting", createJobInfo(8), 8);
-        JobStatus rF8 = createJobStatus("testPendingJobSorting", createJobInfo(8), 6);
-        JobStatus eF9 = createJobStatus("testPendingJobSorting",
-                createJobInfo(9).setExpedited(true), 6);
-        JobStatus rC10 = createJobStatus("testPendingJobSorting", createJobInfo(10), 3);
-        JobStatus eC11 = createJobStatus("testPendingJobSorting",
-                createJobInfo(11).setExpedited(true), 3);
-        JobStatus rG12 = createJobStatus("testPendingJobSorting", createJobInfo(12), 7);
-        JobStatus rG13 = createJobStatus("testPendingJobSorting", createJobInfo(13), 7);
-        JobStatus eE14 = createJobStatus("testPendingJobSorting",
-                createJobInfo(14).setExpedited(true), 5);
-
-        rA1.enqueueTime = 10;
-        rB2.enqueueTime = 20;
-        eC3.enqueueTime = 30;
-        rD4.enqueueTime = 40;
-        eE5.enqueueTime = 50;
-        eB6.enqueueTime = 60;
-        eA7.enqueueTime = 70;
-        rF8.enqueueTime = 80;
-        rH8.enqueueTime = 80;
-        eF9.enqueueTime = 90;
-        rC10.enqueueTime = 100;
-        eC11.enqueueTime = 110;
-        rG12.enqueueTime = 120;
-        rG13.enqueueTime = 130;
-        eE14.enqueueTime = 140;
-
-        mService.mPendingJobs.clear();
-        // Add in random order so sorting is apparent.
-        mService.mPendingJobs.add(eC3);
-        mService.mPendingJobs.add(eE5);
-        mService.mPendingJobs.add(rA1);
-        mService.mPendingJobs.add(rG13);
-        mService.mPendingJobs.add(rD4);
-        mService.mPendingJobs.add(eA7);
-        mService.mPendingJobs.add(rG12);
-        mService.mPendingJobs.add(rH8);
-        mService.mPendingJobs.add(rF8);
-        mService.mPendingJobs.add(eB6);
-        mService.mPendingJobs.add(eE14);
-        mService.mPendingJobs.add(eF9);
-        mService.mPendingJobs.add(rB2);
-        mService.mPendingJobs.add(rC10);
-        mService.mPendingJobs.add(eC11);
-
-        mService.mPendingJobComparator.refreshLocked();
-        mService.mPendingJobs.sort(mService.mPendingJobComparator);
-
-        final JobStatus[] expectedOrder = new JobStatus[]{
-                eA7, rA1, eB6, rB2, eC3, rD4, eE5, eF9, rH8, rF8, eC11, rC10, rG12, rG13, eE14};
-        for (int i = 0; i < expectedOrder.length; ++i) {
-            assertEquals("List wasn't correctly sorted @ index " + i,
-                    expectedOrder[i].getJobId(), mService.mPendingJobs.get(i).getJobId());
-        }
-    }
-
-    private void checkPendingJobInvariants() {
-        final SparseBooleanArray regJobSeen = new SparseBooleanArray();
-        // Latest priority enqueue times seen for each priority for each app.
-        final SparseArray<SparseLongArray> latestPriorityRegEnqueueTimesPerUid =
-                new SparseArray<>();
-        final SparseArray<SparseLongArray> latestPriorityEjEnqueueTimesPerUid = new SparseArray<>();
-        final long noEntry = -1;
-
-        for (int i = 0; i < mService.mPendingJobs.size(); ++i) {
-            final JobStatus job = mService.mPendingJobs.get(i);
-            final int uid = job.getSourceUid();
-
-            // Invariant #1: All jobs (for a UID) are sorted by priority order
-            // Invariant #2: Jobs (for a UID) with the same priority are sorted by enqueue time.
-            // Invariant #3: EJs (for a UID) should be before regular jobs
-
-            final int priority = job.getEffectivePriority();
-            final SparseArray<SparseLongArray> latestPriorityEnqueueTimesPerUid =
-                    job.isRequestedExpeditedJob()
-                            ? latestPriorityEjEnqueueTimesPerUid
-                            : latestPriorityRegEnqueueTimesPerUid;
-            SparseLongArray latestPriorityEnqueueTimes = latestPriorityEnqueueTimesPerUid.get(uid);
-            if (latestPriorityEnqueueTimes != null) {
-                // Invariant 1
-                for (int p = priority - 1; p >= JobInfo.PRIORITY_MIN; --p) {
-                    // If we haven't seen the priority, there shouldn't be an entry in the array.
-                    assertEquals("Jobs not properly sorted by priority for uid " + uid,
-                            noEntry, latestPriorityEnqueueTimes.get(p, noEntry));
-                }
-
-                // Invariant 2
-                final long lastSeenPriorityEnqueueTime =
-                        latestPriorityEnqueueTimes.get(priority, noEntry);
-                if (lastSeenPriorityEnqueueTime != noEntry) {
-                    assertTrue("Jobs with same priority not sorted by enqueue time: "
-                                    + lastSeenPriorityEnqueueTime + " vs " + job.enqueueTime,
-                            lastSeenPriorityEnqueueTime <= job.enqueueTime);
-                }
-            } else {
-                latestPriorityEnqueueTimes = new SparseLongArray();
-                latestPriorityEnqueueTimesPerUid.put(uid, latestPriorityEnqueueTimes);
-            }
-            latestPriorityEnqueueTimes.put(priority, job.enqueueTime);
-
-            // Invariant 3
-            if (!job.isRequestedExpeditedJob()) {
-                regJobSeen.put(uid, true);
-            } else if (regJobSeen.get(uid)) {
-                fail("UID " + uid + " had an EJ ordered after a regular job");
-            }
-        }
-    }
-
-    private static String sortedJobToString(JobStatus job) {
-        return "testJob " + job.getSourceUid() + "/" + job.getJobId()
-                + "/p" + job.getEffectivePriority()
-                + "/" + job.isRequestedExpeditedJob() + "@" + job.enqueueTime;
-    }
-
-    @Test
-    public void testPendingJobSorting_Random() {
-        Random random = new Random(1); // Always use the same series of pseudo random values.
-
-        mService.mPendingJobs.clear();
-
-        for (int i = 0; i < 5000; ++i) {
-            JobStatus job = createJobStatus("testPendingJobSorting_Random",
-                    createJobInfo(i).setExpedited(random.nextBoolean()), random.nextInt(250));
-            job.enqueueTime = random.nextInt(1_000_000);
-            mService.mPendingJobs.add(job);
-        }
-
-        mService.mPendingJobComparator.refreshLocked();
-        try {
-            mService.mPendingJobs.sort(mService.mPendingJobComparator);
-        } catch (Exception e) {
-            for (JobStatus toDump : mService.mPendingJobs) {
-                Log.i(TAG, sortedJobToString(toDump));
-            }
-            throw e;
-        }
-        checkPendingJobInvariants();
-    }
-
-    private int sign(int i) {
-        if (i > 0) {
-            return 1;
-        }
-        if (i < 0) {
-            return -1;
-        }
-        return 0;
-    }
-
-    @Test
-    public void testPendingJobSortingTransitivity() {
-        // Always use the same series of pseudo random values.
-        for (int seed : new int[]{1337, 7357, 606, 6357, 41106010, 3, 2, 1}) {
-            Random random = new Random(seed);
-
-            mService.mPendingJobs.clear();
-
-            for (int i = 0; i < 300; ++i) {
-                JobStatus job = createJobStatus("testPendingJobSortingTransitivity",
-                        createJobInfo(i).setExpedited(random.nextBoolean()), random.nextInt(50));
-                job.enqueueTime = random.nextInt(1_000_000);
-                job.overrideState = random.nextInt(4);
-                mService.mPendingJobs.add(job);
-            }
-
-            verifyPendingJobComparatorTransitivity();
-        }
-    }
-
-    @Test
-    @LargeTest
-    public void testPendingJobSortingTransitivity_Concentrated() {
-        // Always use the same series of pseudo random values.
-        for (int seed : new int[]{1337, 6000, 637739, 6357, 1, 7, 13}) {
-            Random random = new Random(seed);
-
-            mService.mPendingJobs.clear();
-
-            for (int i = 0; i < 300; ++i) {
-                JobStatus job = createJobStatus("testPendingJobSortingTransitivity_Concentrated",
-                        createJobInfo(i).setExpedited(random.nextFloat() < .03),
-                        random.nextInt(20));
-                job.enqueueTime = random.nextInt(250);
-                job.overrideState = random.nextFloat() < .01
-                        ? JobStatus.OVERRIDE_SORTING : JobStatus.OVERRIDE_NONE;
-                mService.mPendingJobs.add(job);
-                Log.d(TAG, sortedJobToString(job));
-            }
-
-            verifyPendingJobComparatorTransitivity();
-        }
-    }
-
-    @Test
-    public void testPendingJobSorting_Random_WithPriority() {
-        Random random = new Random(1); // Always use the same series of pseudo random values.
-
-        mService.mPendingJobs.clear();
-
-        for (int i = 0; i < 5000; ++i) {
-            final boolean isEj = random.nextBoolean();
-            final int priority;
-            if (isEj) {
-                priority = random.nextBoolean() ? JobInfo.PRIORITY_MAX : JobInfo.PRIORITY_HIGH;
-            } else {
-                priority = sRegJobPriorities[random.nextInt(sRegJobPriorities.length)];
-            }
-            JobStatus job = createJobStatus("testPendingJobSorting_Random_WithPriority",
-                    createJobInfo(i).setExpedited(isEj).setPriority(priority),
-                    random.nextInt(250));
-            job.enqueueTime = random.nextInt(1_000_000);
-            mService.mPendingJobs.add(job);
-        }
-
-        mService.mPendingJobComparator.refreshLocked();
-        try {
-            mService.mPendingJobs.sort(mService.mPendingJobComparator);
-        } catch (Exception e) {
-            for (JobStatus toDump : mService.mPendingJobs) {
-                Log.i(TAG, sortedJobToString(toDump));
-            }
-            throw e;
-        }
-        checkPendingJobInvariants();
-    }
-
-    @Test
-    public void testPendingJobSortingTransitivity_WithPriority() {
-        // Always use the same series of pseudo random values.
-        for (int seed : new int[]{1337, 7357, 606, 6357, 41106010, 3, 2, 1}) {
-            Random random = new Random(seed);
-
-            mService.mPendingJobs.clear();
-
-            for (int i = 0; i < 300; ++i) {
-                final boolean isEj = random.nextBoolean();
-                final int priority;
-                if (isEj) {
-                    priority = random.nextBoolean() ? JobInfo.PRIORITY_MAX : JobInfo.PRIORITY_HIGH;
-                } else {
-                    priority = sRegJobPriorities[random.nextInt(sRegJobPriorities.length)];
-                }
-                JobStatus job = createJobStatus("testPendingJobSortingTransitivity_WithPriority",
-                        createJobInfo(i).setExpedited(isEj).setPriority(priority),
-                        random.nextInt(50));
-                job.enqueueTime = random.nextInt(1_000_000);
-                job.overrideState = random.nextInt(4);
-                mService.mPendingJobs.add(job);
-            }
-
-            verifyPendingJobComparatorTransitivity();
-        }
-    }
-
-    @Test
-    @LargeTest
-    public void testPendingJobSortingTransitivity_Concentrated_WithPriority() {
-        // Always use the same series of pseudo random values.
-        for (int seed : new int[]{1337, 6000, 637739, 6357, 1, 7, 13}) {
-            Random random = new Random(seed);
-
-            mService.mPendingJobs.clear();
-
-            for (int i = 0; i < 300; ++i) {
-                final boolean isEj = random.nextFloat() < .03;
-                final int priority;
-                if (isEj) {
-                    priority = random.nextBoolean() ? JobInfo.PRIORITY_MAX : JobInfo.PRIORITY_HIGH;
-                } else {
-                    priority = sRegJobPriorities[random.nextInt(sRegJobPriorities.length)];
-                }
-                JobStatus job = createJobStatus(
-                        "testPendingJobSortingTransitivity_Concentrated_WithPriority",
-                        createJobInfo(i).setExpedited(isEj).setPriority(priority),
-                        random.nextInt(20));
-                job.enqueueTime = random.nextInt(250);
-                job.overrideState = random.nextFloat() < .01
-                        ? JobStatus.OVERRIDE_SORTING : JobStatus.OVERRIDE_NONE;
-                mService.mPendingJobs.add(job);
-                Log.d(TAG, sortedJobToString(job));
-            }
-
-            verifyPendingJobComparatorTransitivity();
-        }
-    }
-
-    private void verifyPendingJobComparatorTransitivity() {
-        mService.mPendingJobComparator.refreshLocked();
-
-        for (int i = 0; i < mService.mPendingJobs.size(); ++i) {
-            final JobStatus job1 = mService.mPendingJobs.get(i);
-
-            for (int j = 0; j < mService.mPendingJobs.size(); ++j) {
-                final JobStatus job2 = mService.mPendingJobs.get(j);
-                final int sign12 = sign(mService.mPendingJobComparator.compare(job1, job2));
-                final int sign21 = sign(mService.mPendingJobComparator.compare(job2, job1));
-                if (sign12 != -sign21) {
-                    final String job1String = sortedJobToString(job1);
-                    final String job2String = sortedJobToString(job2);
-                    fail("compare(" + job1String + ", " + job2String + ") != "
-                            + "-compare(" + job2String + ", " + job1String + ")");
-                }
-
-                for (int k = 0; k < mService.mPendingJobs.size(); ++k) {
-                    final JobStatus job3 = mService.mPendingJobs.get(k);
-                    final int sign23 = sign(mService.mPendingJobComparator.compare(job2, job3));
-                    final int sign13 = sign(mService.mPendingJobComparator.compare(job1, job3));
-
-                    // Confirm 1 < 2 < 3 or 1 > 2 > 3 or 1 == 2 == 3
-                    if ((sign12 == sign23 && sign12 != sign13)
-                            // Confirm that if 1 == 2, then (1 < 3 AND 2 < 3) OR (1 > 3 && 2 > 3)
-                            || (sign12 == 0 && sign13 != sign23)) {
-                        final String job1String = sortedJobToString(job1);
-                        final String job2String = sortedJobToString(job2);
-                        final String job3String = sortedJobToString(job3);
-                        fail("Transitivity fail"
-                                + ": compare(" + job1String + ", " + job2String + ")=" + sign12
-                                + ", compare(" + job2String + ", " + job3String + ")=" + sign23
-                                + ", compare(" + job1String + ", " + job3String + ")=" + sign13);
-                    }
-                }
-            }
-        }
-    }
 }
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index a6194df..449177e 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -100,7 +100,7 @@
         android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
     <uses-permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY" />
     <uses-permission android:name="android.permission.READ_NEARBY_STREAMING_POLICY" />
-
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
     <uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" />
 
     <queries>
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
index c0ba3c7..5b6aebc 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
@@ -45,6 +45,7 @@
     private Context mContext;
     private AudioSystemAdapter mAudioSystem;
     @Spy private SystemServerAdapter mSpySystemServer;
+    private SettingsAdapter mSettingsAdapter;
     // the class being unit-tested here
     private AudioService mAudioService;
 
@@ -59,7 +60,9 @@
         mContext = InstrumentationRegistry.getTargetContext();
         mAudioSystem = new NoOpAudioSystemAdapter();
         mSpySystemServer = spy(new NoOpSystemServerAdapter());
-        mAudioService = new AudioService(mContext, mAudioSystem, mSpySystemServer);
+        mSettingsAdapter = new NoOpSettingsAdapter();
+        mAudioService = new AudioService(mContext, mAudioSystem, mSpySystemServer,
+                mSettingsAdapter);
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpSettingsAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpSettingsAdapter.java
new file mode 100644
index 0000000..bf80f27b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/audio/NoOpSettingsAdapter.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.audio;
+
+import android.content.ContentResolver;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class NoOpSettingsAdapter extends SettingsAdapter {
+
+    /**
+     * No-op methods for Settings.Global
+     */
+
+    private Map<String, Integer> mGlobalIntSettings = new HashMap<>();
+    private Map<String, String> mGlobalStringSettings = new HashMap<>();
+
+    @Override
+    public int getGlobalInt(ContentResolver cr, String name, int def) {
+        return mGlobalIntSettings.getOrDefault(name, def);
+    }
+
+    @Override
+    public String getGlobalString(ContentResolver resolver, String name) {
+        return mGlobalStringSettings.getOrDefault(name, null);
+    }
+
+    @Override
+    public boolean putGlobalInt(ContentResolver cr, String name, int value) {
+        mGlobalIntSettings.put(name, value);
+        return true;
+    }
+
+    @Override
+    public boolean putGlobalString(ContentResolver resolver, String name, String value) {
+        mGlobalStringSettings.put(name, value);
+        return true;
+    }
+
+    /**
+     * No-op methods for Settings.System
+     */
+
+    private Map<String, Integer> mSystemIntSettings = new HashMap<>();
+
+    @Override
+    public int getSystemIntForUser(ContentResolver cr, String name, int def, int userHandle) {
+        return mSystemIntSettings.getOrDefault(name, def);
+    }
+
+    @Override
+    public boolean putSystemIntForUser(ContentResolver cr, String name, int value, int userHandle) {
+        mSystemIntSettings.put(name, value);
+        return true;
+    }
+
+    /**
+     * No-op methods for Settings.Secure
+     */
+
+    private Map<String, Integer> mSecureIntSettings = new HashMap<>();
+    private Map<String, String> mSecureStringSettings = new HashMap<>();
+
+    @Override
+    public int getSecureIntForUser(ContentResolver cr, String name, int def, int userHandle) {
+        return mSecureIntSettings.getOrDefault(name, def);
+    }
+
+    @Override
+    public String getSecureStringForUser(ContentResolver resolver, String name, int userHandle) {
+        return mSecureStringSettings.getOrDefault(name, null);
+    }
+
+    @Override
+    public boolean putSecureIntForUser(ContentResolver cr, String name, int value, int userHandle) {
+        mSecureIntSettings.put(name, value);
+        return true;
+    }
+
+    @Override
+    public boolean putSecureStringForUser(ContentResolver cr, String name, String value,
+            int userHandle) {
+        mSecureStringSettings.put(name, value);
+        return true;
+    }
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 61d7ede..ef9f90f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -530,5 +530,10 @@
 
             return true;
         }
+
+        @Override
+        public Context createContextAsUser(UserHandle user) {
+            return context;
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 89bd10f..484dc84 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -158,7 +158,6 @@
 import org.hamcrest.Matcher;
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.mockito.Mockito;
 import org.mockito.internal.util.collections.Sets;
@@ -187,7 +186,6 @@
  */
 @SmallTest
 @Presubmit
-@Ignore("b/225415867")
 public class DevicePolicyManagerTest extends DpmTestBase {
 
     private static final String TAG = DevicePolicyManagerTest.class.getSimpleName();
@@ -1786,10 +1784,9 @@
         final int userId = CALLER_USER_HANDLE;
         final UserHandle user = UserHandle.of(userId);
 
-        mContext.applicationInfo = new ApplicationInfo();
-        mContext.callerPermissions.add(permission.MANAGE_USERS);
-        mContext.packageName = "com.android.frameworks.servicestests";
-        getServices().addPackageContext(user, mContext);
+        mServiceContext.packageName = mRealTestContext.getPackageName();
+        mServiceContext.applicationInfo = new ApplicationInfo();
+        mServiceContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
 
         StringParceledListSlice oneCert = asSlice(new String[] {"1"});
@@ -6281,6 +6278,7 @@
     @Test
     public void testGetOwnerInstalledCaCertsForDeviceOwner() throws Exception {
         mServiceContext.packageName = mRealTestContext.getPackageName();
+        mServiceContext.applicationInfo = new ApplicationInfo();
         mServiceContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         mAdmin1Context.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setDeviceOwner();
@@ -6291,6 +6289,7 @@
     @Test
     public void testGetOwnerInstalledCaCertsForProfileOwner() throws Exception {
         mServiceContext.packageName = mRealTestContext.getPackageName();
+        mServiceContext.applicationInfo = new ApplicationInfo();
         mServiceContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         mAdmin1Context.binder.callingUid = DpmMockContext.CALLER_UID;
         setAsProfileOwner(admin1);
@@ -6302,6 +6301,7 @@
     @Test
     public void testGetOwnerInstalledCaCertsForDelegate() throws Exception {
         mServiceContext.packageName = mRealTestContext.getPackageName();
+        mServiceContext.applicationInfo = new ApplicationInfo();
         mServiceContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         mAdmin1Context.binder.callingUid = DpmMockContext.CALLER_UID;
         setAsProfileOwner(admin1);
diff --git a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
index 150e3c6..12fc958 100644
--- a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
@@ -28,7 +28,6 @@
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
 import android.util.SparseLongArray;
 
 import com.android.server.job.controllers.JobStatus;
@@ -212,13 +211,26 @@
         jobQueue.add(eC11);
 
         checkPendingJobInvariants(jobQueue);
-        final JobStatus[] expectedOrder = new JobStatus[]{
-                eA7, rA1, eB6, rB2, eC3, rD4, eE5, eF9, rH8, rF8, eC11, rC10, rG12, rG13, eE14};
-        int idx = 0;
         JobStatus job;
+        final JobStatus[] expectedPureOrder = new JobStatus[]{
+                eC3, rD4, eE5, eB6, rB2, eA7, rA1, rH8, eF9, rF8, eC11, rC10, rG12, rG13, eE14};
+        int idx = 0;
+        jobQueue.setOptimizeIteration(false);
+        jobQueue.resetIterator();
         while ((job = jobQueue.next()) != null) {
             assertEquals("List wasn't correctly sorted @ index " + idx,
-                    expectedOrder[idx].getJobId(), job.getJobId());
+                    expectedPureOrder[idx].getJobId(), job.getJobId());
+            idx++;
+        }
+
+        final JobStatus[] expectedOptimizedOrder = new JobStatus[]{
+                eC3, eC11, rD4, eE5, eE14, eB6, rB2, eA7, rA1, rH8, eF9, rF8,  rC10, rG12, rG13};
+        idx = 0;
+        jobQueue.setOptimizeIteration(true);
+        jobQueue.resetIterator();
+        while ((job = jobQueue.next()) != null) {
+            assertEquals("Optimized list wasn't correctly sorted @ index " + idx,
+                    expectedOptimizedOrder[idx].getJobId(), job.getJobId());
             idx++;
         }
     }
@@ -371,38 +383,36 @@
 
     private void checkPendingJobInvariants(PendingJobQueue jobQueue) {
         final SparseBooleanArray regJobSeen = new SparseBooleanArray();
-        final SparseIntArray lastOverrideStateSeen = new SparseIntArray();
         // Latest priority enqueue times seen for each priority for each app.
         final SparseArray<SparseLongArray> latestPriorityRegEnqueueTimesPerUid =
                 new SparseArray<>();
         final SparseArray<SparseLongArray> latestPriorityEjEnqueueTimesPerUid = new SparseArray<>();
         final int noEntry = -1;
+        int prevOverrideState = noEntry;
 
         JobStatus job;
         jobQueue.resetIterator();
+        int count = 0;
         while ((job = jobQueue.next()) != null) {
+            count++;
             final int uid = job.getSourceUid();
 
-            // Invariant #1: All jobs (for a UID) are sorted by override state
+            // Invariant #1: All jobs are sorted by override state
             // Invariant #2: All jobs (for a UID) are sorted by priority order
             // Invariant #3: Jobs (for a UID) with the same priority are sorted by enqueue time.
             // Invariant #4: EJs (for a UID) should be before regular jobs
 
-            final int prevOverrideState = lastOverrideStateSeen.get(uid, noEntry);
-            lastOverrideStateSeen.put(uid, job.overrideState);
-            if (prevOverrideState == noEntry) {
-                // First job for UID
-                continue;
-            }
-
             // Invariant 1
             if (prevOverrideState != job.overrideState) {
-                assertTrue(prevOverrideState > job.overrideState);
-                // Override state can make ordering weird. Clear the other cached states for this
-                // UID to avoid confusion in the other checks.
-                latestPriorityEjEnqueueTimesPerUid.remove(uid);
-                latestPriorityRegEnqueueTimesPerUid.remove(uid);
-                regJobSeen.delete(uid);
+                if (prevOverrideState != noEntry) {
+                    assertTrue(prevOverrideState > job.overrideState);
+                }
+                // Override state can make ordering weird. Clear the other cached states
+                // to avoid confusion in the other checks.
+                latestPriorityEjEnqueueTimesPerUid.clear();
+                latestPriorityRegEnqueueTimesPerUid.clear();
+                regJobSeen.clear();
+                prevOverrideState = job.overrideState;
             }
 
             final int priority = job.getEffectivePriority();
@@ -423,8 +433,9 @@
                 final long lastSeenPriorityEnqueueTime =
                         latestPriorityEnqueueTimes.get(priority, noEntry);
                 if (lastSeenPriorityEnqueueTime != noEntry) {
-                    assertTrue("Jobs with same priority not sorted by enqueue time: "
-                                    + lastSeenPriorityEnqueueTime + " vs " + job.enqueueTime,
+                    assertTrue("Jobs with same priority for uid " + uid
+                                    + " not sorted by enqueue time: "
+                                    + lastSeenPriorityEnqueueTime + " before " + job.enqueueTime,
                             lastSeenPriorityEnqueueTime <= job.enqueueTime);
                 }
             } else {
@@ -440,6 +451,7 @@
                 fail("UID " + uid + " had an EJ ordered after a regular job");
             }
         }
+        assertEquals("Iterator didn't go through all jobs", jobQueue.size(), count);
     }
 
     private static String testJobToString(JobStatus job) {
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index af8ac6f..7634b09 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -118,6 +118,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.Signature;
 import android.content.pm.UserInfo;
 import android.net.ConnectivityManager;
@@ -166,6 +167,7 @@
 import com.android.internal.util.test.FsUtil;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.LocalServices;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.usage.AppStandbyInternal;
 
 import com.google.common.util.concurrent.AbstractFuture;
@@ -216,6 +218,7 @@
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 /**
@@ -274,6 +277,7 @@
             ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
 
     private ActivityManagerInternal mActivityManagerInternal;
+    private PackageManagerInternal mPackageManagerInternal;
 
     private IUidObserver mUidObserver;
     private INetworkManagementEventObserver mNetworkObserver;
@@ -335,6 +339,7 @@
         when(usageStats.getIdleUidsForUser(anyInt())).thenReturn(new int[]{});
 
         mActivityManagerInternal = addLocalServiceMock(ActivityManagerInternal.class);
+        mPackageManagerInternal = addLocalServiceMock(PackageManagerInternal.class);
 
         final PowerSaveState state = new PowerSaveState.Builder()
                 .setBatterySaverEnabled(false).build();
@@ -483,8 +488,15 @@
                 .thenReturn(buildApplicationInfo(PKG_NAME_B, UID_B));
         when(mPackageManager.getApplicationInfo(eq(PKG_NAME_C), anyInt()))
                 .thenReturn(buildApplicationInfo(PKG_NAME_C, UID_C));
-        when(mPackageManager.getInstalledApplications(anyInt())).thenReturn(
-                buildInstalledApplicationInfoList());
+        doAnswer(arg -> {
+            final Consumer<AndroidPackage> consumer =
+                    (Consumer<AndroidPackage>) arg.getArguments()[0];
+            for (AndroidPackage androidPackage : buildInstalledPackageList()) {
+                consumer.accept(androidPackage);
+            }
+            return null;
+        }).when(mPackageManagerInternal).forEachInstalledPackage(
+                any(Consumer.class), anyInt());
         when(mUserManager.getUsers()).thenReturn(buildUserInfoList());
         when(mNetworkManager.isBandwidthControlEnabled()).thenReturn(true);
         when(mNetworkManager.setDataSaverModeEnabled(anyBoolean())).thenReturn(true);
@@ -536,6 +548,7 @@
         LocalServices.removeServiceForTest(DeviceIdleInternal.class);
         LocalServices.removeServiceForTest(AppStandbyInternal.class);
         LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
     }
 
     @After
@@ -2037,14 +2050,20 @@
         return ai;
     }
 
-    private List<ApplicationInfo> buildInstalledApplicationInfoList() {
-        final List<ApplicationInfo> installedApps = new ArrayList<>();
-        installedApps.add(buildApplicationInfo(PKG_NAME_A, UID_A));
-        installedApps.add(buildApplicationInfo(PKG_NAME_B, UID_B));
-        installedApps.add(buildApplicationInfo(PKG_NAME_C, UID_C));
+    private List<AndroidPackage> buildInstalledPackageList() {
+        final List<AndroidPackage> installedApps = new ArrayList<>();
+        installedApps.add(createPackageMock(UID_A));
+        installedApps.add(createPackageMock(UID_B));
+        installedApps.add(createPackageMock(UID_C));
         return installedApps;
     }
 
+    private AndroidPackage createPackageMock(int uid) {
+        final AndroidPackage androidPackage = mock(AndroidPackage.class);
+        when(androidPackage.getUid()).thenReturn(uid);
+        return androidPackage;
+    }
+
     private List<UserInfo> buildUserInfoList() {
         final List<UserInfo> users = new ArrayList<>();
         users.add(new UserInfo(USER_ID, "user1", 0));
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java
index 0301e94..b907c62 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -277,6 +277,6 @@
         Settings.System.putIntForUser(
                 mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
         // FakeSettingsProvider don't support testing triggering ContentObserver yet.
-        mVibrationSettings.updateSettings();
+        mVibrationSettings.update();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
index ec16188..0c28d8c 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -476,6 +476,24 @@
     }
 
     @Test
+    public void shouldIgnoreVibration_updateTriggeredAfterInternalRingerModeChanged() {
+        // Vibrating settings on are overruled by ringer mode.
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 1);
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+        setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1);
+        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+
+        assertVibrationNotIgnoredForUsage(USAGE_RINGTONE);
+
+        // Testing the broadcast flow manually.
+        mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_SILENT);
+        mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
+                new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
+
+        assertVibrationIgnoredForUsage(USAGE_RINGTONE, Vibration.Status.IGNORED_FOR_RINGER_MODE);
+    }
+
+    @Test
     public void shouldVibrateInputDevices_returnsSettingsValue() {
         setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
         assertTrue(mVibrationSettings.shouldVibrateInputDevices());
@@ -577,7 +595,7 @@
         Settings.System.putIntForUser(mContextSpy.getContentResolver(),
                 Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW,
                 UserHandle.USER_CURRENT);
-        mVibrationSettings.mUserReceiver.onReceive(mContextSpy,
+        mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
                 new Intent(Intent.ACTION_USER_SWITCHED));
         assertEquals(VIBRATION_INTENSITY_LOW,
                 mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
@@ -587,7 +605,7 @@
     public void getCurrentIntensity_noHardwareFeedbackValueUsesHapticFeedbackValue() {
         setDefaultIntensity(USAGE_HARDWARE_FEEDBACK, VIBRATION_INTENSITY_MEDIUM);
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
-        mVibrationSettings.updateSettings();
+        mVibrationSettings.update();
         assertEquals(VIBRATION_INTENSITY_OFF, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH));
         // If haptic feedback is off, fallback to default value.
         assertEquals(VIBRATION_INTENSITY_MEDIUM,
@@ -596,7 +614,7 @@
                 mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION));
 
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
-        mVibrationSettings.updateSettings();
+        mVibrationSettings.update();
         assertEquals(VIBRATION_INTENSITY_HIGH,
                 mVibrationSettings.getCurrentIntensity(USAGE_TOUCH));
         // If haptic feedback is on, fallback to that value.
@@ -633,7 +651,7 @@
                 mVibrationSettings.shouldIgnoreVibration(UID,
                         new VibrationAttributes.Builder()
                                 .setUsage(usage)
-                                .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED)
+                                .setFlags(flags)
                                 .build()));
     }
 
@@ -654,18 +672,19 @@
         Settings.System.putStringForUser(
                 mContextSpy.getContentResolver(), settingName, null, UserHandle.USER_CURRENT);
         // FakeSettingsProvider doesn't support testing triggering ContentObserver yet.
-        mVibrationSettings.updateSettings();
+        mVibrationSettings.update();
     }
 
     private void setUserSetting(String settingName, int value) {
         Settings.System.putIntForUser(
                 mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
         // FakeSettingsProvider doesn't support testing triggering ContentObserver yet.
-        mVibrationSettings.updateSettings();
+        mVibrationSettings.update();
     }
 
     private void setRingerMode(int ringerMode) {
         mAudioManager.setRingerModeInternal(ringerMode);
         assertEquals(ringerMode, mAudioManager.getRingerModeInternal());
+        mVibrationSettings.update();
     }
 }
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 704729e..9f13591 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,13 @@
         assertTrue(mThread.isRunningVibrationId(vibrationId));
         assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
 
-        conductor.notifyCancelled(/* immediate= */ false);
+        conductor.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false);
         waitForCompletion();
         assertFalse(mThread.isRunningVibrationId(vibrationId));
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
         List<Float> playedAmplitudes = fakeVibrator.getAmplitudes();
@@ -288,10 +288,10 @@
         VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
 
         assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(/* immediate= */ false);
+        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
         waitForCompletion();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(Arrays.asList(expectedOneShot(1000)),
                 fakeVibrator.getEffectSegments(vibrationId));
@@ -310,10 +310,10 @@
         VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
 
         assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(/* immediate= */ false);
+        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
         waitForCompletion();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(Arrays.asList(expectedOneShot(5550)),
                 fakeVibrator.getEffectSegments(vibrationId));
@@ -334,10 +334,10 @@
 
         assertTrue(waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length,
                 1000 + TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(/* immediate= */ false);
+        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
         waitForCompletion();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(2, fakeVibrator.getEffectSegments(vibrationId).size());
         // First time turn vibrator ON for minimum of 1s.
@@ -371,13 +371,14 @@
         // 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(/* immediate= */ false));
+                new Thread(() -> conductor.notifyCancelled(
+                        Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE, /* immediate= */ false));
         cancellingThread.start();
 
         waitForCompletion(/* timeout= */ 50);
         cancellingThread.join();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
     }
 
@@ -397,13 +398,14 @@
         // 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(/* immediate= */ false));
+                new Thread(() -> conductor.notifyCancelled(
+                        Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false));
         cancellingThread.start();
 
         waitForCompletion(/* timeout= */ 50);
         cancellingThread.join();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
     }
 
@@ -647,7 +649,7 @@
         waitForCompletion();
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED);
     }
 
     @Test
@@ -1043,7 +1045,8 @@
         // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
         // fail at waitForCompletion(cancellingThread).
         Thread cancellingThread = new Thread(
-                () -> conductor.notifyCancelled(/* immediate= */ false));
+                () -> conductor.notifyCancelled(
+                        Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false));
         cancellingThread.start();
 
         // Cancelling the vibration should be fast and return right away, even if the thread is
@@ -1052,7 +1055,7 @@
 
         // After the vibrator call ends the vibration is cancelled and the vibrator is turned off.
         waitForCompletion(/* timeout= */ latency + TEST_TIMEOUT_MILLIS);
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
     }
 
@@ -1080,13 +1083,14 @@
         // 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(/* immediate= */ false));
+                () -> conductor.notifyCancelled(
+                        Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false));
         cancellingThread.start();
 
         waitForCompletion(/* timeout= */ 50);
         cancellingThread.join();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED);
         assertFalse(mControllers.get(1).isVibrating());
         assertFalse(mControllers.get(2).isVibrating());
     }
@@ -1113,13 +1117,14 @@
         // 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(/* immediate= */ false));
+                new Thread(() -> conductor.notifyCancelled(
+                        Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false));
         cancellingThread.start();
 
         waitForCompletion(/* timeout= */ 50);
         cancellingThread.join();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
         assertFalse(mControllers.get(1).isVibrating());
         assertFalse(mControllers.get(2).isVibrating());
     }
@@ -1139,7 +1144,7 @@
 
         verify(mVibrationToken).linkToDeath(same(conductor), eq(0));
         verify(mVibrationToken).unlinkToDeath(same(conductor), eq(0));
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BINDER_DIED);
         assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty());
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
     }
@@ -1193,12 +1198,13 @@
                 mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
 
         // Will stop the ramp down right away.
-        conductor.notifyCancelled(/* immediate= */ true);
+        conductor.notifyCancelled(
+                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));
+                eq(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE));
         verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
     }
 
@@ -1214,10 +1220,10 @@
         VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
         assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
                 TEST_TIMEOUT_MILLIS));
-        conductor.notifyCancelled(/* immediate= */ false);
+        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
         waitForCompletion();
 
-        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
 
         // Duration extended for 10000 + 15.
         assertEquals(Arrays.asList(expectedOneShot(10_015)),
@@ -1337,7 +1343,7 @@
         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(/* immediate= */ false);
+        conductor2.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
         waitForCompletion();
 
         startThreadAndDispatcher(vibrationId3, effect3);
@@ -1346,7 +1352,7 @@
         // Effect4 is a long oneshot, but it gets cancelled as fast as possible.
         long start4 = System.currentTimeMillis();
         VibrationStepConductor conductor4 = startThreadAndDispatcher(vibrationId4, effect4);
-        conductor4.notifyCancelled(/* immediate= */ true);
+        conductor4.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ true);
         waitForCompletion();
         long duration4 = System.currentTimeMillis() - start4;
 
@@ -1366,7 +1372,7 @@
 
         // Effect2: repeating, cancelled.
         verify(mControllerCallbacks, atLeast(2)).onComplete(VIBRATOR_ID, vibrationId2);
-        verifyCallbacksTriggered(vibrationId2, Vibration.Status.CANCELLED);
+        verifyCallbacksTriggered(vibrationId2, Vibration.Status.CANCELLED_BY_USER);
 
         // The exact count of segments might vary, so just check that there's more than 2 and
         // all elements are the same segment.
@@ -1384,7 +1390,7 @@
                 fakeVibrator.getEffectSegments(vibrationId3));
 
         // Effect4: cancelled quickly.
-        verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED);
+        verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED_SUPERSEDED);
         assertTrue("Tested duration=" + duration4, duration4 < 2000);
 
         // Effect5: normal oneshot. Don't worry about amplitude, as effect4 may or may not have
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 92736c5..9c72ce2 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -673,6 +673,42 @@
     }
 
     @Test
+    public void vibrate_withVibrationAttributesEnforceFreshSettings_refreshesVibrationSettings()
+            throws Exception {
+        mockVibrators(0);
+        mVibratorProviders.get(0).setSupportedEffects(VibrationEffect.EFFECT_CLICK,
+                VibrationEffect.EFFECT_TICK);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_HIGH);
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationAttributes enforceFreshAttrs = 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));
+
+        assertEquals(
+                Arrays.asList(
+                        expectedPrebaked(VibrationEffect.EFFECT_CLICK,
+                                VibrationEffect.EFFECT_STRENGTH_STRONG),
+                        expectedPrebaked(VibrationEffect.EFFECT_TICK,
+                                VibrationEffect.EFFECT_STRENGTH_LIGHT)),
+                mVibratorProviders.get(0).getAllEffectSegments());
+    }
+
+    @Test
     public void vibrate_withAttributesUnknownUsage_usesEffectToIdentifyTouchUsage() {
         VibratorManagerService service = createSystemReadyService();
 
@@ -1280,7 +1316,11 @@
     }
 
     private VibrationEffectSegment expectedPrebaked(int effectId) {
-        return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+        return expectedPrebaked(effectId, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+    }
+
+    private VibrationEffectSegment expectedPrebaked(int effectId, int effectStrength) {
+        return new PrebakedSegment(effectId, false, effectStrength);
     }
 
     private void mockCapabilities(long... capabilities) {
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 40b4601..9169267 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -414,6 +414,33 @@
         imeContainer.setOrganizer(null);
     }
 
+    @Test
+    public void testImeContainerIsReparentedUnderParentWhenOrganized() {
+        final DisplayArea.Tokens imeContainer = mDisplayContent.getImeContainer();
+        final ActivityRecord activity = createActivityRecord(mDisplayContent);
+
+        final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity,
+                "startingWin");
+        startingWin.setHasSurface(true);
+        assertTrue(startingWin.canBeImeTarget());
+
+        final Transaction transaction = mDisplayContent.getPendingTransaction();
+        spyOn(transaction);
+
+        // Organized the ime container.
+        final IDisplayAreaOrganizer mockImeOrganizer = mock(IDisplayAreaOrganizer.class);
+        when(mockImeOrganizer.asBinder()).thenReturn(new Binder());
+        imeContainer.setOrganizer(mockImeOrganizer);
+
+        // Verify that the ime container surface is reparented under
+        // its parent surface as a consequence of the setOrganizer call.
+        SurfaceControl imeParentSurfaceControl = imeContainer.getParentSurfaceControl();
+        verify(transaction).reparent(imeContainer.getSurfaceControl(), imeParentSurfaceControl);
+
+        // Clean up organizer.
+        imeContainer.setOrganizer(null);
+    }
+
     /**
      * This tests root task movement between displays and proper root task's, task's and app token's
      * display container references updates.
@@ -1984,7 +2011,7 @@
         appWin2.setHasSurface(true);
         assertTrue(appWin2.canBeImeTarget());
         doReturn(true).when(appWin1).isClosing();
-        doReturn(true).when(appWin1).inAppOrRecentsTransition();
+        doReturn(true).when(appWin1).inTransitionSelfOrParent();
 
         // Test step 3: Verify appWin2 will be the next IME target and the IME snapshot surface will
         // be attached and shown on the display at this time.
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 c615866..b1c9d3d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -18,6 +18,7 @@
 
 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 com.android.server.wm.testing.Assert.assertThrows;
 
 import static org.junit.Assert.assertEquals;
@@ -249,19 +250,13 @@
         // the organizer.
         mTransaction.setBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100));
 
-        assertThrows(SecurityException.class, () -> {
-            try {
-                mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
-            } catch (RemoteException e) {
-                fail();
-            }
-        });
+        assertApplyTransactionDisallowed(mTransaction);
 
         // Allow transaction to change a TaskFragment created by the organizer.
         mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
                 "Test:TaskFragmentOrganizer" /* processName */);
 
-        mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+        assertApplyTransactionAllowed(mTransaction);
     }
 
     @Test
@@ -272,19 +267,13 @@
         // the organizer.
         mTransaction.reorder(mFragmentWindowToken, true /* onTop */);
 
-        assertThrows(SecurityException.class, () -> {
-            try {
-                mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
-            } catch (RemoteException e) {
-                fail();
-            }
-        });
+        assertApplyTransactionDisallowed(mTransaction);
 
         // Allow transaction to change a TaskFragment created by the organizer.
         mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
                 "Test:TaskFragmentOrganizer" /* processName */);
 
-        mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+        assertApplyTransactionAllowed(mTransaction);
     }
 
     @Test
@@ -298,27 +287,21 @@
         // the organizer.
         mTransaction.deleteTaskFragment(mFragmentWindowToken);
 
-        assertThrows(SecurityException.class, () -> {
-            try {
-                mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
-            } catch (RemoteException e) {
-                fail();
-            }
-        });
+        assertApplyTransactionDisallowed(mTransaction);
 
         // Allow transaction to change a TaskFragment created by the organizer.
         mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
                 "Test:TaskFragmentOrganizer" /* processName */);
         clearInvocations(mAtm.mRootWindowContainer);
 
-        mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+        assertApplyTransactionAllowed(mTransaction);
 
         // No lifecycle update when the TaskFragment is not recorded.
         verify(mAtm.mRootWindowContainer, never()).resumeFocusedTasksTopActivities();
 
         mAtm.mWindowOrganizerController.mLaunchTaskFragments
                 .put(mFragmentToken, mTaskFragment);
-        mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+        assertApplyTransactionAllowed(mTransaction);
 
         verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities();
     }
@@ -335,13 +318,7 @@
         // the organizer.
         mTransaction.setAdjacentRoots(mFragmentWindowToken, token2, false /* moveTogether */);
 
-        assertThrows(SecurityException.class, () -> {
-            try {
-                mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
-            } catch (RemoteException e) {
-                fail();
-            }
-        });
+        assertApplyTransactionDisallowed(mTransaction);
 
         // Allow transaction to change a TaskFragment created by the organizer.
         mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
@@ -350,7 +327,7 @@
                 "Test:TaskFragmentOrganizer" /* processName */);
         clearInvocations(mAtm.mRootWindowContainer);
 
-        mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+        assertApplyTransactionAllowed(mTransaction);
 
         verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities();
     }
@@ -423,20 +400,14 @@
         // the organizer.
         mTransaction.reparentChildren(mFragmentWindowToken, null /* newParent */);
 
-        assertThrows(SecurityException.class, () -> {
-            try {
-                mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
-            } catch (RemoteException e) {
-                fail();
-            }
-        });
+        assertApplyTransactionDisallowed(mTransaction);
 
         // Allow transaction to change a TaskFragment created by the organizer.
         mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
                 "Test:TaskFragmentOrganizer" /* processName */);
         clearInvocations(mAtm.mRootWindowContainer);
 
-        mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+        assertApplyTransactionAllowed(mTransaction);
 
         verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities();
     }
@@ -454,6 +425,7 @@
         mAtm.mWindowOrganizerController.mLaunchTaskFragments
                 .put(mFragmentToken, mTaskFragment);
         mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token);
+        doReturn(true).when(mTaskFragment).isAllowedToEmbedActivity(activity);
         clearInvocations(mAtm.mRootWindowContainer);
 
         mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
@@ -484,6 +456,40 @@
         verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
     }
 
+    @Test
+    public void testCanSendPendingTaskFragmentEventsAfterActivityResumed() {
+        // Task - TaskFragment - Activity.
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .setOrganizer(mOrganizer)
+                .setFragmentToken(mFragmentToken)
+                .createActivityCount(1)
+                .build();
+        final ActivityRecord activity = taskFragment.getTopMostActivity();
+
+        // Mock the task to invisible
+        doReturn(false).when(task).shouldBeVisible(any());
+        taskFragment.setResumedActivity(null, "test");
+
+        // Sending events
+        mController.registerOrganizer(mIOrganizer);
+        taskFragment.mTaskFragmentAppearedSent = true;
+        mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
+        mController.dispatchPendingEvents();
+
+        // Verifies that event was not sent
+        verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
+
+        // Mock the task becomes visible, and activity resumed
+        doReturn(true).when(task).shouldBeVisible(any());
+        taskFragment.setResumedActivity(activity, "test");
+
+        // Verifies that event is sent.
+        mController.dispatchPendingEvents();
+        verify(mOrganizer).onTaskFragmentInfoChanged(any());
+    }
+
     /**
      * Tests that a task fragment info changed event is still sent if the task is invisible only
      * when the info changed event is because of the last activity in a task finishing.
@@ -541,6 +547,66 @@
     }
 
     /**
+     * For config change to untrusted embedded TaskFragment, we only allow bounds change within
+     * its parent bounds.
+     */
+    @Test
+    public void testUntrustedEmbedding_configChange() throws RemoteException  {
+        mController.registerOrganizer(mIOrganizer);
+        mOrganizer.applyTransaction(mTransaction);
+        mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+                "Test:TaskFragmentOrganizer" /* processName */);
+        doReturn(false).when(mTaskFragment).isAllowedToBeEmbeddedInTrustedMode();
+        final Task task = createTask(mDisplayContent);
+        final Rect taskBounds = new Rect(task.getBounds());
+        final Rect taskAppBounds = new Rect(task.getWindowConfiguration().getAppBounds());
+        final int taskScreenWidthDp = task.getConfiguration().screenWidthDp;
+        final int taskScreenHeightDp = task.getConfiguration().screenHeightDp;
+        final int taskSmallestScreenWidthDp = task.getConfiguration().smallestScreenWidthDp;
+        task.addChild(mTaskFragment, POSITION_TOP);
+
+        // Throw exception if the transaction is trying to change bounds of an untrusted outside of
+        // its parent's.
+
+        // setBounds
+        final Rect tfBounds = new Rect(taskBounds);
+        tfBounds.right++;
+        mTransaction.setBounds(mFragmentWindowToken, tfBounds);
+        assertApplyTransactionDisallowed(mTransaction);
+
+        mTransaction.setBounds(mFragmentWindowToken, taskBounds);
+        assertApplyTransactionAllowed(mTransaction);
+
+        // setAppBounds
+        final Rect tfAppBounds = new Rect(taskAppBounds);
+        tfAppBounds.right++;
+        mTransaction.setAppBounds(mFragmentWindowToken, tfAppBounds);
+        assertApplyTransactionDisallowed(mTransaction);
+
+        mTransaction.setAppBounds(mFragmentWindowToken, taskAppBounds);
+        assertApplyTransactionAllowed(mTransaction);
+
+        // setScreenSizeDp
+        mTransaction.setScreenSizeDp(mFragmentWindowToken, taskScreenWidthDp + 1,
+                taskScreenHeightDp + 1);
+        assertApplyTransactionDisallowed(mTransaction);
+
+        mTransaction.setScreenSizeDp(mFragmentWindowToken, taskScreenWidthDp, taskScreenHeightDp);
+        assertApplyTransactionAllowed(mTransaction);
+
+        // setSmallestScreenWidthDp
+        mTransaction.setSmallestScreenWidthDp(mFragmentWindowToken, taskSmallestScreenWidthDp + 1);
+        assertApplyTransactionDisallowed(mTransaction);
+
+        mTransaction.setSmallestScreenWidthDp(mFragmentWindowToken, taskSmallestScreenWidthDp);
+        assertApplyTransactionAllowed(mTransaction);
+
+        // Any of the change mask is not allowed.
+        mTransaction.setFocusable(mFragmentWindowToken, false);
+        assertApplyTransactionDisallowed(mTransaction);
+    }
+
+    /**
      * Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls
      * {@link WindowOrganizerController#applyTransaction} to apply the transaction,
      */
@@ -556,4 +622,24 @@
         // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
         wct.createTaskFragment(params);
     }
+
+    /** Asserts that applying the given transaction will throw a {@link SecurityException}. */
+    private void assertApplyTransactionDisallowed(WindowContainerTransaction t) {
+        assertThrows(SecurityException.class, () -> {
+            try {
+                mAtm.getWindowOrganizerController().applyTransaction(t);
+            } catch (RemoteException e) {
+                fail();
+            }
+        });
+    }
+
+    /** Asserts that applying the given transaction will not throw any exception. */
+    private void assertApplyTransactionAllowed(WindowContainerTransaction t) {
+        try {
+            mAtm.getWindowOrganizerController().applyTransaction(t);
+        } catch (RemoteException e) {
+            fail();
+        }
+    }
 }
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 7e5d017..3330ca9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -617,8 +617,8 @@
         }
         player.startTransition();
 
-        assertFalse(statusBar.mToken.inTransition());
-        assertFalse(decorToken.inTransition());
+        assertFalse(mDisplayContent.mTransitionController.isCollecting(statusBar.mToken));
+        assertFalse(mDisplayContent.mTransitionController.isCollecting(decorToken));
         assertTrue(ime.mToken.inTransition());
         assertTrue(task.inTransition());
         assertTrue(asyncRotationController.isTargetToken(decorToken));
@@ -735,7 +735,7 @@
         statusBar.setOrientationChanging(true);
         player.startTransition();
         // Non-app windows should not be collected.
-        assertFalse(statusBar.mToken.inTransition());
+        assertFalse(mDisplayContent.mTransitionController.isCollecting(statusBar.mToken));
 
         onRotationTransactionReady(player, mWm.mTransactionFactory.get()).onTransactionCommitted();
         assertEquals(ROTATION_ANIMATION_SEAMLESS, player.mLastReady.getChange(
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 08320f8..a0c20c2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -70,7 +70,7 @@
 
 /**
  * Build/Install/Run:
- *  atest WmTests:WindowManagerServiceTests
+ * atest WmTests:WindowManagerServiceTests
  */
 @SmallTest
 @Presubmit
@@ -266,7 +266,8 @@
         final WindowToken windowToken = createTestWindowToken(TYPE_INPUT_METHOD, mDefaultDisplay);
         final Session session = new Session(mWm, new IWindowSessionCallback.Stub() {
             @Override
-            public void onAnimatorScaleChanged(float v) throws RemoteException {}
+            public void onAnimatorScaleChanged(float v) throws RemoteException {
+            }
         });
         final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                 TYPE_APPLICATION_ATTACHED_DIALOG);
@@ -291,8 +292,9 @@
         boolean currentTouchMode = mWm.getInTouchMode();
         int callingPid = Binder.getCallingPid();
         int callingUid = Binder.getCallingUid();
-        doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString());
-        when(mWm.mAtmService.isInstrumenting(callingPid)).thenReturn(true);
+        doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean());
+        when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid,
+                android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(true);
 
         mWm.setInTouchMode(!currentTouchMode);
 
@@ -305,8 +307,9 @@
         boolean currentTouchMode = mWm.getInTouchMode();
         int callingPid = Binder.getCallingPid();
         int callingUid = Binder.getCallingUid();
-        doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString());
-        when(mWm.mAtmService.isInstrumenting(callingPid)).thenReturn(false);
+        doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean());
+        when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid,
+                android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(false);
 
         mWm.setInTouchMode(!currentTouchMode);
 
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 b4d305b..9faf499 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -309,6 +309,51 @@
     }
 
     @Test
+    public void testOrganizerDeathReturnsRegistrationToPrevious() throws RemoteException {
+        final Task rootTask = createRootTask();
+        final Task task = createTask(rootTask);
+        final Task rootTask2 = createRootTask();
+        final Task task2 = createTask(rootTask2);
+        final Task rootTask3 = createRootTask();
+        final Task task3 = createTask(rootTask3);
+        final ArrayList<TaskAppearedInfo> existingTasks = new ArrayList<>();
+        final ITaskOrganizer organizer = registerMockOrganizer(existingTasks);
+        // Ensure events dispatch to organizer.
+        mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
+
+        // verify that tasks are returned and taskAppeared is not called
+        assertContainsTasks(existingTasks, rootTask, rootTask2, rootTask3);
+        verify(organizer, times(0)).onTaskAppeared(any(RunningTaskInfo.class),
+                any(SurfaceControl.class));
+        verify(organizer, times(0)).onTaskVanished(any());
+        assertTrue(rootTask.isOrganized());
+
+        // Now we replace the registration and verify the new organizer receives existing tasks
+        final ArrayList<TaskAppearedInfo> existingTasks2 = new ArrayList<>();
+        final ITaskOrganizer organizer2 = registerMockOrganizer(existingTasks2);
+        // Ensure events dispatch to organizer.
+        mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
+        assertContainsTasks(existingTasks2, rootTask, rootTask2, rootTask3);
+        verify(organizer2, times(0)).onTaskAppeared(any(RunningTaskInfo.class),
+                any(SurfaceControl.class));
+        verify(organizer2, times(0)).onTaskVanished(any());
+        // Removed tasks from the original organizer
+        assertTaskVanished(organizer, true /* expectVanished */, rootTask, rootTask2, rootTask3);
+        assertTrue(rootTask2.isOrganized());
+
+        // Trigger binderDied for second one, the first one should automatically be reregistered
+        // so we verify that it's now seeing changes.
+        mWm.mAtmService.mTaskOrganizerController.getTaskOrganizerState(organizer2.asBinder())
+                .getDeathRecipient().binderDied();
+
+        // Ensure events dispatch to organizer.
+        mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
+        verify(organizer, times(3))
+                .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
+        assertTaskVanished(organizer2, true /* expectVanished */, rootTask, rootTask2, rootTask3);
+    }
+
+    @Test
     public void testRegisterTaskOrganizerWithExistingTasks() throws RemoteException {
         final Task rootTask = createRootTask();
         final Task task = createTask(rootTask);
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 bd1f9d5..6e0d854 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1264,6 +1264,7 @@
                         mOrganizer.getOrganizerToken(), DEFAULT_TASK_FRAGMENT_ORGANIZER_UID,
                         DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
             }
+            spyOn(taskFragment);
             return taskFragment;
         }
     }
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
index 1db018e..8c9e80f 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
@@ -296,8 +296,8 @@
                             + operationID + " key:" + key);
                 }
                 try {
-                    sCallbacks.put(operationID, callback);
-                    mProxy.resetUsbPort(portName, operationID);
+                    sCallbacks.put(key, callback);
+                    mProxy.resetUsbPort(portName, key);
                 } catch (RemoteException e) {
                     logAndPrintException(mPw,
                             "resetUsbPort: Failed to resetUsbPort: portID="
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index dda5ea7..3691fb0 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9499,42 +9499,26 @@
     private void addConfig(String key, Object value, PersistableBundle configs) {
         if (value instanceof String) {
             configs.putString(key, (String) value);
-        }
-
-        if (value instanceof String[]) {
+        } else if (value instanceof String[]) {
             configs.putStringArray(key, (String[]) value);
-        }
-
-        if (value instanceof Integer) {
+        } else if (value instanceof Integer) {
             configs.putInt(key, (Integer) value);
-        }
-
-        if (value instanceof Long) {
+        } else if (value instanceof Long) {
             configs.putLong(key, (Long) value);
-        }
-
-        if (value instanceof Double) {
+        } else if (value instanceof Double) {
             configs.putDouble(key, (Double) value);
-        }
-
-        if (value instanceof Boolean) {
+        } else if (value instanceof Boolean) {
             configs.putBoolean(key, (Boolean) value);
-        }
-
-        if (value instanceof int[]) {
+        } else if (value instanceof int[]) {
             configs.putIntArray(key, (int[]) value);
-        }
-
-        if (value instanceof double[]) {
+        } else if (value instanceof double[]) {
             configs.putDoubleArray(key, (double[]) value);
-        }
-
-        if (value instanceof boolean[]) {
+        } else if (value instanceof boolean[]) {
             configs.putBooleanArray(key, (boolean[]) value);
-        }
-
-        if (value instanceof long[]) {
+        } else if (value instanceof long[]) {
             configs.putLongArray(key, (long[]) value);
+        } else if (value instanceof PersistableBundle) {
+            configs.putPersistableBundle(key, (PersistableBundle) value);
         }
     }
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 1b3a29d..725518d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -16870,7 +16870,7 @@
      * @param executor The executor where {@code listener} will be invoked
      * @param listener The callback to register
      * @hide
-     * @deprecated Use {@link #unregisterCarrierPrivilegesCallback} instead. This API will be
+     * @deprecated Use {@link #registerCarrierPrivilegesCallback} instead. This API will be
      * removed prior to API finalization.
      */
     @Deprecated
diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java
index 77d4837..8287a03 100644
--- a/telephony/java/android/telephony/data/DataServiceCallback.java
+++ b/telephony/java/android/telephony/data/DataServiceCallback.java
@@ -50,8 +50,7 @@
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({RESULT_SUCCESS, RESULT_ERROR_UNSUPPORTED, RESULT_ERROR_INVALID_ARG, RESULT_ERROR_BUSY,
-            RESULT_ERROR_ILLEGAL_STATE, RESULT_ERROR_TEMPORARILY_UNAVAILABLE,
-            RESULT_ERROR_INVALID_RESPONSE})
+            RESULT_ERROR_ILLEGAL_STATE, RESULT_ERROR_TEMPORARILY_UNAVAILABLE})
     public @interface ResultCode {}
 
     /** Request is completed successfully */
@@ -69,11 +68,6 @@
      * @hide
      */
     public static final int RESULT_ERROR_TEMPORARILY_UNAVAILABLE = 5;
-    /**
-     * Request failed to complete due to an invalid response.
-     * @hide
-     */
-    public static final int RESULT_ERROR_INVALID_RESPONSE = 6;
 
     private final IDataServiceCallback mCallback;
 
@@ -261,8 +255,6 @@
                 return "RESULT_ERROR_ILLEGAL_STATE";
             case RESULT_ERROR_TEMPORARILY_UNAVAILABLE:
                 return "RESULT_ERROR_TEMPORARILY_UNAVAILABLE";
-            case RESULT_ERROR_INVALID_RESPONSE:
-                return "RESULT_ERROR_INVALID_RESPONSE";
             default:
                 return "Unknown(" + resultCode + ")";
         }
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
new file mode 100644
index 0000000..2dbf304
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.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.server.wm.flicker.helpers
+
+import android.app.Instrumentation
+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.FlickerComponentName
+import com.android.server.wm.traces.parser.toFlickerComponent
+
+ class FixedOrientationAppHelper @JvmOverloads constructor(
+     instr: Instrumentation,
+     launcherName: String = ActivityOptions.PORTRAIT_ONLY_ACTIVITY_LAUNCHER_NAME,
+     component: FlickerComponentName =
+             ActivityOptions.PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
+     launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
+             .getInstance(instr)
+             .launcherStrategy
+ ) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
new file mode 100644
index 0000000..2a296b7
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.ime
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.view.Surface
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.*
+import com.android.server.wm.flicker.annotation.Group2
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.*
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test IME window layer will become visible when switching from the fixed orientation activity.
+ * To run this test: `atest FlickerTests:OpenImeWindowFromFixedOrientationAppTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group2
+class OpenImeWindowFromFixedOrientationAppTest(private val testSpec: FlickerTestParameter) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val fixedOrientationApp = FixedOrientationAppHelper(instrumentation)
+    private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+
+    @FlickerBuilderProvider
+    fun buildFlicker(): FlickerBuilder {
+        return FlickerBuilder(instrumentation).apply {
+            setup {
+                eachRun {
+                    fixedOrientationApp.launchViaIntent(wmHelper)
+                    this.setRotation(Surface.ROTATION_90)
+                }
+            }
+            transitions {
+                imeTestApp.launchViaIntent(wmHelper)
+            }
+            teardown {
+                test {
+                    fixedOrientationApp.exit(wmHelper)
+                }
+            }
+        }
+    }
+
+    @Postsubmit
+    @Test
+    fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
+
+    @Postsubmit
+    @Test
+    fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
+
+    @Postsubmit
+    @Test
+    fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
+
+    @Postsubmit
+    @Test
+    fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
+
+    @FlakyTest(bugId = 206753786)
+    fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
+
+    @Postsubmit
+    @Test
+    fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible()
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+         * repetitions, screen orientation and navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTestParameter> {
+            return FlickerTestParameterFactory.getInstance()
+                    .getConfigNonRotationTests(
+                            repetitions = 3,
+                            supportedRotations = listOf(Surface.ROTATION_0),
+                            supportedNavigationModes = listOf(
+                                    WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+                            )
+                    )
+        }
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..6f2edd0
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.ime
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.RequiresDevice
+import android.view.Surface
+import android.view.WindowManagerPolicyConstants
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.*
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import org.junit.Assume.assumeTrue
+import org.junit.Assume.assumeFalse
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test IME window layer will be associated with the app task when going to the overview screen.
+ * To run this test: `atest FlickerTests:OpenImeWindowToOverViewTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group4
+class OpenImeWindowToOverViewTest(private val testSpec: FlickerTestParameter) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+
+    @FlickerBuilderProvider
+    fun buildFlicker(): FlickerBuilder {
+        return FlickerBuilder(instrumentation).apply {
+            setup {
+                eachRun {
+                    imeTestApp.launchViaIntent(wmHelper)
+                }
+            }
+            transitions {
+                device.pressRecentApps()
+                waitForRecentsActivityVisible(wmHelper)
+            }
+            teardown {
+                test {
+                    device.pressHome()
+                    imeTestApp.exit(wmHelper)
+                }
+            }
+        }
+    }
+    @Postsubmit
+    @Test
+    fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
+
+    @Postsubmit
+    @Test
+    fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
+
+    @Postsubmit
+    @Test
+    fun imeWindowIsAlwaysVisible() {
+        testSpec.imeWindowIsAlwaysVisible()
+    }
+
+    @Postsubmit
+    @Test
+    fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
+
+    @Postsubmit
+    @Test
+    fun statusBarLayerIsVisibleInPortrait() {
+        assumeFalse(testSpec.isLandscapeOrSeascapeAtStart)
+        testSpec.statusBarLayerIsVisible()
+    }
+
+    @Postsubmit
+    @Test
+    fun statusBarLayerIsInVisibleInLandscape() {
+        assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
+        testSpec.assertLayersStart {
+            this.isVisible(FlickerComponentName.STATUS_BAR)
+        }
+        testSpec.assertLayersEnd {
+            this.isInvisible(FlickerComponentName.STATUS_BAR)
+        }
+    }
+
+    @Postsubmit
+    @Test
+    fun imeLayerIsVisibleAndAssociatedWithAppWidow() {
+        testSpec.assertLayersStart {
+            isVisible(FlickerComponentName.IME).visibleRegion(FlickerComponentName.IME)
+                    .coversAtMost(isVisible(imeTestApp.component)
+                            .visibleRegion(imeTestApp.component).region)
+        }
+        testSpec.assertLayers {
+            this.invoke("imeLayerIsVisibleAndAlignAppWidow") {
+                val imeVisibleRegion = it.visibleRegion(FlickerComponentName.IME)
+                val appVisibleRegion = it.visibleRegion(imeTestApp.component)
+                if (imeVisibleRegion.region.isNotEmpty) {
+                    it.isVisible(FlickerComponentName.IME)
+                    imeVisibleRegion.coversAtMost(appVisibleRegion.region)
+                }
+            }
+        }
+    }
+
+    private fun waitForRecentsActivityVisible(
+        wmHelper: WindowManagerStateHelper
+    ) {
+        val waitMsg = "state of Recents activity to be visible"
+        require(
+                wmHelper.waitFor(waitMsg) {
+                    it.wmState.homeActivity?.let { act ->
+                        it.wmState.isActivityVisible(act.name)
+                    } == true ||
+                            it.wmState.recentsActivity?.let { act ->
+                                it.wmState.isActivityVisible(act.name)
+                            } == true
+                }
+        ) { "Recents activity should be visible" }
+        wmHelper.waitForAppTransitionIdle()
+        // Ensure WindowManagerService wait until all animations have completed
+        instrumentation.uiAutomation.syncInputTransactions()
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+         * repetitions, screen orientation and navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTestParameter> {
+            return FlickerTestParameterFactory.getInstance()
+                    .getConfigNonRotationTests(
+                            repetitions = 1,
+                            supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
+                            supportedNavigationModes = listOf(
+                                    WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+                                    WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+                            )
+                    )
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 9e371e5..739fe02 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -108,5 +108,16 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+        <activity android:name=".PortraitOnlyActivity"
+            android:taskAffinity="com.android.server.wm.flicker.testapp.PortraitOnlyActivity"
+            android:theme="@style/CutoutShortEdges"
+            android:screenOrientation="portrait"
+            android:configChanges="orientation|screenSize"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 13adb68..3040a09 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -60,4 +60,9 @@
     public static final ComponentName DIALOG_THEMED_ACTIVITY_COMPONENT_NAME =
             new ComponentName(FLICKER_APP_PACKAGE,
                     FLICKER_APP_PACKAGE + ".DialogThemedActivity");
+
+    public static final String PORTRAIT_ONLY_ACTIVITY_LAUNCHER_NAME = "PortraitOnlyActivity";
+    public static final ComponentName PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME =
+            new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".PortraitOnlyActivity");
 }
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitOnlyActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitOnlyActivity.java
new file mode 100644
index 0000000..b1876b5
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitOnlyActivity.java
@@ -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.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class PortraitOnlyActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        WindowManager.LayoutParams p = getWindow().getAttributes();
+        p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
+                .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+        getWindow().setAttributes(p);
+        setContentView(R.layout.activity_simple);
+    }
+}
diff --git a/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt b/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt
index 493f3bd..cf4965f 100644
--- a/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt
+++ b/tests/TrustTests/src/android/trust/BaseTrustAgentService.kt
@@ -41,7 +41,7 @@
         private const val TAG = "BaseTrustAgentService"
 
         fun instance(serviceClass: KClass<out BaseTrustAgentService>): BaseTrustAgentService? {
-            return instances[serviceClass]!!
+            return instances[serviceClass]
         }
     }
 }
diff --git a/tests/TrustTests/src/android/trust/test/LockUserTest.kt b/tests/TrustTests/src/android/trust/test/LockUserTest.kt
index a7dd41ad..1194afa 100644
--- a/tests/TrustTests/src/android/trust/test/LockUserTest.kt
+++ b/tests/TrustTests/src/android/trust/test/LockUserTest.kt
@@ -57,7 +57,6 @@
 
     companion object {
         private const val TAG = "LockUserTest"
-        private fun await() = Thread.sleep(250)
     }
 }
 
diff --git a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
index 00f457b..2031af2 100644
--- a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
@@ -22,7 +22,6 @@
 import android.util.Log
 import android.view.WindowManagerGlobal
 import androidx.test.core.app.ApplicationProvider.getApplicationContext
-import com.google.common.truth.Truth.assertThat
 import org.junit.rules.TestRule
 import org.junit.runner.Description
 import org.junit.runners.model.Statement
@@ -53,37 +52,12 @@
     }
 
     fun assertLocked() {
-        val maxWaits = 50
-        var waitCount = 0
-
-        // First verify we get the call in LockState via TrustListener
-        while ((lockState.locked == false) && waitCount < maxWaits) {
-            Log.i(TAG, "phone still unlocked (TrustListener), wait 50ms more ($waitCount)")
-            Thread.sleep(50)
-            waitCount++
-        }
-        assertThat(lockState.locked).isTrue()
-
-        // TODO(b/225231929): refactor checks into one loop and re-use for assertUnlocked
-        // Then verify we get the window manager locked
-        while (!windowManager.isKeyguardLocked && waitCount < maxWaits) {
-            Log.i(TAG, "phone still unlocked (WindowManager), wait 50ms more ($waitCount)")
-            Thread.sleep(50)
-            waitCount++
-        }
-        assertThat(windowManager.isKeyguardLocked).isTrue()
+        wait("un-locked per TrustListener") { lockState.locked == true }
+        wait("keyguard lock") { windowManager.isKeyguardLocked }
     }
 
     fun assertUnlocked() {
-        val maxWaits = 50
-        var waitCount = 0
-
-        while ((lockState.locked == true) && waitCount < maxWaits) {
-            Log.i(TAG, "phone still unlocked, wait 50ms more ($waitCount)")
-            Thread.sleep(50)
-            waitCount++
-        }
-        assertThat(lockState.locked).isFalse()
+        wait("locked per TrustListener") { lockState.locked == false }
     }
 
     inner class Listener : TrustListener {
diff --git a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
index 127653d..7eb8157 100644
--- a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
@@ -42,7 +42,7 @@
     override fun apply(base: Statement, description: Description) = object : Statement() {
         override fun evaluate() {
             verifyNoScreenLockAlreadySet()
-            verifyKeyguardDismissed()
+            dismissKeyguard()
             setScreenLock()
             setLockOnPowerButton()
 
@@ -51,7 +51,7 @@
             } finally {
                 removeScreenLock()
                 revertLockOnPowerButton()
-                verifyKeyguardDismissed()
+                dismissKeyguard()
             }
         }
     }
@@ -62,30 +62,21 @@
                 .isFalse()
     }
 
-    private fun verifyKeyguardDismissed() {
-        val maxWaits = 30
-        var waitCount = 0
-
-        while (windowManager.isKeyguardLocked && waitCount < maxWaits) {
-            Log.i(TAG, "Keyguard still showing; attempting to dismiss and wait 50ms ($waitCount)")
+    fun dismissKeyguard() {
+        wait("keyguard dismissed") { count ->
             windowManager.dismissKeyguard(null, null)
 
             // Sometimes, bouncer gets shown due to a race, so we have to put display to sleep
             // and wake it back up to get it to go away
-            if (waitCount >= 10 && waitCount % 5 == 0) {
-                Log.i(TAG, "Escalation: attempting screen off/on to get rid of bouncer (+500ms)")
+            if (count >= 10 && count % 5 == 0) {
+                Log.i(TAG, "Escalation: attempting screen off/on to get rid of bouncer")
                 uiDevice.sleep()
                 Thread.sleep(250)
                 uiDevice.wakeUp()
-                Thread.sleep(250)
             }
 
-            Thread.sleep(50)
-            waitCount++
+            !windowManager.isKeyguardLocked
         }
-        assertWithMessage("Keyguard should be unlocked")
-                .that(windowManager.isKeyguardLocked)
-                .isFalse()
     }
 
     private fun setScreenLock() {
@@ -94,9 +85,7 @@
                 LockscreenCredential.createNone(),
                 context.userId
         )
-        assertWithMessage("Screen Lock should now be set")
-                .that(lockPatternUtils.isSecure(context.userId))
-                .isTrue()
+        wait("screen lock set") { lockPatternUtils.isSecure(context.userId) }
         Log.i(TAG, "Device PIN set to $PIN")
     }
 
@@ -110,21 +99,16 @@
                 LockscreenCredential.createNone(),
                 LockscreenCredential.createPin(PIN),
                 context.userId)
-        Thread.sleep(100)
+        Log.i(TAG, "Removing screen lock")
         assertWithMessage("Lock screen credential should be unset")
                 .that(lockCredentialUnset)
                 .isTrue()
 
         lockPatternUtils.setLockScreenDisabled(true, context.userId)
-        Thread.sleep(100)
-        assertWithMessage("Lockscreen needs to be disabled")
-                .that(lockPatternUtils.isLockScreenDisabled(context.userId))
-                .isTrue()
-
-        // this is here because somehow it helps the keyguard not get stuck
-        uiDevice.sleep()
-        Thread.sleep(500) // delay added to avoid initiating camera by double clicking power
-        uiDevice.wakeUp()
+        wait("screen lock un-set") {
+            lockPatternUtils.isLockScreenDisabled(context.userId)
+        }
+        wait("screen lock insecure") { !lockPatternUtils.isSecure(context.userId) }
     }
 
     private fun revertLockOnPowerButton() {
diff --git a/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
index 2a9e002..18bc029 100644
--- a/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
@@ -50,7 +50,6 @@
             verifyTrustServiceRunning()
             unlockDeviceWithCredential()
             enableTrustAgent()
-            waitForEnablement()
 
             try {
                 verifyAgentIsRunning()
@@ -80,15 +79,10 @@
         lockPatternUtils.setEnabledTrustAgents(agents, userId)
     }
 
-    private fun waitForEnablement() {
-        Log.d(TAG, "Waiting for $WAIT_TIME ms")
-        Thread.sleep(WAIT_TIME)
-        Log.d(TAG, "Done waiting")
-    }
-
     private fun verifyAgentIsRunning() {
-        assertWithMessage("${serviceClass.simpleName} should be running")
-            .that(BaseTrustAgentService.instance(serviceClass)).isNotNull()
+        wait("${serviceClass.simpleName} to be running") {
+            BaseTrustAgentService.instance(serviceClass) != null
+        }
     }
 
     private fun disableTrustAgent() {
@@ -112,6 +106,5 @@
             TrustAgentRule(T::class)
 
         private const val TAG = "TrustAgentRule"
-        private val WAIT_TIME = 1000L
     }
 }
diff --git a/tests/TrustTests/src/android/trust/test/lib/utils.kt b/tests/TrustTests/src/android/trust/test/lib/utils.kt
new file mode 100644
index 0000000..78140ab
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/lib/utils.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 android.trust.test.lib
+
+import android.util.Log
+import com.google.common.truth.Truth.assertWithMessage
+
+private const val TAG = "TrustTestUtils"
+
+/**
+ * Waits for [conditionFunction] to be true with a failed assertion if it is not after [maxWait]
+ * ms.
+ *
+ * The condition function can perform additional logic (for example, logging or attempting to make
+ * the condition become true).
+ *
+ * @param conditionFunction function which takes the attempt count & returns whether the condition
+ *                          is met
+ */
+internal fun wait(
+    description: String? = null,
+    maxWait: Long = 1500L,
+    rate: Long = 50L,
+    conditionFunction: (count: Int) -> Boolean
+) {
+    var waited = 0L
+    var count = 0
+    while (!conditionFunction.invoke(count)) {
+        assertWithMessage("Condition exceeded maximum wait time of $maxWait ms: $description")
+            .that(waited <= maxWait)
+            .isTrue()
+        waited += rate
+        count++
+        Log.i(TAG, "Waiting for $description ($waited/$maxWait) #$count")
+        Thread.sleep(rate)
+    }
+}
diff --git a/tools/locked_region_code_injection/Android.bp b/tools/locked_region_code_injection/Android.bp
index 3e12971..6efd1f6 100644
--- a/tools/locked_region_code_injection/Android.bp
+++ b/tools/locked_region_code_injection/Android.bp
@@ -12,10 +12,10 @@
     manifest: "manifest.txt",
     srcs: ["src/**/*.java"],
     static_libs: [
-        "asm-7.0",
-        "asm-commons-7.0",
-        "asm-tree-7.0",
-        "asm-analysis-7.0",
+        "asm-9.2",
+        "asm-commons-9.2",
+        "asm-tree-9.2",
+        "asm-analysis-9.2",
         "guava-21.0",
     ],
 }
diff --git a/tools/sdkparcelables/Android.bp b/tools/sdkparcelables/Android.bp
index ec2bffd..6ebacd8 100644
--- a/tools/sdkparcelables/Android.bp
+++ b/tools/sdkparcelables/Android.bp
@@ -14,7 +14,7 @@
         "src/**/*.kt",
     ],
     static_libs: [
-        "asm-7.0",
+        "asm-9.2",
     ],
 }
 
diff --git a/tools/traceinjection/Android.bp b/tools/traceinjection/Android.bp
index 1395c5f..39d1b1c 100644
--- a/tools/traceinjection/Android.bp
+++ b/tools/traceinjection/Android.bp
@@ -12,10 +12,10 @@
     manifest: "manifest.txt",
     srcs: ["src/**/*.java"],
     static_libs: [
-        "asm-7.0",
-        "asm-commons-7.0",
-        "asm-tree-7.0",
-        "asm-analysis-7.0",
+        "asm-9.2",
+        "asm-commons-9.2",
+        "asm-tree-9.2",
+        "asm-analysis-9.2",
         "guava-21.0",
     ],
 }